Compare commits
10 Commits
dcdadfc215
...
b7894f10ca
Author | SHA1 | Date | |
---|---|---|---|
b7894f10ca | |||
9392eecab1 | |||
cc370cedbf | |||
931f763e12 | |||
d231b64544 | |||
f5e321c443 | |||
bcabef7925 | |||
53bfd71f9f | |||
8d089808b6 | |||
d167d81dfc |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,5 +3,5 @@
|
|||
*.d
|
||||
*.o
|
||||
*.out
|
||||
bin/*
|
||||
build/*
|
||||
bin/
|
||||
build/
|
||||
|
|
|
@ -9,8 +9,8 @@ before_script:
|
|||
- make clean all -C ${HW_DIR}
|
||||
stages:
|
||||
- build
|
||||
- run
|
||||
- test
|
||||
- run
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
|
|
593
hw5-doc/README.md
Normal file
593
hw5-doc/README.md
Normal file
|
@ -0,0 +1,593 @@
|
|||
# Homework 5 - CSE 320 - Spring 2022
|
||||
#### Professor Eugene Stark
|
||||
|
||||
### **Due Date: Friday 5/6/2022 @ 11:59pm**
|
||||
|
||||
## Introduction
|
||||
|
||||
The goal of this assignment is to become familiar with low-level POSIX
|
||||
threads, multi-threading safety, concurrency guarantees, and networking.
|
||||
The overall objective is to implement a server that simulates the behavior
|
||||
of a Private Branch Exchange (PBX) telephone system.
|
||||
As you will probably find this somewhat difficult, to grease the way
|
||||
I have provided you with a design for the server, as well as binary object
|
||||
files for almost all the modules. This means that you can build a
|
||||
functioning server without initially facing too much complexity.
|
||||
In each step of the assignment, you will replace one of my
|
||||
binary modules with one built from your own source code. If you succeed
|
||||
in replacing all of my modules, you will have completed your own
|
||||
version of the server.
|
||||
|
||||
For this assignment, there are four modules to work on:
|
||||
|
||||
* Server initialization (`main.c`)
|
||||
* Server module (`server.c`)
|
||||
* PBX module (`pbx.c`)
|
||||
* TU (telephone unit) module (`tu.c`)
|
||||
|
||||
It is probably best if you work on the modules in the order listed.
|
||||
You should turn in whatever modules you have worked on.
|
||||
Though the exact details are not set at this time, I expect that your
|
||||
code will be compiled and tested in the following configurations:
|
||||
|
||||
1. Blackbox tests using your `main.c` and your `server.c` (if implemented,
|
||||
otherwise my `server.o`), and my `pbx.o` and `tu.o`.
|
||||
2. Blackbox tests using the modules you implemented, with mine for the rest.
|
||||
3. Unit tests on your `pbx.c` in isolation.
|
||||
4. Unit tests on your `tu.c` in isolation.
|
||||
|
||||
For each configuration, you will receive points for whatever test cases in
|
||||
that configuration are passed. If you are able to achieve some reasonable
|
||||
functionality in a module, it will probably be beneficial to submit it.
|
||||
It is probably not a good strategy to submit modules that are completely
|
||||
broken.
|
||||
It is definitely not a good strategy to submit code that does not compile,
|
||||
as this might prevent you from getting any points at all!
|
||||
|
||||
### Takeaways
|
||||
|
||||
After completing this homework, you should:
|
||||
|
||||
* Have a basic understanding of socket programming
|
||||
* Understand thread execution, mutexes, and semaphores
|
||||
* Have an understanding of POSIX threads
|
||||
* Have some insight into the design of concurrent data structures
|
||||
* Have enhanced your C programming abilities
|
||||
|
||||
## Hints and Tips
|
||||
|
||||
* We strongly recommend you check the return codes of all system
|
||||
calls. This will help you catch errors.
|
||||
|
||||
* **BEAT UP YOUR OWN CODE!** Exercise your modules with rapid and
|
||||
concurrent calls to ensure that they do not crash or deadlock.
|
||||
Ideally, besides basic sequential tests, you would write multi-threaded
|
||||
test drivers to achieve this. We will use tests of this nature in grading.
|
||||
|
||||
* Your code should **NEVER** crash. We will be deducting points for
|
||||
each time your program crashes during grading. Make sure your code
|
||||
handles invalid usage gracefully.
|
||||
|
||||
* You should make use of the macros in `debug.h`. In non-debugging,
|
||||
"production" use, your code should basically be silent, except perhaps
|
||||
to emit a one-line error message just before terminating in case a fatal
|
||||
error situation requires an abort.
|
||||
**FOLLOW THIS GUIDELINE!** `make debug` is your friend.
|
||||
|
||||
> :scream: **DO NOT** modify any of the header files provided to you in the base code.
|
||||
> These have to remain unmodified so that the modules can interoperate correctly.
|
||||
> We will replace these header files with the original versions during grading.
|
||||
> You are of course welcome to create your own header files that contain anything
|
||||
> you wish.
|
||||
|
||||
## Helpful Resources
|
||||
|
||||
### Textbook Readings
|
||||
|
||||
You should make sure that you understand the material covered in
|
||||
chapters **11.4** and **12** of **Computer Systems: A Programmer's
|
||||
Perspective 3rd Edition** before starting this assignment. These
|
||||
chapters cover networking and concurrency in great detail and will be
|
||||
an invaluable resource for this assignment.
|
||||
|
||||
### pthread Man Pages
|
||||
|
||||
The pthread man pages can be easily accessed through your terminal.
|
||||
However, [this opengroup.org site](http://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html)
|
||||
provides a list of all the available functions. The same list is also
|
||||
available for [semaphores](http://pubs.opengroup.org/onlinepubs/7908799/xsh/semaphore.h.html).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Fetch and merge the base code for `hw5` as described in `hw0`. You can
|
||||
find it at this link: https://gitlab02.cs.stonybrook.edu/cse320/hw5.
|
||||
Remember to use the `--stategy-option=theirs` flag for the `git merge`
|
||||
command to avoid merge conflicts in the Gitlab CI file.
|
||||
|
||||
### The Base Code
|
||||
|
||||
Here is the structure of the base code:
|
||||
|
||||
```
|
||||
.
|
||||
├── demo
|
||||
│ └── pbx
|
||||
├── hw5.sublime-project
|
||||
├── include
|
||||
│ ├── debug.h
|
||||
│ ├── pbx.h
|
||||
│ ├── server.h
|
||||
│ └── tu.h
|
||||
├── lib
|
||||
│ └── pbx.a
|
||||
├── Makefile
|
||||
├── src
|
||||
│ ├── globals.c
|
||||
│ ├── main.c
|
||||
│ ├── pbx.c
|
||||
│ ├── server.c
|
||||
│ └── tu.c
|
||||
└── tests
|
||||
├── basecode_tests.c
|
||||
├── script_tester.c
|
||||
└── __test_includes.h
|
||||
|
||||
```
|
||||
|
||||
The base code consists of header files that define module interfaces,
|
||||
a library `pbx.a` containing binary object code for my
|
||||
implementations of the modules, a source code file `globals.c` that
|
||||
contains definitions of some global variables, and a source code file `main.c`
|
||||
that contains a stub for function `main()`. The `Makefile` is
|
||||
designed to compile any existing source code files and then link them
|
||||
against the provided library. The result is that any modules for
|
||||
which you provide source code will be included in the final
|
||||
executable, but modules for which no source code is provided will be
|
||||
pulled in from the library. The `pbx.a` library was compiled
|
||||
without `-DDEBUG`, so it does not produce any debugging printout.
|
||||
Also provided is a demonstration binary `demo/pbx`. This executable
|
||||
is a complete implementation of the PBX server, which is intended to help
|
||||
you understand the specifications from a behavioral point of view.
|
||||
It also does not produce any debugging printout, however.
|
||||
|
||||
Most of the detailed specifications for the various modules and functions
|
||||
that you are to implement are provided in comments that precede stubs for
|
||||
these functions in the various source files. In the interests of brevity and
|
||||
avoiding redundancy, those specifications are not reproduced in this document.
|
||||
Nevertheless, the information they contain is very important, and constitutes
|
||||
the authoritative specification of what you are to implement.
|
||||
|
||||
> :scream: The various functions and variables defined in the header files
|
||||
> constitute the **entirety** of the interfaces between the modules in this program.
|
||||
> Use these functions and variables as described and **do not** introduce any
|
||||
> additional functions or global variables as "back door" communication paths
|
||||
> between the modules. If you do, the modules you implement will not interoperate
|
||||
> properly with my implementations, and it will also likely negatively impact
|
||||
> our ability to test your code.
|
||||
|
||||
The function stubs that appear in the various source files have been
|
||||
commented out in the basecode. This is important, because when the linker
|
||||
does not see any definitions for these functions, it will link in binaries
|
||||
from the `pbx.a` library. If you choose to implement a function in one of the
|
||||
modules, you must uncomment the stubs for all the other functions in that module,
|
||||
otherwise the linker will still link the library version of that module,
|
||||
which will result in "multiply defined" errors at link time.
|
||||
|
||||
## The PBX Server: Overview
|
||||
|
||||
"PBX" is a simple implementation of a server that simulates a PBX
|
||||
telephone system. A PBX is a private telephone exchange that is used
|
||||
within a business or other organization to allow calls to be placed
|
||||
between telephone units (TUs) attached to the system, without having to
|
||||
route those calls over the public telephone network. We will use the
|
||||
familiar term "extension" to refer to one of the TUs attached to a PBX.
|
||||
|
||||
The PBX system provides the following basic capabilities:
|
||||
|
||||
* *Register* a TU as an extension in the system.
|
||||
* *Unregister* a previously registered extension.
|
||||
|
||||
Once a TU has been registered, the following operations are available
|
||||
to perform on it:
|
||||
|
||||
* *Pick up* the handset of a registered TU. If the TU was ringing,
|
||||
then a connection is established with a calling TU. If the TU was not
|
||||
ringing, then the user hears a dial tone over the receiver.
|
||||
* *Hang up* the handset of a registered TU. Any call in progress is
|
||||
disconnected.
|
||||
* *Dial* an extension on a registered TU. If the dialed extension is
|
||||
currently "on hook" (*i.e.* the telephone handset is on the switchhook),
|
||||
then the dialed extension starts to ring, indicating the presence of an
|
||||
incoming call, and a "ring back" notification is played over the receiver
|
||||
of the calling extension. Otherwise, if the dialed extension is "off hook",
|
||||
then a "busy signal" notification is played over the receiver of the
|
||||
calling extension.
|
||||
* *Chat* over the connection made when one TU has dialed an extension
|
||||
and the called extension has picked up.
|
||||
|
||||
The basic idea of these operations should be simple and familiar,
|
||||
since I am sure that everyone has used a telephone system :wink:.
|
||||
However, we will need a rather more detailed and complete specification
|
||||
than just this simple overview.
|
||||
|
||||
## The PBX Server: Details
|
||||
|
||||
Our PBX system will be implemented as a multi-threaded network server.
|
||||
When the server is started, a **master server** thread sets up a socket on
|
||||
which to listen for connections from clients (*i.e.* the TUs). When a network
|
||||
connection is accepted, a **client service thread** is started to handle requests
|
||||
sent by the client over that connection. The client service thread registers
|
||||
the client with the PBX system and is assigned an extension number.
|
||||
The client service thread then executes a service loop in which it repeatedly
|
||||
receives a **message** sent by the client, performs some operation determined
|
||||
by the message, and sends one or more messages in response.
|
||||
The server will also send messages to a client asynchronously as a result of actions
|
||||
performed on behalf of other clients.
|
||||
For example, if one client sends a "dial" message to dial another extension,
|
||||
then if that extension is currently on-hook it will receive an asynchronous
|
||||
"ring" message, indicating that the ringer is to be activated.
|
||||
|
||||
Messages from a client to a server represent commands to be performed.
|
||||
Except for messages containing "chat" sent from a connected TU, every message
|
||||
from the server to a client will consist of a notification of the current state
|
||||
of that client, as it is currently understood by the server.
|
||||
Usually these messages will inform the client of a state change that has occurred
|
||||
as a result of a command the client sent, or of an asynchronous state change
|
||||
that has occurred as a result of a command sent by some other client.
|
||||
If a command sent by a client does not result in any state change, then the
|
||||
response sent by the server will simply report the unchanged state.
|
||||
|
||||
> :nerd: One of the basic tenets of network programming is that a
|
||||
> network connection can be broken at any time and the parties using
|
||||
> such a connection must be able to handle this situation. In the
|
||||
> present context, the client's connection to the PBX server may
|
||||
> be broken at any time, either as a result of explicit action by the
|
||||
> client or for other reasons. When disconnection of the client is
|
||||
> noticed by the client service thread, the server acts as though the client
|
||||
> had sent an explicit **hangup** command, the client is then unregistered from
|
||||
> the PBX, and the client service thread terminates.
|
||||
|
||||
The PBX system maintains the set of registered clients in the form of a mapping
|
||||
from assigned extension numbers to clients.
|
||||
It also maintains, for each registered client, information about the current
|
||||
state of the TU for that client. The following are the possible states of a TU:
|
||||
|
||||
* **On hook**: The TU handset is on the switchhook and the TU is idle.
|
||||
* **Ringing**: The TU handset is on the switchhook and the TU ringer is
|
||||
active, indicating the presence of an incoming call.
|
||||
* **Dial tone**: The TU handset is off the switchhook and a dial tone is
|
||||
being played over the TU receiver.
|
||||
* **Ring back**: The TU handset is off the switchhook and a "ring back"
|
||||
signal is being played over the TU receiver.
|
||||
* **Busy signal**: The TU handset is off the switchhook and a "busy"
|
||||
signal is being played over the TU receiver.
|
||||
* **Connected**: The TU handset is off the switchhook and a connection has been
|
||||
established between this TU and the TU at another extension. In this state
|
||||
it is possible for users at the two TUs to "chat" with each other over the
|
||||
connection.
|
||||
* **Error**: The TU handset is off the switchhook and an "error" signal
|
||||
is being played over the TU receiver.
|
||||
|
||||
Transitions between TU states occur in response to messages received from the
|
||||
associated client, and sometimes also asynchronously in conjunction with
|
||||
state transitions of other TUs.
|
||||
The list below specifies all the possible transitions between states that a TU
|
||||
can perform. The arrival of any message other than those explicitly listed for
|
||||
each particular state does not cause any transition between states to take place.
|
||||
|
||||
* When in the **on hook** state:
|
||||
|
||||
* A **pickup** message from the client will cause a transition to the **dial tone** state.
|
||||
* An asynchronous transition to the **ringing** state is also possible from this state.
|
||||
|
||||
* When in the **ringing** state:
|
||||
|
||||
* A **pickup** message from the client will cause a transition to the **connected** state.
|
||||
Simultaneously, the calling TU will also make an asynchronous transition from the
|
||||
**ring back** state to the **connected** state. The PBX will establish a connection
|
||||
between these two TUs, which we will refer to as *peers* as long as the connection
|
||||
remains established.
|
||||
* An asynchronous transition to the **on hook** state is also possible from this state.
|
||||
This would occur if the calling TU hangs up before the call is answered.
|
||||
|
||||
* When in the **dial tone** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
* A **dial** message from the client will cause a transition to either the **error**,
|
||||
**busy signal**, or **ring back** states, depending firstly on whether or not a TU
|
||||
is currently registered at the dialed extension, and secondly, whether the TU at
|
||||
the dialed extension is currently in the **on hook** state or in some other state.
|
||||
If there is no TU registered at the dialed extension, the transition will be to the
|
||||
**error** state.
|
||||
If there is a TU registered at the dialed extension, then if that extension is
|
||||
currently not in the **on hook** state, then the transition will be to the
|
||||
**busy signal** state, otherwise the transition will be to the **ring back** state.
|
||||
In the latter case, the TU at the dialed extension makes an asynchronous transition
|
||||
to the **ringing** state, simultaneously with the transition of the calling TU to the
|
||||
**ring back** state.
|
||||
|
||||
* When in the **ring back** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
Simultaneously, the called TU will make an asynchronous transition from the **ringing**
|
||||
state to the **on hook** state.
|
||||
* An asynchronous transition to the **connected** state (if the called TU picks up)
|
||||
or to the **dial tone** state (if the called TU unregisters) is also possible from this state.
|
||||
|
||||
* When in the **busy signal** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
|
||||
* When in the **connected** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
Simultaneously, the peer TU will make a transition from the **connected** state to
|
||||
the **dial tone** state.
|
||||
* An asynchronous transition to the **dial tone** state is also possible from this state.
|
||||
This would occur if the peer TU were to hang up.
|
||||
|
||||
* When in the **error** state:
|
||||
|
||||
* A **hangup** message from the client will cause a transition to the **on hook** state.
|
||||
|
||||
Messages are sent between the client and server in a text-based format,
|
||||
in which each message consists of a single line of text, the end of which is
|
||||
indicated by the two-byte line termination sequence `"\r\n"`.
|
||||
There is no *a priori* limitation on the length of the line of text that is sent
|
||||
in a message.
|
||||
The possible messages that can be sent are listed below.
|
||||
The initial keywords in each message are case-sensitive.
|
||||
|
||||
* Commands from Client to Server
|
||||
* **pickup**
|
||||
* **hangup**
|
||||
* **dial** #, where # is the number of the extension to be dialed.
|
||||
* **chat** ...arbitrary text...
|
||||
|
||||
* Responses from Server to Client
|
||||
* **ON HOOK** #, where # reports the extension number of the client.
|
||||
* **RINGING**
|
||||
* **DIAL TONE**
|
||||
* **RING BACK**
|
||||
* **CONNECTED** #, where # is the number of the extension to which the
|
||||
connection exists.
|
||||
* **ERROR**
|
||||
* **CHAT** ...arbitrary text...
|
||||
|
||||
### Demonstration Server
|
||||
|
||||
To help you understand what the PBX server is supposed to do, I have provided
|
||||
a complete implementation for demonstration purposes.
|
||||
Run it by typing the following command:
|
||||
|
||||
```
|
||||
$ demo/pbx -p 3333
|
||||
```
|
||||
|
||||
You may replace `3333` by any port number 1024 or above (port numbers below 1024
|
||||
are generally reserved for use as "well-known" ports for particular services,
|
||||
and require "root" privilege to be used).
|
||||
The server should report that it has been initialized and is listening
|
||||
on the specified port.
|
||||
From another terminal window, use `telnet` to connect to the server as follows:
|
||||
|
||||
```
|
||||
$ telnet localhost 3333
|
||||
Trying 127.0.0.1...
|
||||
Connected to localhost.
|
||||
Escape character is '^]'.
|
||||
ON HOOK 4
|
||||
```
|
||||
|
||||
You can now issue commands to the server:
|
||||
|
||||
```
|
||||
pickup
|
||||
DIAL TONE
|
||||
dial 5
|
||||
ERROR
|
||||
hangup
|
||||
ON HOOK 4
|
||||
```
|
||||
|
||||
If you make a second connection to the server from yet another terminal window,
|
||||
you can make calls.
|
||||
|
||||
Note that the server will silently ignore syntactically incorrect commands,
|
||||
such as "dial" without an extension number. If commands are issued when the
|
||||
TU at the server side is in an inappropriate state (for example, if "dial 5"
|
||||
is sent when the TU is "on hook"), then a response will be sent by the server
|
||||
that just repeats the state of the TU, which does not change.
|
||||
|
||||
## Task I: Server Initialization
|
||||
|
||||
When the base code is compiled and run, it will print out a message
|
||||
saying that the server will not function until `main()` is
|
||||
implemented. This is your first task. The `main()` function will
|
||||
need to do the following things:
|
||||
|
||||
- Obtain the port number to be used by the server from the command-line
|
||||
arguments. The port number is to be supplied by the required option
|
||||
`-p <port>`.
|
||||
|
||||
- Install a `SIGHUP` handler so that clean termination of the server can
|
||||
be achieved by sending it a `SIGHUP`. Note that you need to use
|
||||
`sigaction()` rather than `signal()`, as the behavior of the latter is
|
||||
not well-defined in a multithreaded context.
|
||||
|
||||
- Set up the server socket and enter a loop to accept connections
|
||||
on this socket. For each connection, a thread should be started to
|
||||
run function `pbx_client_service()`.
|
||||
|
||||
These things should be relatively straightforward to accomplish, given the
|
||||
information presented in class and in the textbook. If you do them properly,
|
||||
the server should function and accept connections on the specified port,
|
||||
and you should be able to connect to the server using the test client.
|
||||
|
||||
## Task II: Server Module
|
||||
|
||||
In this part of the assignment, you are to implement the server module,
|
||||
which provides the function `pbx_client_service()` that is invoked
|
||||
when a client connects to the server. You should implement this function
|
||||
in the `src/server.c` file.
|
||||
|
||||
The `pbx_client_service` function is invoked as the **thread function**
|
||||
for a thread that is created (using ``pthread_create()``) to service a
|
||||
client connection.
|
||||
The argument is a pointer to the integer file descriptor to be used
|
||||
to communicate with the client. Once this file descriptor has been
|
||||
retrieved, the storage it occupied needs to be freed.
|
||||
The thread must then become detached, so that it does not have to be
|
||||
explicitly reaped, it must initialize a new TU with that file descriptor,
|
||||
and it must register the TU with the PBX module under a particular
|
||||
extension number. The demo program uses the file descriptor as the
|
||||
extension number, but you may choose a different scheme if you wish.
|
||||
Finally, the thread should enter a service loop in which it repeatedly
|
||||
receives a message sent by the client, parses the message, and carries
|
||||
out the specified command.
|
||||
The actual work involved in carrying out the command is performed by calling
|
||||
the functions provided by the PBX module.
|
||||
These functions will also send the required response back to the client
|
||||
(each syntactically correct command will elicit a single response that
|
||||
contains the resulting state of the TU)
|
||||
so the server module need not be directly concerned with that.
|
||||
|
||||
## Task III: PBX Module
|
||||
|
||||
The PBX module is the central module in the implementation of the server.
|
||||
It provides the functions listed below, for which more detailed specifications
|
||||
are given in the source file `pbx.c`.
|
||||
|
||||
* `PBX *pbx_init()`: Initialize a new PBX.
|
||||
* `void pbx_shutdown(PBX *pbx)`: Shut down a PBX.
|
||||
* `int pbx_register(PBX *pbx, TU *tu, int ext)`: Register a TU with a PBX.
|
||||
* `int pbx_unregister(PBX *pbx, TU *tu)`: Unregister a TU from a PBX.
|
||||
* `int pbx_dial(PBX *pbx, TU *tu, int ext)`: Dial an extension.
|
||||
|
||||
The PBX module will need to maintain a registry of connected clients and
|
||||
manage the TU objects associated with these clients.
|
||||
The PBX will need to be able to map each extension number to the associated TU object.
|
||||
As the PBX object will be accessed concurrently by multiple threads,
|
||||
it will need to provide appropriate synchronization (for example, using mutexes and/or
|
||||
semaphores) to ensure correct and reliable operation.
|
||||
Finally, the `pbx_shutdown()` function is required to shut down the network connections
|
||||
to all registered clients (the `shutdown(2)` function can be used to shut down a socket
|
||||
for reading, writing, or both, without closing the associated file descriptor)
|
||||
and it is then required to wait for all the client service threads to unregister
|
||||
the associated TUs before returning. Consider using a semaphore, possibly in conjunction
|
||||
with additional bookkeeping variables, for this purpose.
|
||||
|
||||
## Task IV: TU Module
|
||||
|
||||
The TU module implements objects that simulate a telephone unit.
|
||||
The functions provided by this module are shown below. More detailed specifications
|
||||
can be found in the source file `tu.c`.
|
||||
|
||||
* `TU *tu_init(int fd)`: Initialize a new TU with the file descriptor for a client.
|
||||
* `void tu_ref(TU *tu, char *reason)`: Increase the reference count (see below) of a TU by one.
|
||||
* `void tu_unref(TU *tu, char *reason)`: Decrease the reference count of a TU by one,
|
||||
freeing the TU and associated resources if the reference count reaches zero.
|
||||
* `int tu_fileno(TU *tu)`: Get the file descriptor for the network connection underlying a TU.
|
||||
* `int tu_extension(TU *tu)`: Get the extension number for a TU.
|
||||
* `int tu_set_extension(TU *tu, int ext)`: Set the extension number for a TU.
|
||||
* `int tu_pickup(TU *tu)`: Take a TU receiver off-hook (i.e. pick up the handset).
|
||||
* `int tu_hangup(TU *tu)`: Hang up a TU (i.e. replace the handset on the switchhook).
|
||||
* `int tu_dial(TU *tu, int ext)`: Use a TU to originate a call to a specified extension.
|
||||
* `int tu_chat(TU *tu, char *msg)`: "Chat" during a call to another TU.
|
||||
|
||||
Each TU object will contain the file descriptor of an underlying network connection
|
||||
to a client, as well as a representation of the current state of the TU.
|
||||
It will need to use the file descriptor to issue responses to the client,
|
||||
as well as any required asynchronous notifications, whenever any of the `tu_xxx`
|
||||
functions is called.
|
||||
Since the TU objects will be accessed concurrently by multiple threads,
|
||||
the TU module will need to provide appropriate synchronization
|
||||
Changes to the state of a TU will require exclusive access to the TU.
|
||||
Some operations require simultaneous changes of state of two TUs; these will require
|
||||
exclusive access to both TUs at the same time in order to ensure the simultaneity of
|
||||
the state changes. Care must be taken to avoid deadlock when obtaining exclusive
|
||||
access to two TUs at the same time.
|
||||
Sending responses and notifications to the network client managed by a TU will also
|
||||
require exclusive access to the TU, in order to ensure that messages sent by separate
|
||||
threads are serialized over the network connection, rather than possibly intermingled.
|
||||
|
||||
In order for TU objects to exist independently of a PBX object, yet still avoid the possibility
|
||||
of having "dangling pointers" to TU objects that have unexpectedly been freed, the TU module
|
||||
uses a *reference counting* scheme. The basic idea of reference counts is that they are
|
||||
an integer field stored in an object that keeps track of the number of references
|
||||
(*i.e.* pointers) that exist to that object. When a pointer to a TU object is copied,
|
||||
the reference count on that object must be increased in order to account for the additional
|
||||
pointer that now exists. When a pointer to a TU object is discarded, the reference count on
|
||||
that object must be decreased in order to account for the pointer that no longer exists.
|
||||
When the reference count on a TU object has been decremented to zero, that means there are
|
||||
no longer any pointers by which that object can be accessed and therefore the object can
|
||||
safely be freed. Incrementing and decrementing the reference count of a TU is performed
|
||||
by calling the `tu_ref()` and `tu_unref()` functions. These functions take as an argument
|
||||
the TU to be manipulated, but in addition they take a second argument `msg`.
|
||||
This is for debugging purposes: when you increment or decrement a TU you should supply
|
||||
as the `msg` argument a string giving the reason why the reference count is being changed.
|
||||
If, when you implement the TU module, you have the `tu_ref()` and `tu_unref()` functions
|
||||
announce when they are called, along with the reason why, it will make it easier to
|
||||
debug reference count issues.
|
||||
|
||||
## Test Exerciser
|
||||
|
||||
The `tests` directory in the basecode contains a test driver with a few selected tests
|
||||
to get you started. These tests are coded using Criterion as usual, but note that it
|
||||
is important that when you run the tests you supply the `-j1` argument to `pbx_tests`.
|
||||
This is because each test starts its own server instance and if you run them all
|
||||
concurrently only one server instance will be able to bind the port number that is
|
||||
being used and the other server instances will fail.
|
||||
|
||||
The file `basecode_tests.c` contains the Criterion portion of the tests and the file
|
||||
`script_tester.c` contains the test driver that they use. The file `__test_includes.h`
|
||||
contains some macros and declarations that are shared between the two files.
|
||||
The overall idea of the tests are that they are table-driven. Each test has a "script",
|
||||
which defines an array of `TEST_STEP` structures. The `TEST_STEP` structure is
|
||||
defined in `__test_includes.h`. Each step contains an `id`, which identifies a particular
|
||||
TU that should execute the step, a `command` which is the command to be sent to the TU,
|
||||
an `id_to_dial` field which specifies the extension to be dialed in case the `command`
|
||||
is TU_DIAL, a `response` field, which specifies the TU state that it is expected will
|
||||
be sent in the response from the server, and a `timeout` field, which can be used to
|
||||
place a limit on how long the tester will wait for a response from the server.
|
||||
The `timeout` values themselves have type `struct timeval` (see `man 2 setitimer` for
|
||||
a definition). There are several predefined timeout values of various lengths defined
|
||||
in `__test_includes.h`. The idea is that if the expected response from the server does
|
||||
not arrive before the timeout expires, then the test script fails. A timeout value of
|
||||
`ZERO_SEC` means the test driver will wait indefinitely for the response from the server.
|
||||
|
||||
To use the full capabilities of the test driver is probably somewhat complicated,
|
||||
since if you get multiple TUs sending commands in a concurrent fashion you have to
|
||||
start worrying about asynchronous notifications from the server "crossing in front of"
|
||||
the expected response to a command. But you can probably follow the basic pattern in
|
||||
the scripts that I have provided to create other similar scripts that test the ability of
|
||||
your server process other series of commands issued in rapid succession.
|
||||
|
||||
## Debugging Multi-threaded Code with `gdb`
|
||||
|
||||
The `gdb` debugger has features for debugging multi-threaded code.
|
||||
In particular, it is aware of the presence of multiple threads and it provides
|
||||
commands for you to switch the focus of debugging from one thread to another.
|
||||
Use the `info threads` command to get a list of the existing threads.
|
||||
Use the command `thread nnn` (replace `nnn` by the thread number) to switch
|
||||
the focus of debugging to that thread. Once you have done, this, the `gdb`
|
||||
commands such as `bt` that examine the stack will be executed with respect
|
||||
to the selected thread. Threads can be stopped and started independently
|
||||
using `gdb`, as well.
|
||||
|
||||
## Submission Instructions
|
||||
|
||||
Make sure your hw5 directory looks similarly to the way it did
|
||||
initially and that your homework compiles (be sure to try compiling
|
||||
both with and without "debug").
|
||||
Note that you should omit any source files for modules that you did not
|
||||
complete, and that you might have some source and header files in addition
|
||||
to those shown. You are also, of course, encouraged to create Criterion
|
||||
tests for your code.
|
||||
|
||||
It would definitely be a good idea to use `valgrind` to check your program
|
||||
for memory and file descriptor leaks. Keeping track of allocated objects
|
||||
and making sure to free them is potentially one of the more challenging aspects
|
||||
of this assignment.
|
||||
|
||||
To submit, run `git submit hw5`.
|
199
hw5/include/csapp.h
Normal file
199
hw5/include/csapp.h
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* csapp.h - prototypes and definitions for the CS:APP3e book
|
||||
*/
|
||||
/* $begin csapp.h */
|
||||
#ifndef __CSAPP_H__
|
||||
#define __CSAPP_H__
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
#include <semaphore.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
|
||||
/* Default file permissions are DEF_MODE & ~DEF_UMASK */
|
||||
/* $begin createmasks */
|
||||
#define DEF_MODE S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
|
||||
#define DEF_UMASK S_IWGRP|S_IWOTH
|
||||
/* $end createmasks */
|
||||
|
||||
/* Simplifies calls to bind(), connect(), and accept() */
|
||||
/* $begin sockaddrdef */
|
||||
typedef struct sockaddr SA;
|
||||
/* $end sockaddrdef */
|
||||
|
||||
/* Persistent state for the robust I/O (Rio) package */
|
||||
/* $begin rio_t */
|
||||
#define RIO_BUFSIZE 8192
|
||||
typedef struct {
|
||||
int rio_fd; /* Descriptor for this internal buf */
|
||||
int rio_cnt; /* Unread bytes in internal buf */
|
||||
char *rio_bufptr; /* Next unread byte in internal buf */
|
||||
char rio_buf[RIO_BUFSIZE]; /* Internal buffer */
|
||||
} rio_t;
|
||||
/* $end rio_t */
|
||||
|
||||
/* External variables */
|
||||
extern int h_errno; /* Defined by BIND for DNS errors */
|
||||
extern char **environ; /* Defined by libc */
|
||||
|
||||
/* Misc constants */
|
||||
#define MAXLINE 8192 /* Max text line length */
|
||||
#define MAXBUF 8192 /* Max I/O buffer size */
|
||||
#define LISTENQ 1024 /* Second argument to listen() */
|
||||
|
||||
/* Our own error-handling functions */
|
||||
void unix_error(char *msg);
|
||||
void posix_error(int code, char *msg);
|
||||
void dns_error(char *msg);
|
||||
// void gai_error(int code, char *msg);
|
||||
void app_error(char *msg);
|
||||
|
||||
/* Process control wrappers */
|
||||
pid_t Fork(void);
|
||||
void Execve(const char *filename, char *const argv[], char *const envp[]);
|
||||
pid_t Wait(int *status);
|
||||
pid_t Waitpid(pid_t pid, int *iptr, int options);
|
||||
void Kill(pid_t pid, int signum);
|
||||
unsigned int Sleep(unsigned int secs);
|
||||
void Pause(void);
|
||||
unsigned int Alarm(unsigned int seconds);
|
||||
void Setpgid(pid_t pid, pid_t pgid);
|
||||
pid_t Getpgrp();
|
||||
|
||||
/* Signal wrappers */
|
||||
typedef void handler_t(int);
|
||||
handler_t *Signal(int signum, handler_t *handler);
|
||||
void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
|
||||
void Sigemptyset(sigset_t *set);
|
||||
void Sigfillset(sigset_t *set);
|
||||
void Sigaddset(sigset_t *set, int signum);
|
||||
void Sigdelset(sigset_t *set, int signum);
|
||||
int Sigismember(const sigset_t *set, int signum);
|
||||
int Sigsuspend(const sigset_t *set);
|
||||
|
||||
/* Sio (Signal-safe I/O) routines */
|
||||
ssize_t sio_puts(char s[]);
|
||||
ssize_t sio_putl(long v);
|
||||
void sio_error(char s[]);
|
||||
|
||||
/* Sio wrappers */
|
||||
ssize_t Sio_puts(char s[]);
|
||||
ssize_t Sio_putl(long v);
|
||||
void Sio_error(char s[]);
|
||||
|
||||
/* Unix I/O wrappers */
|
||||
int Open(const char *pathname, int flags, mode_t mode);
|
||||
ssize_t Read(int fd, void *buf, size_t count);
|
||||
ssize_t Write(int fd, const void *buf, size_t count);
|
||||
off_t Lseek(int fildes, off_t offset, int whence);
|
||||
void Close(int fd);
|
||||
int Select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||
struct timeval *timeout);
|
||||
int Dup2(int fd1, int fd2);
|
||||
void Stat(const char *filename, struct stat *buf);
|
||||
void Fstat(int fd, struct stat *buf) ;
|
||||
|
||||
/* Directory wrappers */
|
||||
DIR *Opendir(const char *name);
|
||||
struct dirent *Readdir(DIR *dirp);
|
||||
int Closedir(DIR *dirp);
|
||||
|
||||
/* Memory mapping wrappers */
|
||||
void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
|
||||
void Munmap(void *start, size_t length);
|
||||
|
||||
/* Standard I/O wrappers */
|
||||
void Fclose(FILE *fp);
|
||||
FILE *Fdopen(int fd, const char *type);
|
||||
char *Fgets(char *ptr, int n, FILE *stream);
|
||||
FILE *Fopen(const char *filename, const char *mode);
|
||||
void Fputs(const char *ptr, FILE *stream);
|
||||
size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
|
||||
/* Dynamic storage allocation wrappers */
|
||||
void *Malloc(size_t size);
|
||||
void *Realloc(void *ptr, size_t size);
|
||||
void *Calloc(size_t nmemb, size_t size);
|
||||
void Free(void *ptr);
|
||||
|
||||
/* Sockets interface wrappers */
|
||||
int Socket(int domain, int type, int protocol);
|
||||
void Setsockopt(int s, int level, int optname, const void *optval, int optlen);
|
||||
void Bind(int sockfd, struct sockaddr *my_addr, int addrlen);
|
||||
void Listen(int s, int backlog);
|
||||
int Accept(int s, struct sockaddr *addr, socklen_t *addrlen);
|
||||
void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
|
||||
|
||||
/* Protocol independent wrappers */
|
||||
void Getaddrinfo(const char *node, const char *service,
|
||||
const struct addrinfo *hints, struct addrinfo **res);
|
||||
void Getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host,
|
||||
size_t hostlen, char *serv, size_t servlen, int flags);
|
||||
void Freeaddrinfo(struct addrinfo *res);
|
||||
void Inet_ntop(int af, const void *src, char *dst, socklen_t size);
|
||||
void Inet_pton(int af, const char *src, void *dst);
|
||||
|
||||
/* DNS wrappers */
|
||||
struct hostent *Gethostbyname(const char *name);
|
||||
struct hostent *Gethostbyaddr(const char *addr, int len, int type);
|
||||
|
||||
/* Pthreads thread control wrappers */
|
||||
void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp,
|
||||
void * (*routine)(void *), void *argp);
|
||||
void Pthread_join(pthread_t tid, void **thread_return);
|
||||
void Pthread_cancel(pthread_t tid);
|
||||
void Pthread_detach(pthread_t tid);
|
||||
void Pthread_exit(void *retval);
|
||||
pthread_t Pthread_self(void);
|
||||
void Pthread_once(pthread_once_t *once_control, void (*init_function)());
|
||||
|
||||
/* POSIX semaphore wrappers */
|
||||
void Sem_init(sem_t *sem, int pshared, unsigned int value);
|
||||
void P(sem_t *sem);
|
||||
void V(sem_t *sem);
|
||||
|
||||
/* Rio (Robust I/O) package */
|
||||
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
|
||||
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
|
||||
void rio_readinitb(rio_t *rp, int fd);
|
||||
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);
|
||||
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
|
||||
|
||||
/* Wrappers for Rio package */
|
||||
ssize_t Rio_readn(int fd, void *usrbuf, size_t n);
|
||||
void Rio_writen(int fd, void *usrbuf, size_t n);
|
||||
void Rio_readinitb(rio_t *rp, int fd);
|
||||
ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n);
|
||||
ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
|
||||
|
||||
/* Reentrant protocol-independent client/server helpers */
|
||||
int open_clientfd(char *hostname, char *port);
|
||||
int open_listenfd(char *port);
|
||||
|
||||
/* Wrappers for reentrant protocol-independent client/server helpers */
|
||||
int Open_clientfd(char *hostname, char *port);
|
||||
int Open_listenfd(char *port);
|
||||
|
||||
|
||||
#endif /* __CSAPP_H__ */
|
||||
/* $end csapp.h */
|
0
hw5/include/excludes.h
Normal file
0
hw5/include/excludes.h
Normal file
1068
hw5/src/csapp.c
Normal file
1068
hw5/src/csapp.c
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1,11 +1,11 @@
|
|||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "pbx.h"
|
||||
#include "server.h"
|
||||
#include "debug.h"
|
||||
#include "csapp.h"
|
||||
|
||||
static void terminate(int status);
|
||||
void sighandler(int sig);
|
||||
|
||||
/*
|
||||
* "PBX" telephone exchange simulation.
|
||||
|
@ -27,12 +27,52 @@ int main(int argc, char* argv[]){
|
|||
// a SIGHUP handler, so that receipt of SIGHUP will perform a clean
|
||||
// shutdown of the server.
|
||||
|
||||
fprintf(stderr, "You have to finish implementing main() "
|
||||
"before the PBX server will function.\n");
|
||||
|
||||
// check arg counts and second arg valid
|
||||
if (argc != 3 || argv[1][0] != '-' || argv[1][1] != 'p' || argv[1][2] != '\0')
|
||||
{
|
||||
fprintf(stderr, "Usage: ./pbx -p <port>\n");
|
||||
terminate(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// check port num
|
||||
int port = 0;
|
||||
char* ptr = argv[2];
|
||||
while (*ptr)
|
||||
{
|
||||
if (*ptr > '9' || *ptr < '0' || port > 65535)
|
||||
{
|
||||
fprintf(stderr, "Please provide a valid port number\n");
|
||||
terminate(EXIT_FAILURE);
|
||||
}
|
||||
port *= 10;
|
||||
port += *ptr - '0';
|
||||
ptr ++;
|
||||
}
|
||||
|
||||
// install SIGHUP handler
|
||||
struct sigaction act;
|
||||
act.sa_handler = sighandler;
|
||||
if(sigaction(SIGHUP, &act, NULL)<0)
|
||||
{
|
||||
fprintf(stderr, "Failed to install a SIGHUP handler");
|
||||
terminate(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// multi threading & socket connection
|
||||
int listenfd, *connfdp;
|
||||
socklen_t clientlen;
|
||||
struct sockaddr_storage clientaddr;
|
||||
pthread_t tid;
|
||||
listenfd = Open_listenfd(argv[2]);
|
||||
while (1) {
|
||||
clientlen = sizeof(struct sockaddr_storage);
|
||||
connfdp = Malloc(sizeof(int));
|
||||
*connfdp = Accept(listenfd, (SA *) &clientaddr, &clientlen);
|
||||
Pthread_create(&tid, NULL, (void*) pbx_client_service, connfdp);
|
||||
}
|
||||
terminate(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/*
|
||||
* Function called to cleanly shut down the server.
|
||||
*/
|
||||
|
@ -42,3 +82,9 @@ static void terminate(int status) {
|
|||
debug("PBX server terminating");
|
||||
exit(status);
|
||||
}
|
||||
|
||||
void sighandler(int sig)
|
||||
{
|
||||
debug("Server turned down successfully...");
|
||||
terminate(EXIT_SUCCESS);
|
||||
}
|
126
hw5/src/pbx.c
126
hw5/src/pbx.c
|
@ -2,6 +2,9 @@
|
|||
* PBX: simulates a Private Branch Exchange.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "pbx.h"
|
||||
#include "debug.h"
|
||||
|
@ -11,10 +14,28 @@
|
|||
*
|
||||
* @return the newly initialized PBX, or NULL if initialization fails.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
typedef struct pbx
|
||||
{
|
||||
pthread_mutex_t mutex;
|
||||
TU *tus[PBX_MAX_EXTENSIONS];
|
||||
} PBX;
|
||||
|
||||
void pbx_unregister_helper(TU *tu);
|
||||
|
||||
PBX *pbx_init() {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
PBX *pbx = malloc(sizeof(PBX));
|
||||
if (!pbx)
|
||||
{
|
||||
debug("ERROR: Fail to alloc memeory for pbx");
|
||||
return NULL;
|
||||
}
|
||||
// init pbx
|
||||
for (size_t i = 0; i < PBX_MAX_EXTENSIONS; i++)
|
||||
pbx->tus[i] = NULL;
|
||||
|
||||
pthread_mutex_init(&pbx->mutex, NULL);
|
||||
return pbx;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -28,10 +49,25 @@ PBX *pbx_init() {
|
|||
*
|
||||
* @param pbx The PBX to be shut down.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
void pbx_shutdown(PBX *pbx) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!pbx)
|
||||
{
|
||||
debug("ERROR: pbx does not extst");
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&pbx->mutex);
|
||||
for (size_t i = 0; i < PBX_MAX_EXTENSIONS; i++)
|
||||
{
|
||||
if (pbx->tus[i])
|
||||
{
|
||||
pbx_unregister_helper(pbx->tus[i]);
|
||||
pbx->tus[i] = NULL;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&pbx->mutex);
|
||||
pthread_mutex_destroy(&pbx->mutex);
|
||||
free(pbx);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -49,10 +85,32 @@ void pbx_shutdown(PBX *pbx) {
|
|||
* @param ext The extension number on which the TU is to be registered.
|
||||
* @return 0 if registration succeeds, otherwise -1.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int pbx_register(PBX *pbx, TU *tu, int ext) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!pbx || !tu)
|
||||
{
|
||||
debug("ERROR: pbx or tu does not exist");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ext > PBX_MAX_EXTENSIONS + 1 || ext < 1)
|
||||
{
|
||||
debug("ERROR: ext is out of range");
|
||||
return -1;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&pbx->mutex);
|
||||
if (pbx->tus[ext])
|
||||
{
|
||||
debug("ERROR: ext already exist");
|
||||
pthread_mutex_unlock(&pbx->mutex);
|
||||
return -1;
|
||||
}
|
||||
tu_ref(tu, "Register tu to pbx");
|
||||
tu_set_extension(tu, ext);
|
||||
pbx->tus[ext] = tu;
|
||||
pthread_mutex_unlock(&pbx->mutex);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -68,13 +126,39 @@ int pbx_register(PBX *pbx, TU *tu, int ext) {
|
|||
* @param tu The TU to be unregistered.
|
||||
* @return 0 if unregistration succeeds, otherwise -1.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int pbx_unregister(PBX *pbx, TU *tu) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!pbx || !tu)
|
||||
{
|
||||
debug("ERROR: pbx or tu does not exist");
|
||||
return -1;
|
||||
}
|
||||
int ext = tu_extension(tu);
|
||||
if (ext > PBX_MAX_EXTENSIONS + 1 || ext < 1)
|
||||
{
|
||||
debug("ERROR: ext is out of range");
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&pbx->mutex);
|
||||
if (!pbx->tus[ext])
|
||||
{
|
||||
debug("ERROR: ext does not exist");
|
||||
pthread_mutex_unlock(&pbx->mutex);
|
||||
return -1;
|
||||
}
|
||||
pbx_unregister_helper(tu);
|
||||
pbx->tus[ext] = NULL;
|
||||
pthread_mutex_unlock(&pbx->mutex);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void pbx_unregister_helper(TU *tu) {
|
||||
tu_hangup(tu);
|
||||
shutdown(tu_fileno(tu), SHUT_RD);
|
||||
tu_unref(tu, "Unregister tu from pbx");
|
||||
}
|
||||
|
||||
/*
|
||||
* Use the PBX to initiate a call from a specified TU to a specified extension.
|
||||
*
|
||||
|
@ -83,9 +167,21 @@ int pbx_unregister(PBX *pbx, TU *tu) {
|
|||
* @param ext The extension number to be called.
|
||||
* @return 0 if dialing succeeds, otherwise -1.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int pbx_dial(PBX *pbx, TU *tu, int ext) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!pbx || !tu)
|
||||
{
|
||||
debug("ERROR: pbx or tu does not exist");
|
||||
return -1;
|
||||
}
|
||||
if (ext > PBX_MAX_EXTENSIONS || ext < 0)
|
||||
{
|
||||
debug("ERROR: ext is out of range");
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&pbx->mutex);
|
||||
int status = tu_dial(tu, pbx->tus[ext]);
|
||||
pthread_mutex_unlock(&pbx->mutex);
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
|
123
hw5/src/server.c
123
hw5/src/server.c
|
@ -3,19 +3,134 @@
|
|||
* Manages interaction with a client telephone unit (TU).
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "debug.h"
|
||||
#include "pbx.h"
|
||||
#include "server.h"
|
||||
|
||||
#include "tu.h"
|
||||
/*
|
||||
* Thread function for the thread that handles interaction with a client TU.
|
||||
* This is called after a network connection has been made via the main server
|
||||
* thread and a new thread has been created to handle the connection.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int pbx_get_next_command(FILE* f, char** str);
|
||||
int pbx_run_command(TU *tu, char* cmd);
|
||||
|
||||
void *pbx_client_service(void *arg) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
// detach the thread
|
||||
pthread_detach(pthread_self());
|
||||
|
||||
// free file descriptor
|
||||
if (!arg)
|
||||
return NULL;
|
||||
int fd = *(int*)arg;
|
||||
free(arg);
|
||||
|
||||
// init TU
|
||||
TU* tu = tu_init(fd);
|
||||
if (!tu)
|
||||
return NULL;
|
||||
|
||||
// open socket
|
||||
FILE *f = fdopen(fd, "r");
|
||||
|
||||
// register tu
|
||||
if (pbx_register(pbx, tu, fd))
|
||||
{
|
||||
// not success
|
||||
fclose(f);
|
||||
tu_unref(tu, "fail to register a TU");
|
||||
return NULL;
|
||||
}
|
||||
else {
|
||||
// success
|
||||
char* command = NULL;
|
||||
while (1)
|
||||
{
|
||||
command = NULL;
|
||||
if (pbx_get_next_command(f, &command))
|
||||
break;
|
||||
debug("Got command: %s", command);
|
||||
pbx_run_command(tu, command);
|
||||
free(command);
|
||||
}
|
||||
// no more command
|
||||
fclose(f);
|
||||
tu_unref(tu, "unregister tu");
|
||||
pbx_unregister(pbx, tu);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* try to get the next command from f and assign to str
|
||||
* return 0 if next command exist
|
||||
* return -1 if next command does not exist or have length 0
|
||||
* return -2 if fail to alloc memory
|
||||
* return -3 if fail to realloc memory
|
||||
*/
|
||||
int pbx_get_next_command(FILE* f, char** str) {
|
||||
int size = 8, index = 0;
|
||||
char *cmd = malloc(sizeof(char) * size);
|
||||
if (!cmd)
|
||||
// fail to alloc memory
|
||||
return -2;
|
||||
|
||||
char c;
|
||||
while ((c = fgetc(f)) != EOF)
|
||||
{
|
||||
if (index == size - 1)
|
||||
{
|
||||
size *= 2;
|
||||
cmd = realloc(cmd, sizeof(char)*size);
|
||||
if (!cmd)
|
||||
// fail to realloc memory
|
||||
return -3;
|
||||
}
|
||||
|
||||
cmd[index++] = c;
|
||||
if (c == '\r' || c == '\n')
|
||||
cmd[index-1] = '\0';
|
||||
|
||||
if (c == '\n')
|
||||
break;
|
||||
}
|
||||
|
||||
// command is empty
|
||||
if (index == 0) {
|
||||
free(cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
cmd[index--] = '\0';
|
||||
*str = cmd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int pbx_run_command(TU *tu, char* cmd) {
|
||||
if (!strcmp(tu_command_names[TU_PICKUP_CMD], cmd))
|
||||
return tu_pickup(tu);
|
||||
if (!strcmp(tu_command_names[TU_HANGUP_CMD], cmd))
|
||||
return tu_hangup(tu);
|
||||
if (cmd == strstr(cmd, tu_command_names[TU_DIAL_CMD]))
|
||||
{
|
||||
char* ptr = &cmd[strlen(tu_command_names[TU_DIAL_CMD])];
|
||||
while (*ptr == ' ')
|
||||
ptr ++;
|
||||
if (!*ptr)
|
||||
return -1;
|
||||
return pbx_dial(pbx, tu, atoi(ptr));
|
||||
}
|
||||
if (cmd == strstr(cmd, tu_command_names[TU_CHAT_CMD]))
|
||||
{
|
||||
char* ptr = &cmd[strlen(tu_command_names[TU_CHAT_CMD])];
|
||||
while (*ptr == ' ')
|
||||
ptr ++;
|
||||
return tu_chat(tu, ptr);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
|
471
hw5/src/tu.c
471
hw5/src/tu.c
|
@ -2,6 +2,8 @@
|
|||
* TU: simulates a "telephone unit", which interfaces a client with the PBX.
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "pbx.h"
|
||||
#include "debug.h"
|
||||
|
@ -13,10 +15,42 @@
|
|||
* @return The TU, newly initialized and in the TU_ON_HOOK state, if initialization
|
||||
* was successful, otherwise NULL.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
typedef struct tu
|
||||
{
|
||||
int fd;
|
||||
FILE *f;
|
||||
int ext;
|
||||
int ref;
|
||||
TU *chat_TU;
|
||||
int chat_locked;
|
||||
TU_STATE state;
|
||||
pthread_mutex_t mutex;
|
||||
} TU;
|
||||
|
||||
int tu_notify(TU *tu);
|
||||
int tu_lock(TU *tu);
|
||||
int tu_unlock(TU *tu);
|
||||
void tu_destroy(TU* tu);
|
||||
|
||||
TU *tu_init(int fd) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
TU *tu;
|
||||
|
||||
tu = malloc(sizeof(TU));
|
||||
if ( !tu ) {
|
||||
debug("ERROR: fail to alloc memory for tu");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tu->fd = fd;
|
||||
tu->f = fdopen(fd, "w");
|
||||
tu->ext = 0;
|
||||
tu->ref = 1;
|
||||
tu->chat_TU = NULL;
|
||||
tu->chat_locked = 0;
|
||||
tu->state = TU_ON_HOOK;
|
||||
pthread_mutex_init(&tu->mutex, NULL);
|
||||
return tu;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -27,10 +61,17 @@ TU *tu_init(int fd) {
|
|||
* @param reason A string describing the reason why the count is being incremented
|
||||
* (for debugging purposes).
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
void tu_ref(TU *tu, char *reason) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!tu)
|
||||
{
|
||||
debug("ERROR: tu does not exist");
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
// debug(reason);
|
||||
tu->ref ++;
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -41,10 +82,20 @@ void tu_ref(TU *tu, char *reason) {
|
|||
* @param reason A string describing the reason why the count is being decremented
|
||||
* (for debugging purposes).
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
void tu_unref(TU *tu, char *reason) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!tu)
|
||||
{
|
||||
debug("ERROR: tu does not exist");
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
// debug(reason);
|
||||
tu->ref --;
|
||||
if (tu->ref == 0)
|
||||
tu_destroy(tu);
|
||||
else
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -57,10 +108,17 @@ void tu_unref(TU *tu, char *reason) {
|
|||
* @param tu
|
||||
* @return the underlying file descriptor, if any, otherwise -1.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int tu_fileno(TU *tu) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!tu)
|
||||
{
|
||||
debug("ERROR: tu does not exist");
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
int fd = tu->fd;
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return fd;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -74,10 +132,17 @@ int tu_fileno(TU *tu) {
|
|||
* @param tu
|
||||
* @return the extension number, if any, otherwise -1.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int tu_extension(TU *tu) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!tu)
|
||||
{
|
||||
debug("ERROR: tu does not exist");
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
int ext = tu->ext;
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return ext;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -88,10 +153,24 @@ int tu_extension(TU *tu) {
|
|||
*
|
||||
* @param tu The TU whose extension is being set.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int tu_set_extension(TU *tu, int ext) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!tu)
|
||||
{
|
||||
debug("ERROR: tu does not exist");
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
if (tu->ext)
|
||||
{
|
||||
debug("ERROR: extention has already been set");
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return -1;
|
||||
}
|
||||
tu->ext = ext;
|
||||
int status = tu_notify(tu);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -125,10 +204,90 @@ int tu_set_extension(TU *tu, int ext) {
|
|||
* @return 0 if successful, -1 if any error occurs that results in the originating
|
||||
* TU transitioning to the TU_ERROR state.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int tu_dial(TU *tu, TU *target) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!tu)
|
||||
{
|
||||
debug("ERROR: tu or target does not exist");
|
||||
return -1;
|
||||
}
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
if (tu->chat_TU)
|
||||
{
|
||||
debug("ERROR: Already in chat");
|
||||
tu_notify(tu);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return -1;
|
||||
}
|
||||
if (tu->state != TU_DIAL_TONE)
|
||||
{
|
||||
debug("ERROR: not in dial state");
|
||||
tu_notify(tu);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return -1;
|
||||
}
|
||||
if (target)
|
||||
{
|
||||
if (tu == target)
|
||||
{
|
||||
debug("ERROR: calling it self");
|
||||
tu->state = TU_BUSY_SIGNAL;
|
||||
int status = tu_notify(tu);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return status;
|
||||
}
|
||||
if (tu->ext < target->ext)
|
||||
{
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
pthread_mutex_lock(&target->mutex);
|
||||
}
|
||||
else {
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_lock(&target->mutex);
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
}
|
||||
if (target->chat_TU)
|
||||
{
|
||||
debug("ERROR: target is already in chat");
|
||||
tu->state = TU_BUSY_SIGNAL;
|
||||
int status = tu_notify(tu);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_unlock(&target->mutex);
|
||||
return status;
|
||||
}
|
||||
if (target->state != TU_ON_HOOK)
|
||||
{
|
||||
debug("ERROR: target not in on hook state");
|
||||
tu->state = TU_BUSY_SIGNAL;
|
||||
int status = tu_notify(tu);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_unlock(&target->mutex);
|
||||
return status;
|
||||
}
|
||||
debug("dialing");
|
||||
tu->ref++;
|
||||
target->ref++;
|
||||
tu->chat_TU = target;
|
||||
target->chat_TU = tu;
|
||||
tu->state = TU_RING_BACK;
|
||||
target->state = TU_RINGING;
|
||||
int status = 0;
|
||||
if (tu_notify(tu))
|
||||
status = -1;
|
||||
if (tu_notify(target))
|
||||
status = -1;
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_unlock(&target->mutex);
|
||||
return status;
|
||||
}
|
||||
else {
|
||||
debug("ERROR: cannot find target");
|
||||
tu->state = TU_ERROR;
|
||||
int status = tu_notify(tu);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return status;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -149,10 +308,54 @@ int tu_dial(TU *tu, TU *target) {
|
|||
* @return 0 if successful, -1 if any error occurs that results in the originating
|
||||
* TU transitioning to the TU_ERROR state.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int tu_pickup(TU *tu) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!tu)
|
||||
{
|
||||
debug("ERROR: tu or target does not exist");
|
||||
return -1;
|
||||
}
|
||||
tu_lock(tu);
|
||||
if (tu->state == TU_ON_HOOK)
|
||||
{
|
||||
debug("TU is on hook");
|
||||
tu->state = TU_DIAL_TONE;
|
||||
int status = tu_notify(tu);
|
||||
tu_unlock(tu);
|
||||
return status;
|
||||
}
|
||||
if (tu->state == TU_RINGING)
|
||||
{
|
||||
if (tu->chat_TU)
|
||||
{
|
||||
if (tu->chat_TU->state == TU_RING_BACK && tu == tu->chat_TU->chat_TU)
|
||||
{
|
||||
debug("connecting");
|
||||
tu->state = TU_CONNECTED;
|
||||
tu->chat_TU->state = TU_CONNECTED;
|
||||
int status = 0;
|
||||
if (tu_notify(tu))
|
||||
status = -1;
|
||||
if (tu_notify(tu->chat_TU))
|
||||
status = -1;
|
||||
tu_unlock(tu);
|
||||
return status;
|
||||
}
|
||||
debug("ERROR: ringing and has chat_TU but chat_TU is not in ring back or tu's chat_TU's chat_TU is not tu");
|
||||
tu_notify(tu);
|
||||
tu_unlock(tu);
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
debug("ERROR: ringing but no chat_TU");
|
||||
tu_unlock(tu);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
debug("already picked up");
|
||||
tu_notify(tu);
|
||||
tu_unlock(tu);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -177,10 +380,107 @@ int tu_pickup(TU *tu) {
|
|||
* @return 0 if successful, -1 if any error occurs that results in the originating
|
||||
* TU transitioning to the TU_ERROR state.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int tu_hangup(TU *tu) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
if (!tu)
|
||||
{
|
||||
debug("ERROR: tu or target does not exist");
|
||||
return -1;
|
||||
}
|
||||
tu_lock(tu);
|
||||
|
||||
if (tu->state == TU_ON_HOOK)
|
||||
{
|
||||
debug("already on hook");
|
||||
int status = tu_notify(tu);
|
||||
tu_unlock(tu);
|
||||
return status;
|
||||
}
|
||||
if (tu->state == TU_CONNECTED || tu->state == TU_RINGING)
|
||||
{
|
||||
if (tu->chat_TU)
|
||||
{
|
||||
debug("disconnecting peer");
|
||||
tu->state = TU_ON_HOOK;
|
||||
tu->chat_TU->state = TU_DIAL_TONE;
|
||||
int status = 0;
|
||||
if (tu_notify(tu))
|
||||
status = -1;
|
||||
if (tu_notify(tu->chat_TU))
|
||||
status = -1;
|
||||
|
||||
tu->chat_TU->chat_TU = NULL;
|
||||
tu->chat_TU->ref --;
|
||||
if (tu->chat_TU->ref <= 0)
|
||||
tu_destroy(tu->chat_TU);
|
||||
else
|
||||
pthread_mutex_unlock(&tu->chat_TU->mutex);
|
||||
|
||||
tu->chat_TU = NULL;
|
||||
tu->ref --;
|
||||
tu->chat_locked = 0;
|
||||
if (tu->ref == 0)
|
||||
tu_destroy(tu);
|
||||
else
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return status;
|
||||
}
|
||||
else {
|
||||
debug("ERROR: connecting or ringing but no peer");
|
||||
tu->state = TU_ON_HOOK;
|
||||
tu_notify(tu);
|
||||
tu_unlock(tu);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (tu->state == TU_RING_BACK)
|
||||
{
|
||||
if (tu->chat_TU)
|
||||
{
|
||||
debug("disconnecting peer");
|
||||
tu->state = TU_ON_HOOK;
|
||||
tu->chat_TU->state = TU_ON_HOOK;
|
||||
int status = 0;
|
||||
if (tu_notify(tu))
|
||||
status = -1;
|
||||
if (tu_notify(tu->chat_TU))
|
||||
status = -1;
|
||||
|
||||
tu->chat_TU->chat_TU = NULL;
|
||||
tu->chat_TU->ref --;
|
||||
if (tu->chat_TU->ref <= 0)
|
||||
tu_destroy(tu->chat_TU);
|
||||
else
|
||||
pthread_mutex_unlock(&tu->chat_TU->mutex);
|
||||
|
||||
tu->chat_TU = NULL;
|
||||
tu->ref --;
|
||||
tu->chat_locked = 0;
|
||||
if (tu->ref == 0)
|
||||
tu_destroy(tu);
|
||||
else
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return status;
|
||||
}
|
||||
else {
|
||||
debug("ERROR: ringing back but no peer");
|
||||
tu->state = TU_ON_HOOK;
|
||||
tu_notify(tu);
|
||||
tu_unlock(tu);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (tu->chat_TU) {
|
||||
debug("ERROR: no chat_TU in these states");
|
||||
tu->state = TU_ON_HOOK;
|
||||
tu_notify(tu);
|
||||
tu_unlock(tu);
|
||||
return -1;
|
||||
}
|
||||
tu->state = TU_ON_HOOK;
|
||||
int status = tu_notify(tu);
|
||||
tu_unlock(tu);
|
||||
return status;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -197,9 +497,120 @@ int tu_hangup(TU *tu) {
|
|||
* @return 0 If the chat was successfully sent, -1 if there is no call in progress
|
||||
* or some other error occurs.
|
||||
*/
|
||||
#if 0
|
||||
#if 1
|
||||
int tu_chat(TU *tu, char *msg) {
|
||||
// TO BE IMPLEMENTED
|
||||
abort();
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
if (tu->state == TU_CONNECTED)
|
||||
{
|
||||
TU *target = tu->chat_TU;
|
||||
if (target)
|
||||
{
|
||||
if (tu->ext < target->ext)
|
||||
{
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
pthread_mutex_lock(&target->mutex);
|
||||
}
|
||||
else {
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_lock(&target->mutex);
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
}
|
||||
if (tu->chat_TU == target && target->chat_TU == tu
|
||||
&& tu->state == TU_CONNECTED && target->state == TU_CONNECTED)
|
||||
{
|
||||
debug("sending msg");
|
||||
int status = 0;
|
||||
if (fprintf(target->f, "CHAT %s\r\n", msg) < 0 || fflush(target->f) < 0)
|
||||
status = -1;
|
||||
if (tu_notify(tu) < 0)
|
||||
status = -1;
|
||||
pthread_mutex_unlock(&target->mutex);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return status;
|
||||
}
|
||||
else
|
||||
{
|
||||
debug("ERROR: target changed or chat_TU do not match");
|
||||
tu_notify(tu);
|
||||
pthread_mutex_unlock(&target->mutex);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug("ERROR: connected but no chat_TU");
|
||||
tu_notify(tu);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
debug("ERROR: not in connected state");
|
||||
tu_notify(tu);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int tu_notify(TU *tu) {
|
||||
int status = 0;
|
||||
|
||||
if (fputs(tu_state_names[tu->state], tu->f) < 0)
|
||||
status = -1;
|
||||
if (tu->state == TU_ON_HOOK)
|
||||
if (fprintf(tu->f, " %d", tu->fd) < 0)
|
||||
status = -1;
|
||||
if (tu->state == TU_CONNECTED)
|
||||
if (fprintf(tu->f, " %d", tu->chat_TU->fd) < 0)
|
||||
status = -1;
|
||||
if (fprintf(tu->f, "\r\n") < 0 || fflush(tu->f) < 0)
|
||||
status = -1;
|
||||
if (status == -1)
|
||||
debug("ERROR: fail to print message to client");
|
||||
return status;
|
||||
}
|
||||
|
||||
int tu_lock(TU *tu) {
|
||||
while (1) {
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
TU *tmp = tu->chat_TU;
|
||||
if (!tu->chat_TU)
|
||||
return 0;
|
||||
if (tu->ext < tmp->ext)
|
||||
{
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
pthread_mutex_lock(&tmp->mutex);
|
||||
tu->chat_locked = 1;
|
||||
}
|
||||
else {
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_lock(&tmp->mutex);
|
||||
pthread_mutex_lock(&tu->mutex);
|
||||
tu->chat_locked = 1;
|
||||
}
|
||||
if (tu->chat_TU == tmp)
|
||||
return 0;
|
||||
|
||||
pthread_mutex_unlock(&tmp->mutex);
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
int tu_unlock(TU *tu) {
|
||||
if (tu->chat_locked)
|
||||
{
|
||||
pthread_mutex_unlock(&tu->chat_TU->mutex);
|
||||
tu->chat_locked = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tu_destroy(TU* tu) {
|
||||
pthread_mutex_unlock(&tu->mutex);
|
||||
pthread_mutex_destroy(&tu->mutex);
|
||||
free(tu);
|
||||
}
|
Loading…
Reference in New Issue
Block a user