For simplicity, you will only implement data flowing in one direction -- from the client to the server. Traffic back from the server will only entail acknowledgment messages for reliability and flow control.
You will implement both the client and server component of a transport layer. The client will read a stream of data in from STDIN, break it into fixed-sized packets suitable for UDP transport, prepend a control header to the data, and write this packet to the server. The server will read these packets, and write the corresponding data stream to STDOUT.
% tar xvfz ~class/src/reliable.tar.gz % cd reliable % setenv AUTOCONF_VERSION 2.13 % sh ./setup ... % setenv DEBUG -g % ./configure ... % gmake ...
You should start by testing you client and server just by writing data into STDIN on the client. Note that you need to start the server before starting the client, as the client should immediately attempt to connect to the server at the port specified.
% ./srvd [port] % ./clntd [port]
The server (svrd) and client (clntd) expect you to give them the same port (> 1024). If your program is working correctly, anything you type into the client should output by the server to STDOUT.
In clntd.c, you should modify four functions:
In srvd.c, you should modify the handle_pkt function that receives packets sent for the client. When data is correctly received in-order, the server should write it out to STDOUT.
Note that we've provided a bunch of functionality that you might find useful; the functions are declared in reliable_utils.h. For example, you do not need to open or read from sockets yourself, the client and server already just create such sockets and then call add_fd (int fd), which registers the socket with our run engine (see timer.c). This engine polls alls registered sockets and then calls the corresponding handle_pkt on the server or client when appropriate. To write to a socket, you might choose just to use the following function:
int sendpkt (int fd, struct sockaddr_in *to, char *buf, u_int len);
In reliable_utils.h, you should define some header type in struct reliable_hdr that may include such things as a sequence and acknowledgment number, a packet flag type, and a checksum. (See, for instance, TCP's headers on page 383 of the textbook.) Note that as the underlying UDP transport already includes source and destination port, this should not be included in your header. Each packet you send should start with this control header; all information in the control header should be in network-byte-order.
Note that we provide the function in_cksum (u_char *pkt, int len) for computing a packet checksum. This checksum should also cover your header, although note that the checksum field in the header should be set to 0 before actually computing the checksum on the packet header and data. The checksum allows each party to detect any corruption during transmission; corrupted packets should merely be dropped (which of course causes the reliability component of your implementation to resend the dropped packet.)
You are probably going to want to define some type of linked-list structure on both the client and server side corresponding to in-order packets. See section 5.2.4 in the textbook for some hints as to a possible implementation design.
While your client only needs to maintain a single linked-list (as we are assuming a single connection per client), your server should maintain a linked-list per connection. For example, your server might demultiplex a packet to the correct connection based on the from address of the packet.
Our official test program can be run via;
% ./rlbtester ./clntd ./srvd
For those that are curious, the tester works by interposing itself between the client and server: the client actually sends packets to the tester, not the server. The tester then just passes the packets on to the server, optionally dropping, corrupting, (and later, reordering) packets. The server's response also goes through the tester. In some sense, you can think of the tester as a unreliable network. As the testing program forks the client and server when it starts, it also controls their I/O, so writes data into the client, and reads data out from the server, comparing the two results.
% gmake distcheck rm -rf reliable-0.0 ... ============================================== reliable-0.0.tar.gz is ready for distribution ============================================== %To turn in your distribution, copy it to the directory
~class/handin/lab5/usernamewhere username is your username:
% cp reliable-0.0.tar.gz ~class/handin/lab5/`logname`/ %To create a script file, use the
scriptcommand. When you run script, everything you type gets saved in a file called typescript. Press CTRL-D to finish the script. The typescript should be copied to the same directory as the software distribution. For example:
% script Script started, output file is typescript % ./test-reliable ./rlbtester ./clntd ./srvd rlbtester: success: read [Hello.] rlbtester: success: read [This is only a test.] rlbtester: success: read [Hello. This is only a test. Only a test, I say. ] ... Finished test for message transmission... rlbtester: success: read [Hello.] ... Finished test for handling dropped packets... rlbtester: success: read [Hello.] ... Finished test for handling corrupted packets... Finishing experiment: 12 out of 12 points [Internal use only: mfreed @ Thu Apr 15 18:20:09 EDT 2004 MD5 (./clntd) = d1535e178d5ada4113f6cc096b03ceba MD5 (./srvd) = d5611354fc06019b903d8fe85fe4cf98 % ^D Script done, output file is typescript % cp typescript ~class/handin/lab5/`logname`/ %If you have any questions, please contact us at netstaff (at) class cs nyu edu.