CS357 Assignment 2 - UDP Socket Programming
Winter Session 2004
Purpose of the Assignment
This assignment is designed to test your abilities to:
- gain experience writing networked applications on Unix
- gain familiarity with the socket API
- gain experience with the IP protocol stack
- gain experience with writing programs supporting an Internet protocol
Assigned
Tuesday, January 25, 2005 (please check the main course website regularly for any updates or revisions)
Due
Monday, February 14, 2005 by midnight in the CS357b assignment locker
(locker 85, MC basement near the Grad Club). Electronic submission
must also have occurred by that time.
Late Penalty
5% of the total assignment value per day. If the assignment is
not submitted within 5 days of the due date, it will not be graded.
You are required to hand in a completed Assignment Submission Form (available
from on the course
website) with your assignment. If you are using late coupons
toward this assignment, you must indicate the number on your
submission form.
Individual Effort
Your assignment is expected to be your own work. Feel free to discuss
ideas with others in the class; however, your solutions should be your
own work. If it is determined that you are guilty of cheating on the
assignment, you will receive a grade of zero, and you may be penalized
further by the value of the assignment. That is: this assignment is
worth 10% of the overall mark; if you cheat, you could receive a grade
of -10%, and your maximum possible mark in the course would be 80%.
What to Hand in
You will submit a paper copy of all code, header files, Makefiles and test
results to the assignment locker. Submit your assignment in a 9" by
12" envelope that has the course name, your name and your student
number, as well as the assignment number, on the outside. With your
assignment, you must include a CS357b
Assignment Submission Form, on which you declare that the
material you are handing in is exclusively your own work.
In order to retrieve your assignment once it is marked, you need to
print an "Assignment Ticket", and include it with your submission. To
find out more about this process, check the Computer Science Department FAQ
under the question "How do I get my assignment back from the I/O counter?".
As well, you are required to submit your program electronically.
You must store all of the code, headers, Makefiles, and test results for your
assignment in a directory called Assignment2. From the
directory that contains Assignment2 as a subdirectory,
you will issue the command:
submit cs357 Assignment2
to submit your assignment electronically. If you've done it
right, you'll receive an e-mail confirming the fact shortly afterward.
You are reminded that the department reserves the right to use
similarity detection software in an effort to detect cases of
plagiarism and other forms of cheating.
The TAs may run your electronically-submitted programs against some test
cases that meet the parameters of this assignment. Be sure to test
your program well to ensure that it is working properly!
Assignment Task
You are required to implement in C or C++ a stripped down and
simplified instant messaging client and server for our Solaris lab
environments. Before starting to panic, keep in mind that this
software is in essence very simple. We are also going to be building
this application up slowly and deliberately over the remaining
assignments in this course, adding broadcasting support in Assignment
3, and multicasting support in Assignment 4.
For this assignment, we are focussing on the basic client and
server functionality required for a simple instant messaging
application. Clients will be able to register and deregister with
the server, and clients will be able to send instant messages to
one another through the server. The messages will be text only, with
no file attachments, images, and so on that you might find in a modern
messaging application.
Part I -- The Instant Messaging Client
Here are some specific requirements and other important notes about
your instant messaging client:
-
The instant messaging client takes three command-line parameters when
it is started: the name of the user, the host on which the server is
running, and the port on that host that the server is listening on.
In theory, the client can pull the name of the currently logged in user
out of the environment, but then you would be unable to have different
users sending messages between one another.
As an example, suppose you wanted to start your instant messaging
client (imc) under the name of bob, with
the server running on host obelix.gaul.csd.uwo.ca
listening on port 12345. In this case, you would issue
the command:
imc bob obelix.gaul.csd.uwo.ca 12345
-
The client communicates with the server using UDP packets, and not
TCP. Consequently, you will be using sendto() and
recvfrom() for sending and receiving messages
respectively, as shown in the examples in class. These functions will
help you track message addresses easily as well.
-
For this assignment, there are three main types of messages to be
exchanged between the client and the server: registration messages,
deregistration messages, and instant messages. For handling messages, you
should use the following types:
typedef enum im_message_type {
REGISTRATION_MESSAGE,
DEREGISTRATION_MESSAGE,
INSTANT_MESSAGE
} im_message_type;
typedef struct im_message {
im_message_type type;
char to[256];
char from[256];
char message[1024];
} im_message;
The first type defines an enumeration to specify the type of message
to be sent between the client and the server. The second type is
a structure defining the contents of each message. This includes
a type identifier so that we know what type of message we are dealing
with, indicators of which user the message is to, and which user the
message is from, and the actual message itself. We'll look at how
these types are used as we explore the functionality of the client
and the server. When the client and server communicate, they will
be sending these message structures back and forth between each other,
and not simply strings of text. Since both the client and the server
will be using these type definitions, it might not hurt to place them
in a header file included by both code bases.
-
When the client starts, it sends a registration message to the
server. In this case, it sets the type field in the
message structure sent to the server to be REGISTRATION_MESSAGE
and sets the from field to be the user name supplied
as a command line parameter. The to and message
fields in the structure can be left blank. The server will record
the client's name as well as the client's address and port
number retrieved when receiving the client's registration message
with recvfrom().
-
Instant messages are input at the client through its standard input.
To prompt users for input, the client displays a prompt like imc >, after which users can type their instant messages to send to other
users.
To input an instant message, the user inputs the recipient's user name,
followed by a colon, followed by the message to send, all on one line.
You can assume that there are no multi-line messages to keep things simple.
For example, to send the user alice a message, you would end
up with something that looks like:
imc > alice: How are you today?
This message would get packed into a message structure to send to the
server as follows. The type field in the
would be set to INSTANT_MESSAGE. The from field
would be set to be the user name supplied
as a command line parameter. The to field, in this case,
would be alice, and the message
field in the structure would be the text How are you today?.
This structure would then be sent to the server using sendto().
-
Periodically, the server will send instant messages to the client when
it receives them for delivery from other clients. Consequently, the
client must also check its own UDP socket for such messages, and print
them to standard output when they are received from the server. The
socket it checks is the same one that it uses for sending messages to
the server; the client does not have separate sockets for sending and
receiving messages. There are separate incoming and outgoing buffers
for messages, so you do not need to worry about race conditions and
other issues here.
Suppose that the user alice sends a response to the
above message like: I'm fine!. This packet would arrive
from the server in a message structure with the type field in the
structure set to INSTANT_MESSAGE, the from field
would be set to alice, the to field set to the
user's name, and the message field set to the text
I'm fine!. This message would then be printed out to
standard output as:
alice: I'm fine!
To keep the display nice, an extra blank line would be printed before
the message to skip past the current client prompt. It may possible for
a message to be received and printed while a message is being typed in.
In such a case, the display could look a little messy. You do not need
to worry about this situation, however, as it can be quite tricky to solve.
-
A good question that you may have at the moment is how can your program
wait for user input on standard input at the same time that it is also
waiting for input messages on its socket from the server? How can it
wait for two different things at once? That can be done nicely using
a slick function called select().
The select() function is designed
to wait for input from multiple sources at once, and let you process
whichever input actually arrives. So, you can in effect wait for input
messages typed by the user and messages from the server at the same time.
The following sample code demonstrates the use of this handy function:
#include < sys/types.h >
#include < sys/time.h >
...
fd_set readfds;
int readSocket;
int rval;
...
/* Initialize the file descriptor set to include our socket */
/* and the file descriptor for standard input (0). Then, we */
/* can listen to both at once. */
FD_CLR(readSocket, &readfds);
FD_CLR(0, &readfds);
FD_ZERO(&readfds);
FD_SET(readSocket, &readfds);
FD_SET(0, &readfds);
/* We now use select to wait for input at either location. */
/* We don't need to timeout, so we leave that part NULL. */
rval = select(readSocket+1, &readfds, NULL, NULL, NULL);
if ((rval == 1) && (FD_ISSET(readSocket, &readfds))) {
/* We have socket input waiting, and can now do a recvfrom(). */
}
if ((rval == 1) && (FD_ISSET(0, &readfds))) {
/* We have input waiting on standard input, so we can go get it. */
}
if (rval < 0) {
/* An error occured. */
}
Note that if you do this in a loop,
which is likely to handle multiple instant messages, that you must
initialize readfds using FD_CLR() and so on each time
through the loop, or else it will not work properly!
-
The instant messaging service may also send back an error message to
the client, if the client attempts to send an instant message to a
user not currently registered with the service. Such a message would
arrive from the server in a message structure with the
type field in the structure set to
INSTANT_MESSAGE, the from field would be
set to ims (indicating it is from the service itself),
the to field set to the user's name, and the
message field set to the text User [name] not registered., where [name] would be filled in by the server
with the name of the unregistered user that you had just tried to
message. Since this arrives just like any other instant message, it
can be treated and handled in exactly the same way and printed to
standard output as described above.
-
The client will loop forever, waiting for input from the user or
instant messages, processing whichever input arrives, and going back
and waiting again. It does not simply send or receive one message
and exit, like the examples in class did.
-
To exit the client, the user simply types exit at the
client prompt. Before exiting, however, the client first sends a
deregistration message to the server. In this case, it sets the type field in the
message structure sent to the server to be DEREGISTRATION_MESSAGE
and sets the from field to be the user name supplied
as a command line parameter. The to and message
fields in the structure can be left blank. After sending this message,
the client can exit normally.
Part II -- The Instant Messaging Server
Here are some specific requirements and other important notes about
your instant messaging server:
-
Unlike the client, your instant messaging server takes no command line parameters. It
simply starts up and is ready to go without any further input from the
user. Call the server ims when you build it. That way,
a simple call to ims will run everyone's assignment nicely.
-
When the server is started, it will create a UDP socket for communicating
with clients, and have the operating system pick a free port number
for it. It will then report this port number so that clients will know
where to find the server, much in the same way examples in class worked.
-
In essence, the instant messaging server's role is simply to route
messages between clients. Consequently, the server maintains a table
of registered users that it has received registration messages from.
The data structure to use for this table is up to you, although it
is likely simplest to use a fixed-size array that can accommodate at
least 100 registered users. For each registered user, this table stores
the the user's name (from the from
field in the user's registration message), as well as the address and
port number of the user's client
(taken from the return parameters when the server's call to recvfrom() retrieved the message). The address
and port number can remain packed in the address structure it came
from, or separated
into different fields.
When a registration message arrives (with a type field
of REGISTRATION_MESSAGE), the server adds the information
to the table, if the user name does not exist. If the user name does
already exist, it simply overwrites the old address information with
the new address information it just obtained from recvfrom(). When a deregistration message arrives (with a type field of DEREGISTRATION_MESSAGE) it removes
the entry for the named user instead.
-
When the server receives an instant message from a client (with a type field
of INSTANT_MESSAGE), it looks up the user name indicated in the
to field of the received message structure in its table of
users, and resends the message structure to the corresponding address
using sendto().
If the server cannot find the intended recipient in its list of
registered users, the server generates a return message for the client
that originated that instant message. This message would
be sent in a message structure with the
type field in the structure set to
INSTANT_MESSAGE, the from field would be
set to ims (indicating it is from the service itself),
the to field set to the user's name, and the
message field set to the text User [name] not registered., where [name] would be filled in by the server
with the name of the unregistered user that was not in its list.
-
The instant messaging server is intended to run indefinitely, and so
has no explicit means to shut it down. It will simply loop forever, continuously waiting for and processing client requests. However, when you are not testing
your assignment you should kill your server with a Ctrl-C
to save scarce system resources.
Part III -- Some Final Notes and Hints
Here are some hints to help you in your assignment:
-
The man pages are your friends. If you have questions about how to
use some of the functions discussed in this assignment, do not hesitate
to consult the man pages or sample programs in the course notes!
- At many points in the client and server, you will need to do things
to parse text and break it into its various pieces. (For example, when
reading in input from the client and separating the user name from
the message to send to the user.) There are several
simple C functions to help you do this. Be sure to look into the functions
strchr(), strstr(), and strtok().
-
At many points in the client and server, you will also need to compose
strings out of other data. You are likely familiar with functions like
strcat() and strcpy() for string manipulation,
but you should also look into the sprintf() function. This
allows you to use printf() style formatting to produce
strings. Definitely a very useful function!
-
If you ever have to copy a message structure, address structure, or
anything else that is not entirely text, do not use the
strcpy() function to do so.
The strcpy() function will only
copy up to the first NULL character it sees, and then stops! This function
is fine to fill in the text fields of a message structure, but if you
have to copy the structure as a whole or anything else that could contain
binary data, you are better off using functions like bcopy()
or memcpy().
For that matter, it is likely easiest to not even copy message structures
around. Simply fill them in and directly send them through sendto(), and when receiving, call recvfrom() and directly store the results in one of
the structures. Less copying will give fewer problems.
-
It might not hurt to use a function like bzero() or memset() to NULL out message
structures before they are used. (In other words, set all bytes in the
structure to be the NULL character.) You can do this manually by clearing
out each structure field yourself, but those functions are more handy.
If you are doing things properly, you likely don't need to do this, but
sometimes it's a case of better safe than sorry.
-
Lastly, do not worry about the reliability of UDP in your assignment.
It is possible for packets to be dropped, for example, but the chances
of problems occuring in a local area network are fairly small. If
it does happen on the rare occasion, that is fine. If your program
appears to be losing or corrupting packets on a regular basis, then
there is likely a fault in your program. If in doubt, see me or the
TA to discuss the matter further.
You are to write all of the C or C++ code for this assignment yourself.
In addition to your own code, you may only use the C source code
provided in the course notes, or the code on this web page,
in your programs. (If you choose to do so, you must cite its use
in comments where appropriate!) You are not allowed to use C
functions such as system() or anything similar to
execute other programs for you! All server code files and header
files must begin with ims, and all client files
must begin with imc. Using a Makefile is
required. It will make your life, as well as your marker's, easier in
the long run. All of these files must be submitted with your
assignment.
As an important note, marks will be allocated for code style.
This includes appropriate use of comments and indentation for
readability, plus good naming conventions for variables, constants,
and functions. Your code should also be well structured (i.e. not all
in the main function).
Please remember to test your program as well, since marks will be
given for correctness! You should be able to send a variety of different
test messages and respond to error conditions appropriately.
You must use the script command to capture a session of
two clients exchanging messages between one another through the server,
and include this with your submission.