Individual Programming Project #1
(due date: 31/10/05)

short connection_id;
short type;
short status;
char buffer[256];
REQUEST, REQUEST_ACK, DONE, DONE_ACK
|
Message Type |
Field |
Contents |
| REQUEST | Connection_id Status Buffer |
-1 0 pathname of remote file |
| REQUEST_ACK | Connection_id Status Buffer |
connection identifier 0 all zeros |
| DONE | Connection_id Status Buffer |
connection identifier 0 in case of success/error status in case of failure timestamp of remote file |
| DONE_ACK | Connection_id Status Buffer |
connection identifier 0 all zeros |
When manipulating packets locally, you will probably find it convenient to use a struct to contain the various fields that make up the packet. In fact, its definition looks very much like a c-style struct. However, when sending packets over the network, there are two reasons why this is not a good idea. The definition of a protocol (including RTP) is not architecture, language or compiler dependent. In order for all implementations of a given protocol to correctly inter-operate, the position and length of fields within a packet must be exact. For this reason, before sending a packet onto the network, no assumptions can be made concerning the layout of the packet¨s fields in memory. Your implementation must contain codes which explicitly lays each field into memory according to the packet definition. In other words, do not simply pass a pointer to a packet struct to the send() function.
In addition to language and compiler independence, there is an important architectural consideration. Since CPUs can be either big-endian or little-endian, care must be taken to insure that all multi-byte integers sent onto the network are laid-out in a standard network byte-order, which may or may not be identical to host byte-order. Several functions are provided for this purpose: ntohs(), ntohl(), htons(), htonl(). You are required to use these functions for any multi-byte integers sent over the network.
As mentioned above, the server protocol state machine for each connection must be implemented as a separate thread. In your implementation, the initial, main thread should have the task of both creating these new threads for new connections, and of dispatching incoming datagrams to the correct state machines. The main thread should be the only one that executes the recvfrom() system call. The bulk of packet processing should be left to the new threads spawned by this main thread.
Since UDP provides no notion of a connection, incoming datagrams must be de-multiplexed to the appropriate thread by your implementation. It is for this purpose that we have designated the connection_id packet field. You should assign each new connection a unique connection_id, which can be used by the main thread to correctly de-multiplex subsequent incoming datagrams.
In addition to deciding to which state machine an incoming packet belongs, you must determine a mechanism by which the main thread can notify a waiting state machine of a packet arrival. Inefficient spin-waiting loops are not acceptable. Either semaphores or condition variables can be used to put to sleep and consequently notify a waiting thread.
The implementation of a protocol state machine is usually dominated by code for handling error conditions such as malformed or garbled packets, missing or out-of-order packets, etc. In the interest of simplicity, your implementation need not worry about these error conditions. You must, however, check return values of standard UNIX calls for error conditions (e.g., sendto(), recvfrom(), socket(), etc.).
The following items are required for full credit:
sol22(22)% client apollo.cs.ucl.ac.uk public_html/index.html
Trying 128.xxx.xxx.xxx...
Server apollo.cs.ucl.ac.uk is querying the timestamp of public_html/index.html...
Timestamp of public_html/index.html is
Thu Sep 2 13:39:45 2005.
Last Update: 18/09/2005