feat: add hw5 doc
This commit is contained in:
parent
d167d81dfc
commit
8d089808b6
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`.
|
Loading…
Reference in New Issue
Block a user