CSCI 4061 Project 2: Blather Chat Server/Client
- Due: 11:59pm Tue 5/04/2021
- Approximately 20.0% of total grade
- Submit to Gradescope (Submission will open soon)
- Projects may be done in groups of 1 or 2. Indicate groups in the
GROUP_MEMBERS.txt
file and on Gradescope when submitting - No additional collaboration with other students is allowed. Seek help from course staff if you get stuck for too long.
CODE DISTRIBUTION: p2-code.zip
TEST CODE: p2-tests.zip
CHANGELOG:
- Mon May 3 08:09:48 AM CDT 2021
- One missing testing file,
test_valgrind.supp
has been added top2-tests.zip
. - Sun May 2 10:55:24 PM CDT 2021
Tests for Project 2 are now linked to the spec and available here: p2-tests.zip
There are 20 total tests and each is worth 2 points for passing. Download instructions are in the Testing Section of the spec.
To compensate for the late release of the tests, the due date is extended by 1 day to Tue 5/04 11:59pm.
1 Introduction: A Chat Service
Demo Videos: From the Wed 4/21 Lecture, and a demo from a previous semester.
Systems programming is often associated with communication as this
invariably requires coordinating multiple entities that are related
only based on their desire to share information. This project focuses
on developing a simple chat server and client called blather
. The
basic usage is in two parts.
- Server
- Some user starts
bl_server
which manages the chat "room". The server is non-interactive and will likely only print debugging output as it runs. - Client
- Any user who wishes to chat runs
bl_client
which takes input typed on the keyboard and sends it to the server. The server broadcasts the input to all other clients who can respond by typing their own input.
Chat servers are an old idea dating to the late 1970's and if you have
never used one previously, get online a bit more or read about them on
Wikipedia. Even standard Unix terminals have a built-in ability to
communicate with other users through commands like write
, wall
,
and talk
.
Unlike standard internet chat services, blather
will be restricted
to a single Unix machine and to users with permission to read related
files. However, extending the programs to run via the internet will
be the subject of some discussion.
Like most interesting projects, blather
utilizes a combination of
many different system tools. Look for the following items to arise.
- Multiple communicating processes: clients to servers
- Communication through FIFOs
- Signal handling for graceful server shutdown
- Alarm signals for periodic behavior
- Input multiplexing with
poll()
- Multiple threads in the client to handle typed input versus info from the server
Finally, this project includes some advanced features which may be implemented for Makeup Credit: this credit will be added to your overall Project total to make up for mistakes in this and previous projects.
1.1 Basic Features
The specification below is mainly concerned with basic features of the
blather
server and client. Implementing these can garner full credit
for the assignment.
1.2 ADVANCED Features : Makeup Credit
Some portions of the specification and code are marked ADVANCED to indicate optional features that can be implemented for Makeup Credit. Some of these have dependencies so take care to read carefully.
ADVANCED features may be evaluated using tests and manual inspection criteria that are made available after the standard tests are distributed.
2 Download Code and Setup
As in labs, download the code pack linked at the top of the page. Unzip this which will create a folder and create your files in that folder.
File | State | Notes |
---|---|---|
GROUP_MEMBERS.txt |
Edit | Fill in names of group members to indicate partnerships |
Makefile |
Create | Build project, run tests |
server_funcs.c |
Create | Service routines for the server |
bl_server.c |
Create | Main function for bl_server executable |
bl_client.c |
Create | Main function for bl_client executable |
blather.h |
Provided | Header file which contains required structs, defines, and prototypes |
util.c |
Provided | Utility methods debug messages and checking system call returns |
simpio.c |
Provided | Simplified terminal I/O to get nice interactive sessions |
simpio_demo.c |
Provided | Demonstrates simpio features, model for bl_client.c |
Testing | TBD | Will be released separately. |
3 Demo / ScreenShot
Demonstrating an interactive, dynamic chat session between multiple users is difficult but the screenshot below attempts to do so. It shows the server and several clients mid-chat.
- The server
bl_server
is started in the large terminal on the left and shows output about what is going on. The server is required to show some specific output through the use of thelog_printf()
function as it performs certain operations and can print additional debug information using thedbg_printf()
function. - There are 4
bl_client
instances run by various users who log into the server and specify their name. The lower-right userBarbara
joins later while the upper left userBruce
logs out near the end of the session.
NOTE: A few of the message formats might differ in the image from the expectations described in the rest of the project specification.
Figure 1: Sample blather
server and client runs.
4 Overall Architecture
4.1 What Needs to be Implemented
blather
has two parts: the server and client. Both need to be
written along with service routines in server.c
server_funcs.c
data structure manipulations
Implement the functions in this file to manipulate the server_t
and
client_t
data that will ultimately be used by the server to fulfill
its role.
bl_server.c
main function and signal handlers
Implement the server which manages the interactions between clients in
this file making use of the service functions in server_funcs.c
to get the
job done.
bl_client.c
main function and thread workers
Implement the client which allows a single user to communicate with the server in this file. The client must have multiple threads so you will need to implement some worker functions as thread entry points here.
4.2 What's Already Done
Examine the provided files closely as they give some insight into what work is already done.
blather.h
can be included in most files to make programs aware of the data structures and required functions. It contains documentation of the central data structures.util.c
contains a few functions for logging server messages, debugging, and checking system calls.simpio.c
andsimpio_demo.c
provide a small library and data structure to do terminal I/O nicely. The demo program shows how it works.
4.3 Provide a Makefile
Provide a Makefile
which has at least the following targets.
bl_server
: builds the server executable, be included in the default targets.bl_client
: builds the client executable, be included in the default targets.
Correction: An earlier version of the spec stated that testing
targets are required. This has been changed as the included
test_Makefile
provides these.
4.4 No malloc() / free() required
It may come as a surprise, but this set of programs is specifically
oriented so that no dynamic memory allocation is required. All of the
data structures are fixed size, strings have a maximum length, and
communication is done through FIFOs. Keep this in mind as you code
and avoid malloc()
unless you see no other way around it as it will
likely make things harder than they need to be.
5 Basic Protocol and Architecture
Clients communicate with the server along the following lines.
bl_server
creates a FIFO at startup time. The FIFO is created with a name specified according to naming convention at startup. Examples:$> bl_server server1 # creates the server1.fifo $> bl_server batcave # creates the batcave.fifo $> bl_server fortress-of-solitude # creates the fortress-of-solitude.fifo
This FIFO is only for join requests to the server. The name of this file must be known for the clients to connect to server.
- The server then waits for clients to write join requests to join FIFO.
bl_client
creates two FIFOs on startup:- A to-client FIFO for the server to write data intended for the client. The client reads this FIFO.
- A to-server FIFO to which the client writes and from which the server reads.
- The names of these files are inconsequential so long as they are fairly unique. Basing names for the FIFOs on the PID of the client is an easy way to do this. This avoids multiple clients in the same directory mistakenly using each other's FIFOs.
- A client writes a join request to the server which includes the names of its to-client and to-server FIFOs. All subsequent communication between client and server is done through the to-client and to-server FIFOs.
- A client reads typed input from users. When input is ready, it is sent as a mesgt to the server.
- As a server receives messages from clients, it will broadcast the message to all clients including the sender.
- The server sends notices of other kinds to the clients such as when a new client joins. Clients may send departure notices which are re-broadcast by the server.
The sections below give graphical depictions of some of the ideas of the protocols.
5.1 Server and Clients
The following diagram illustrates the single server server1
with 2
clients connected. Each client has its own FIFOs for communication
with the server and the server knows about these FIFOs.
Figure 2: Schematic of server data which has been joined by 2 clients.
5.2 Joining
This diagram illustrates how a new client can join the server. It
creates FIFOs for communication and then writes a join_t
request on
the server's FIFO. The server then adds the client information to its
array and broadcasts the join to all clients.
Figure 3: New client joining the server which has two existing clients.
5.3 Normal Messages
A client wanting to communicate with others sends a normal mesg_t
to
the server with its name and body filled in. The client's to-server
FIFO is used for this. The server then broadcasts the same message to
all clients including the sender.
Figure 4: Client sending a message which is broadcast to all other clients.
5.4 Departures and Removing Clients
A client can indicate it is leaving by sending an appropriate message. The server will remove its FIFOs and shift all elements in its clients array. The departure is broadcast to all clients.
Figure 5: Client sending a message which is broadcast to all other clients.
5.5 Protocol Data Types
The header file blather.h
describes several data types associated
with basic communication operations. One of these is the mesg_t
struct which is used to convey information between clients and server.
// mesg_t: struct for messages between server/client typedef struct { mesg_kind_t kind; // kind of message char name[MAXNAME]; // name of sending client or subject of event char body[MAXLINE]; // body text, possibly empty depending on kind } mesg_t;
Each message has a kind which helps determine what to do with the
messages. The kinds are defined in the mesg_kind_t
enumeration which
sets up specific integers associated with each kind.
// mesg_kind_t: Kinds of messages between server/client typedef enum { BL_MESG = 10, // normal messasge from client with name/body BL_JOINED = 20, // client joined the server, name only BL_DEPARTED = 30, // client leaving/left server normally, name only BL_SHUTDOWN = 40, // server to client : server is shutting down, no name/body BL_DISCONNECTED = 50, // ADVANCED: client disconnected abnormally, name only BL_PING = 60, // ADVANCED: ping to ask or show liveness } mesg_kind_t;
Messages are sent both from client to server and from server to client through the FIFOs that connect them. The specification below will describe what actions need be taken on receiving messages.
Finally, when the a client wishes to initially join the server, it
fills in and sends a join_t
.
// join_t: structure for requests to join the chat room typedef struct { char name[MAXPATH]; // name of the client joining the server char to_client_fname[MAXPATH]; // name of file server writes to to send to client char to_server_fname[MAXPATH]; // name of file client writes to to send to server } join_t;
6 The Server
The bl_server.c
program is responsible for allowing clients to
connect, become aware of one another, and communicate. It's main
functionality is to broadcast messages from one client to all others
and to broadcast status changes such as new clients that join or
depart.
6.1 Server and Client Data
To that end, the server process should maintain a server_t
data
structure which is defined in blather.h
as below
// server_t: data pertaining to server operations typedef struct { char server_name[MAXPATH]; // name of server which dictates file names for joining and logging int join_fd; // file descriptor of join file/FIFO int join_ready; // flag indicating if a join is available int n_clients; // number of clients communicating with server client_t client[MAXCLIENTS]; // array of clients populated up to n_clients int time_sec; // ADVANCED: time in seconds since server started int log_fd; // ADVANCED: file descriptor for log sem_t *log_sem; // ADVANCED: posix semaphore to control who_t section of log file } server_t;
The most prominent features of this structure are as follows.
- The
server_name
which determines the name of the FIFO filename for joining and a few other things. - A
join_fd
file descriptor which should be attached to a FIFO to read requests from clients to join the server. - The
client[][
array ofclient_t
structs that track clients connected to the server. The fieldn_clients
determines how full this array is.
Associated closely with the server_t
is the client_t
struct which
contains data on each client.
// client_t: data on a client connected to the server typedef struct { char name[MAXPATH]; // name of the client int to_client_fd; // file descriptor to write to to send to client int to_server_fd; // file descriptor to read from to receive from client char to_client_fname[MAXPATH]; // name of file (FIFO) to write into send to client char to_server_fname[MAXPATH]; // name of file (FIFO) to read from receive from client int data_ready; // flag indicating a mesg_t can be read from to_server_fd int last_contact_time; // ADVANCED: server time at which last contact was made with client } client_t;
While the client program bl_client
may make use of this struct as
well its main purpose is to help track data for the server. Prominent
features are as follows.
- The user name of the client which is provided when it joins.
- Names and file descriptors of the to-client and to-server FIFOs. The file descriptors are opened by the server based on the file names the provided by the client and used for communication between them.
- A
data_ready
flag which is set and cleared by the server as messages are available.
6.2 Logging and Debugging Messages in the Server
The bl_server
program is somewhat complex and to ease the task of
understanding what it is doing at any given moment, it is required
that it print certain information its current state as it works. This
output will be checked in the automated tests.
Make use of the provided log_printf()
function which is in
util.c
. This function works just like printf()
in that code like
log_printf("This is a message with a '%s' and a number '%d'\n","string",42);
will print a message to the screen with format specifiers. However, it
is special in that the can be silenced by setting the BL_NOLOG
environment variable as demonstrated below:
> ./bl_server serv1 # log messages printed by default LOG: BEGIN: server_start() LOG: END: server_start() LOG: BEGIN: server_check_sources() LOG: poll()'ing to check 1 input sources C-c LOG: poll() completed with return value -1 LOG: poll() interrupted by a signal LOG: END: server_check_sources() LOG: BEGIN: server_shutdown() LOG: END: server_shutdown() > export BL_NOLOG=1 # disable logging > ./bl_server serv1 C-c >
No special actions need to be taken to get this effect. Keep in mind that logging the messages described is REQUIRED.
Conversely, while debugging it is often useful to print more detailed
information. The should e done with the provided dbg_printf()
which
will only print if the BL_DEBUG
environment variable is set. This
variable will not be enabled during grading but is useful for
debugging as shown below.
> unset BL_NOLOG # ensure logging printed > export BL_DEBUG=1 # also print debug messages > ./bl_server serv1 LOG: BEGIN: server_start() LOG: END: server_start() DEBUG: At the top of main loop LOG: BEGIN: server_check_sources() LOG: poll()'ing to check 1 input sources C-c LOG: poll() completed with return value -1 LOG: poll() interrupted by a signal LOG: END: server_check_sources() DEBUG: Finished checking sources DEBUG: Checking 0 clients LOG: BEGIN: server_shutdown() DEBUG: server_broadcast(): 40 from - LOG: END: server_shutdown()
6.3 Server Operations: server_funcs.c
To facilitate operations of bl_server
main program, complete the
server_funcs.c
file which provides service routines that mainly manipulate
server_t
structures. Each of these has a purpose to serve in the
ultimate goal of the server.
Note that some functions have ADVANCED features listed that pertain to the Makeup credit for the project.
Also note that a number of functions have a "LOG Messages" section indicating which messages should be printed by the server and under what circumstances.
client_t *server_get_client(server_t *server, int idx); // Gets a pointer to the client_t struct at the given index. If the // index is beyond n_clients, the behavior of the function is // unspecified and may cause a program crash. void server_start(server_t *server, char *server_name, int perms); // Initializes and starts the server with the given name. A join fifo // called "server_name.fifo" should be created. Removes any existing // file of that name prior to creation. Opens the FIFO and stores its // file descriptor in join_fd. // // ADVANCED: create the log file "server_name.log" and write the // initial empty who_t contents to its beginning. Ensure that the // log_fd is position for appending to the end of the file. Create the // POSIX semaphore "/server_name.sem" and initialize it to 1 to // control access to the who_t portion of the log. // // LOG Messages: // log_printf("BEGIN: server_start()\n"); // at beginning of function // log_printf("END: server_start()\n"); // at end of function void server_shutdown(server_t *server); // Shut down the server. Close the join FIFO and unlink (remove) it so // that no further clients can join. Send a BL_SHUTDOWN message to all // clients and proceed to remove all clients in any order. // // ADVANCED: Close the log file. Close the log semaphore and unlink // it. // // LOG Messages: // log_printf("BEGIN: server_shutdown()\n"); // at beginning of function // log_printf("END: server_shutdown()\n"); // at end of function int server_add_client(server_t *server, join_t *join); // Adds a client to the server according to the parameter join which // should have fileds such as name filed in. The client data is // copied into the client[] array and file descriptors are opened for // its to-server and to-client FIFOs. Initializes the data_ready field // for the client to 0. Returns 0 on success and non-zero if the // server as no space for clients (n_clients == MAXCLIENTS). // // LOG Messages: // log_printf("BEGIN: server_add_client()\n"); // at beginning of function // log_printf("END: server_add_client()\n"); // at end of function int server_remove_client(server_t *server, int idx); // Remove the given client likely due to its having departed or // disconnected. Close fifos associated with the client and remove // them. Shift the remaining clients to lower indices of the client[] // preserving their order in the array; decreases n_clients. Returns 0 // on success, 1 on failure. void server_broadcast(server_t *server, mesg_t *mesg); // Send the given message to all clients connected to the server by // writing it to the file descriptors associated with them. // // ADVANCED: Log the broadcast message unless it is a PING which // should not be written to the log. void server_check_sources(server_t *server); // Checks all sources of data for the server to determine if any are // ready for reading. Sets the servers join_ready flag and the // data_ready flags of each of client if data is ready for them. // Makes use of the poll() system call to efficiently determine which // sources are ready. // // NOTE: the poll() system call will return -1 if it is interrupted by // the process receiving a signal. This is expected to initiate server // shutdown and is handled by returning immediagely from this function. // // LOG Messages: // log_printf("BEGIN: server_check_sources()\n"); // at beginning of function // log_printf("poll()'ing to check %d input sources\n",...); // prior to poll() call // log_printf("poll() completed with return value %d\n",...); // after poll() call // log_printf("poll() interrupted by a signal\n"); // if poll interrupted by a signal // log_printf("join_ready = %d\n",...); // whether join queue has data // log_printf("client %d '%s' data_ready = %d\n",...) // whether client has data ready // log_printf("END: server_check_sources()\n"); // at end of function int server_join_ready(server_t *server); // Return the join_ready flag from the server which indicates whether // a call to server_handle_join() is safe. void server_handle_join(server_t *server); // Call this function only if server_join_ready() returns true. Read a // join request and add the new client to the server. After finishing, // set the servers join_ready flag to 0. // // LOG Messages: // log_printf("BEGIN: server_handle_join()\n"); // at beginnning of function // log_printf("join request for new client '%s'\n",...); // reports name of new client // log_printf("END: server_handle_join()\n"); // at end of function int server_client_ready(server_t *server, int idx); // Return the data_ready field of the given client which indicates // whether the client has data ready to be read from it. void server_handle_client(server_t *server, int idx); // Process a message from the specified client. This function should // only be called if server_client_ready() returns true. Read a // message from to_server_fd and analyze the message kind. Departure // and Message types should be broadcast to all other clients. Ping // responses should only change the last_contact_time below. Behavior // for other message types is not specified. Clear the client's // data_ready flag so it has value 0. // // ADVANCED: Update the last_contact_time of the client to the current // server time_sec. // // LOG Messages: // log_printf("BEGIN: server_handle_client()\n"); // at beginning of function // log_printf("client %d '%s' DEPARTED\n", // indicates client departed // log_printf("client %d '%s' MESSAGE '%s'\n", // indicates client message // log_printf("END: server_handle_client()\n"); // at end of function void server_tick(server_t *server); // ADVANCED: Increment the time for the server void server_ping_clients(server_t *server); // ADVANCED: Ping all clients in the server by broadcasting a ping. void server_remove_disconnected(server_t *server, int disconnect_secs); // ADVANCED: Check all clients to see if they have contacted the // server recently. Any client with a last_contact_time field equal to // or greater than the parameter disconnect_secs should be // removed. Broadcast that the client was disconnected to remaining // clients. Process clients from lowest to highest and take care of // loop indexing as clients may be removed during the loop // necessitating index adjustments. void server_write_who(server_t *server); // ADVANCED: Write the current set of clients logged into the server // to the BEGINNING the log_fd. Ensure that the write is protected by // locking the semaphore associated with the log file. Since it may // take some time to complete this operation (acquire semaphore then // write) it should likely be done in its own thread to preven the // main server operations from stalling. For threaded I/O, consider // using the pwrite() function to write to a specific location in an // open file descriptor which will not alter the position of log_fd so // that appends continue to write to the end of the file. void server_log_message(server_t *server, mesg_t *mesg); // ADVANCED: Write the given message to the end of log file associated // with the server.
6.4 Messages Handled by the Server
The function server_handle_client()
is a workhorse that will read a
message from a client and respond take appropriate action. Here are
the messages that the server should accept and respond to.
kind | Name | Body? | Action |
---|---|---|---|
BL_MESG |
Yes | Yes | Broadcast message to all clients. |
BL_DEPARTED |
Yes | No | Broadcast message to all clients. |
6.5 Other Messages Sent by the Server
The server will broadcast several other kinds of messages to clients under the following circumstances.
kind | Name | Body? | Circumstance |
---|---|---|---|
BL_JOINED |
Yes | No | Broadcast when a new client joins the server |
BL_SHUTDOWN |
No | No | Broadcast when the server starts shutting down |
ADVANCED | |||
BL_PING |
No | No | Broadcast periodically, clients should respond with a ping to indicate liveness |
BL_DISCONNECTED |
Yes | No | Broadcast when a client has not pinged in a while to indicate disconnection. |
6.6 bl_server.c
main function
Define a main()
entry point in bl_server
that ties all of the
server operations together into a function unit.
Listen, Select, Respond
The server main()
function in its simplest form boils down to the
following pseudocode.
REPEAT: check all sources handle a join request if one is ready for each client{ if the client is ready handle data from it } }
The advanced features build somewhat on this but not by much.
Signal Handling
The server should run indefinitely without interaction from an
interactive user. To stop it, send signals. The server should handle
SIGTERM
and SIGINT
by shutting down gracefully: exit the main
computation, call server_shutdown()
and return 0. A typical means to
set this up is to control the main loop with a global variable which
is changed in signal handlers to terminate the loop. After ending the
loop, perform shutdown tasks.
Keep in mind that certain system calls will return immediately if the
process is signaled and may need to check for this in certain
functions in server_funcs.c
.
7 The Client
While bl_server
is a perpetual, non-interactive program, it is
fairly boring in the absence of clients.. bl_client
is a simple
program that allows a user to type into the terminal to send text to
the server and receive text back from the server which is printed to
the screen. The sections below describe how to build it.
7.1 Simplified Terminal I/O
Standard terminal input and output work well in many situations but
bl_client
must intermingle the user typing while data may be printed
to the screen. This calls for somewhat finer control. To simplify the
murky area that is terminal control, the file simpio.c
provides
functions which have the following net effect.
- A prompt can be set that is always displayed as the last final advancing line in the terminal.
- Text can be typed and deleted by the user.
- Any thread printing with the
iprintf()
function will advance the prompt forwards without disrupting the text a user may be typing.
The program simpio_demo.c
demonstrates this facility by spinning up
a user and background thread which both print to the screen. Text
printed by the background thread using iprintf()
does not disrupt
text being typed by the user at the prompt.
Use simpio_demo.c
as a template to begin you development on
bl_client.c
noting the following.
Initialize the simplified terminal I/O with the following sequence
simpio_set_prompt(simpio, prompt); // set the prompt simpio_reset(simpio); // initialize io simpio_noncanonical_terminal_mode(); // set the terminal into a compatible mode
- When threads want to print, use the
iprintf()
function. It works likeprintf()
but takes as its first argument asimpio_t
object which manages the prompt and typed text. - Analyze the input loop in
simpio_demo.c
to see how to read characters from the prompt, detect a completed line, and end of input.
7.2 bl_client.c
Features
Unlike the server, there are no required C functions for the client. Instead, there are a series of required features and suggestions on how to implement them.
Client Name and FIFO Creation
The client program is run as follows
$> bl_client server1 Lois # join the server1 as a user Lois $> bl_client dc-serv Bruce # join the dc-serv server as user Bruce
The first argument is the server to join and corresponds to a FIFO name that is owned by the server.
The client should create its own to-client and to-server FIFOs which become part of the join request to the server. The names of these FIFOs can be selected arbitrarily so long as they do not conflict with other user FIFO names. Examples are to create temporary file names or use the PID of the client process to choose the FIFO names.
Joining the Server
The client should construct a join_t
with its name and the names of
its to-client and to-server FIFOs. This should be written to the name
of the server's FIFO which will notify the server and other clients of
the new user's arrival.
Messages Handled by Client
The client should handle the following kinds of messages from the server.
kind | Name | Body? | Action | Print Format / Example |
---|---|---|---|---|
BL_MESG |
Yes | Yes | [Bruce] : check this out |
|
BL_JOINED |
Yes | No | -- Bruce JOINED -- |
|
BL_DEPARTED |
Yes | No | -- Clark DEPARTED -- |
|
BL_SHUTDOWN |
No | No | !!! server is shutting down !!! |
|
ADVANCED | ||||
BL_DISCONNECTED |
Yes | No | -- Clark DISCONNECTED -- |
|
BL_PING |
No | No | Reply | Send BL_PING message back to server |
User Input and Messages from the Server
To make the user experience reasonable, server messages should be received and printed to the screen as soon as they are available. Printing server messages should not disrupt the text input too much. This combination of requirements suggests the following.
- Use two threads, one for user interactions and the other for server interactions.
- The user thread performs an input loop until the user has completed a line. It then writes message data into the to-server FIFO to get it to the server and goes back to reading user input.
- The server thread reads data from the to-client FIFO and prints to the screen as data is read.
- Both user and server threads use the
iprintf()
function to ensure that data appears correctly on the screen and cooperates with input being typed.
A few additional notes are in order on the interaction of these two threads.
- Should the user thread receive and end of input, it should send a
message to the server with kind
BL_DEPARTED
to indicate the client is shutting down. This will notify other users of the departure. The user thread should cancel the server thread and return. - If the server thread receives a
BL_SHUTDOWN
message, the server is shutting down. This means the client should exit as well. The server thread should cancel the user thread and return.
Typed Input is Sent to the Server, not Printed
When users type input, it is echoed onto the screen by simpio
at the
prompt. However, on hitting enter, the data is sent immediately to the
server as a mesg_t
with kind MESG
, name
filled in with the
user's name and body
filled in with the text line typed.
Though the message body is known by the client which just sent it, the body is not printed to the screen. Instead, the client waits for the server to broadcast the message back and prints its own message just as if it were a message from another user. This ensures that no special actions need to be taken to avoid duplicate messages.
This "print only what the server sends" feature restriction is somewhat modified by some of the advanced features described later.
The Client has No Signal Handlers
Unlike the server, bl_client
should not establish an signal
handlers. It will be assumed during testing that the client behaves in
the following way.
Pressing
Ctrl-d
while running the client indicates the end of input and should cause the client to depart but it is NOT a signal. It should be detected with the client and trigger a normal shutdown of the client after usingiprintf()
to display the message:End of Input, Departing
- Pressing
Ctrl-c
while running the client will send an interrupt signal and should trigger the client to terminate immediately and ungracefully.
7.3 Client Main Approach
To summarize, bl_client
will roughly take the following steps.
read name of server and name of user from command line args create to-server and to-client FIFOs write a join_t request to the server FIFO start a user thread to read inpu start a server thread to listen to the server wait for threads to return restore standard terminal output user thread{ repeat: read input using simpio when a line is ready create a mesg_t with the line and write it to the to-server FIFO until end of input print "End of Input, Departing" write a DEPARTED mesg_t into to-server cancel the server thread server thread{ repeat: read a mesg_t from to-client FIFO print appropriate response to terminal with simpio until a SHUTDOWN mesg_t is read cancel the user thread
8 Grading Criteria grading
The following criteria will be examined during manual inspection of code by graders. Use this as a guide to avoid omitting important steps or committing bad style fouls.
Location | Wgt | Criteria |
---|---|---|
40 | AUTOMATED TESTS | |
20 Tests | ||
2pts per Test Passed | ||
MANUAL INSPECTION | ||
Makefile |
5 | A Makefile is provided which compiles bl_server, bl_client and any ADVANCED programs implemented |
Required test_Makefile is included via include test_Makefile |
||
server_funcs.c |
||
server_start() |
5 | creates and opens FIFO correctly for joins, removes existing FIFO |
server_shutdown() |
closes and removes FIFO to prevent additional joins | |
broadcasts a shutdown message | ||
server_add_client() |
5 | does bounds checking to prevent overflow on add |
adds client to end of array and increments n_clients |
||
fills in fixed fields of client data based on join parameter | ||
makes use of strncpy() to prevent buffer overruns | ||
opens to-client and to-server FIFOs for reading/writing | ||
server_remove_client() |
uses server_get_client() for readability |
|
closes to-client and from-client FIFOs | ||
correctly shifts array of clients to maintain contiguous client array and order of joining | ||
server_check_sources() |
5 | makes use of poll() system call to detect ready clients/joins |
checks the return value of poll() to determine if it completed or was signaled |
||
checks join FIFO and all clients in poll() call |
||
does not read data but sets read flags for join and clients |
||
server_handle_join() |
5 | reads a join_t from join FIFO correctly |
adds client with server_add_client() |
||
broadcasts join | ||
server_handle_client() |
5 | reads a mesg_t from the to-server FIFO |
processes message properly and broadcasts if needed | ||
Overall Style | 5 | makes use of the log_printf() function to print required log messages |
makes some use of the dbg_printf() function to print debug messages |
||
does basic error checking of system calls to check for errors, possibly using the check_fail() function |
||
handles interrupt/term signals but avoids calling non-reentrant functions in the handlers | ||
includes comments on complex portions of the code which require explanation | ||
bl_server.c |
||
5 | main loop makes use of the server_funcs.c routines extensively |
|
clear code to check input sources and handle ready input | ||
signal handling for graceful shutdown is clear | ||
bl_client.c |
||
main and overall | 10 | proper setup of terminal with simpio |
use of simpio for input and output | ||
clear creation and joining of threads | ||
resets terminal at end of run | ||
user thread | 5 | clear use of a user thread to handle typed input |
user thread sends data to server | ||
cancels server thread at end of input before returning | ||
server thread | 5 | clear use of a server thread to listen data on to-client FIFO |
clear handling of different kinds of messages coming from server | ||
cancels user thread on receiving shutdown message and returns | ||
60 | Manual Inspection Total |
9 ADVANCED Features
The following advanced features may be implemented to garner Makeup Credit on the total project score for the semester. Each additional feature is worth the Makeup credit indicated.
Update: To be compatible with the automated tests, only enable
advanced features if the environment variable BL_ADVANCED
is set.
Particularly the "Ping" feature may cause the server to output
different log messages when pinging than normal. A standard way to do
this that we have discussed is early in the main()
function for
bl_server
, have code that looks like following.
int main(...) { ... if(getenv("BL_ADVANCED")){ DO_ADVANCED=1; } ..
Here DO_ADVANCED
is a variable that, when 0, will do normal server
operations but when non-zero will enable the ADVANCED features
described in this section.
Later in bl_main.c
one do
if( DO_ADVANCED ){ ... // code an advanced feature like server pinging }
which will only utilized the advanced feature when enabled.
9.1 Server Ping and Disconnected Clients (10%)
Clients may terminate abruptly in the real world before they have a
chance to send a BL_DEPARTED
message to the server. This additional
feature adds period pinging to the server and clients. A ping is a
short message just to establish whether a communication partner is
still present. In this case, it is a message with kind BL_PING
.
Implementing this feature will cause the server broadcast a ping to all clients every second. Clients should respond immediately by sending a ping back.
To detect disconnected clients, the server should maintain its
time_sec
incrementing it each second. Whenever a message is
received from a client, including BL_PING
messages, the server
updates the client's last_contact_time
to the current server
time. Any client that abruptly departs will eventually have a large
gap between its last_contact_time
and the time_sec
.
Every second, the server runs the function
server_remove_disconnected(server, disconnect_secs)
. Any client
with a difference in its client->last_contact_time
and server->time_sec
that
is larger than the argument disconnect_secs
should be removed. A
BL_DISCONNECTED
message should be broadcast for this client to
notify other clients of the change.
A good way to implement periodic activity is to use the alarm()
function. This arranges for a periodic signal to be sent to the server
process. This should be done in main()
for bl_server.c
. The
signal handler for the SIGALRM
that is raised can set a variable to
indicate that 1 second has passed. In the main loop of the server, a
condition checks this variable and performs the ping broadcast and
checks for disconnected clients.
All changes for this feature are on the server side. Additional functions to write or modify are as follows.
// server_funcs.c // Modify int server_handle_client(server_t *server, int idx) // Write void server_tick(server_t *server) void server_ping_clients(server_t *server) void server_remove_disconnected(server_t *server, int disconnect_secs) // bl_server.c int main(int argc, char *argv[])
Update: Make sure only to start the pinging / alarm()
if the
BL_ADVANCED
environment variable is set. Alarms send signals to a
process which will cause the output of the server to change if the
Pinging feature is implemented. This may break compatibility with
automated tests if it is always done. By starting the alarms ONLY when
BL_ADVANCED
is set, one can produce "normal" output in those cases
get the pinging effect when the environment variable indicates it is
desired.
9.2 Binary Server Log File (10%)
There is cause to want a log of all messages that pass through a server. This feature will cause the server to write such a log in a binary file.
The file should be named after the server name similarly to the join FIFO. For example
$> bl_server server1 # creates server1.fifo # creates server1.log $> bl_server batcave # creates batcave.fifo # creates batcave.log
The format of this binary log file is as follows.
- The first part should contain a binary
who_t
structure which has the active users in it. To complete the logging feature, thewho_t
does not need to be kept to date. - After the
who_t
is a growing sequence ofmesg_t
structs in the log file. Any message that is broadcast by the server EXCEPT for pings is written to the end of this log, NOT in text format but as the direct binary representation of amesg_t
. - Each new message that is broadcast is appended to the end of the log in binary format.
When the server starts up, the log file should be created and the
log_fd
field filled with its file descriptor. A good place to write
log messages is during server_broadcast()
. In it, any message that
is not a ping should be written to the end of log.
When the server shuts down, close the file descriptor of the log but do not remove the file. On startup, re-open any existing log and continue appending to it.
This feature is implemented entirely in the server and requires modification of the following functions.
// server_funcs.c void server_start(server_t *server, char *server_name, int perms) void server_shutdown(server_t *server) int server_broadcast(server_t *server, mesg_t *mesg) void server_write_who(server_t *server) void server_log_message(server_t *server, mesg_t *mesg) // bl_server.c int main(int argc, char *argv[])
In addition, provide a file called bl_showlog.c
which will read the
binary log file and print its output. This should include the who_t
at the beginning and all log messages. Here is a demo run including
the presumed Makefile
target. Output is nearly identical to what is
shown in the client.
> make bl_showlog gcc -Wall -g -c bl_showlog.c gcc -Wall -g -c util.c gcc -Wall -g -o bl_showlog bl_showlog.o util.o -lpthread > file server1.log server1.log: data > ./bl_showlog server1.log 3 CLIENTS 0: Clark 1: Lois 2: Barbara MESSAGES -- Bruce JOINED -- -- Clark JOINED -- -- Lois JOINED -- [Bruce] : hey, want to know secret identity? [Clark] : dude, I have x-ray vision [Lois] : wait, you have x-ray vision [Clark] : ah, er, I mean... [Bruce] : ha ha. I'd never do something dumb like that. [Lois] : why not? [Clark] : oh boy, here it comes... [Bruce] : because... [Lois] : ?? [Bruce] : AHYM BAAATMAN!!! [Clark] : walked into that one [Lois] : yup -- Barbara JOINED -- [Barbara] : hey guys [Clark] : hey Barbara [Lois] : what up! [Barbara] : Did I miss anything important? [Clark] : the big "reveal" from Bruce... [Barbara] : what, that he's Batman? [Lois] : yup [Bruce] : wait, how did you know that? [Barbara] : you told me when we were dating [Lois] : me too [Bruce] : awkward. time for a SMOKE BOMB!!! -- Bruce DEPARTED -- [Barbara] : that guy >
9.3 Last Messages for Client (10%)
This feature requires the Binary Server Log File Feature.
Clients that recently join a server have no idea what has transpired prior to their joining. This can be ameliorated if clients can access the binary log to examine the last N messages. For example:
$> bl_client server1 Lois -- Lois JOINED -- [Clark] : eek! -- Clark DEPARTED -- [Lois] : what's his deal? [Bruce] : um.. Lois> %last 10 ==================== LAST 10 MESSAGES [Bruce] : what up man? [Clark] : not much, just down in the fortress of solitude [Bruce] : with lois [Clark] : nope. lana [Bruce] : nice -- Lois JOINED -- [Clark] : eek! -- Clark DEPARTED -- [Lois] : what's his deal? [Bruce] : um.. ==================== Lois>> wtf?!?
To accomplish this bl_client
should examine the typed line that is
entered to see if it starts with the string %last
in which case it
will be followed by an integer which is the number of messages
requested.
On detecting this command request, client should open the binary log
file for the server and seek to the end of it using lseek()
. Note
that the exact position in the binary log file can be calculated
immediately by using.
sizeof(mesg_t
- The number of messages requested
- The argument
SEEK_END
tolseek()
Thus no looping is required to find that position.
The client should then read off these messages and print them to the screen. It is a good idea to write function that will print logged messages to keep the main loop and thread loops simple in the client.
No server interaction is required for this feature so long as the
server is producing the binary log file. Modify only bl_client.c
.
Modify the following code for this feature.
// bl_client.c // helper functions and maybe main() int main(int argc, char *argv[])
9.4 who_t
for Client and Server (10%)
Users who log into the server have no way of knowing who is logged in. This feature ameliorates that.
- The server writes its active users as a
who_t
structure to the beginning of its binary log every second at the same time as it would check for disconnected users. - Clients can read the
who_t
at the beginning of the log file to determine which users are connected to the server. The client the provides a%who
command which prints active users. - To prevent the server from writing the
who_t
structure while a client is reading or vice versa, the server creates a semaphore which must be acquired to access this part of the log.
The who_t
structure is a simple, fixed size array of strings.
// who_t: data to write into server log for current clients (ADVANCED) typedef struct { int n_clients; // number of clients on server char names[MAXCLIENTS][MAXNAME]; // names of clients } who_t;
This fixed size means every time it is written it will overwrite the
same bytes. The server should write it every second in the same block
using the same approach as is used to check for disconnected clients
(alarm(1)
). Use of pwrite()
is suggested to allow the log_fd
to
stay in position to write to the end of the log file.
The client should implement the %who
command by checking the
simpio->buf
for the string. It should then acquire the semaphore,
read the who_t
and print the results the screen similar to the
following.
-- Barbara JOINED -- [Barbara] : Bruce? [Barbara] : Bruce, are you there? [Clark] : Ummm.... [Barbara] : hang on Barbara>> %who ==================== 3 CLIENTS 0: Batman 1: Clark 2: Barbara ==================== [Barbara] : (sigh) Batman? [Batman] : I AM THE NIGHT! Barbara>>
The server is responsible for creating the semaphore which controls
access to the who_t
portion of the log. Follow the same naming
convention as before.
$> bl_server server1 # creates server1.fifo FIFO # creates server1.log binary log file # creates /server1.sem POSIX semaphore $> bl_server batcave # creates batcave.fifo FIFO # creates batcave.log binary log file # creates /batcave.sem POSIX semaphore
Recall the following about POSIX semaphores which are relevant to their use here.
- They are created / accessed with the
sem_open()
function which takes a name which must start with a forward slash, thus the/server1.sem
convention. - POSIX semaphores use
sem_wait()
(decrement) andsem_post()
(increment) operations to lock/unlock the semaphore. - When the server wants to write a
who_t
into the log, it acquires the semaphore withsem_wait()
, writes, then releases withsem_post()
. - Clients follow a similar protocol when reading the
who_t
. - When shutting down the, server should unlink the semaphore with
sem_unlink()
.
Modify the following code for this feature.
// server_funcs.c void server_start(server_t *server, char *server_name, int perms) void server_shutdown(server_t *server) void server_write_who(server_t *server) // bl_server.c int main(int argc, char *argv[]) // bl_client.c // helper functions and maybe main() int main(int argc, char *argv[])
10 Automated Tests and Project Submission
10.1 Setting up and Running the Automated Tests
Unzip
p2-tests.zip
and copy all the files into your project directory. Doing anls
should give something likep2-code> ls Makefile test_Makefile test_blather.org testy server_funcs.c ...
Place the following line at the END of your
Makefile
include test_Makefile
This will enable you to run the automated tests
> make test gcc -Wall -g -c bl_client.c gcc -Wall -g -c server_funcs.c ... ./testy test_blather.org ============================================================ == testy test_blather.org == Running 20 / 20 tests 1) Server Start/End : ok 2) Single Client Join / Depart : ok 3) Single Client Join + Shutdown : ok ... 20) Stress Test : ok ============================================================ RESULTS: 20 / 20 tests passed
A single test can be run using the following
p2-code> make test testnum=5 # run a single test
To create a zip for submission, use
p2-code> make zip ... Zip created in p2-code.zip
10.2 Submit to Gradescope
After creating a Zip file via make zip
, refer to the P1 Submission
instructions on how to upload your project to Gradescope, retrieve
results from the autograder, and indicate what partner you worked with
(if any).
https://www-users.cs.umn.edu/~kauffman/4061/p1.html (bottom)
Don't forget to add your partner on Gradescope.