EECS 489 Lab 5: Best Effort Netimg

This assignment is due on Friday, 18 Mar 2016, 6 pm.

Introduction

This lab introduces you to UDP-based cient-server programming. It allows you to modify the receiver window and maximum segment size of the client and the packet drop probability of the server and observe what happens when there is no flow control and no error recovery at the transport layer. Your task is to re-implement the simple client-server remote image viewer, familiar from previous assignments, in UDP.

Support Code

You're provided with a skeleton code and a reference Linux binary executable of the client, refudpimg, and the server, refudpdb in /afs/umich.edu/class/eecs489/w16/lab5. You should be able to run your client against the provided server running on an eecs489 host and the provided client, again running on an eecs489 host, against your server. The provided Makefile builds udpimg and udpdb. These are really netimg and imgdb renamed, to prevent you from running the wrong binaries when testing. The programs still refer to themselves as netimg and imgdb in the diagnostic messages printed to screen. You can download the support code from the course folder. (Running make on the provided skeletal code unmodified will give you warnings about some variables being unused or used uninitialized. It's your job to initialize and use them properly as part of this assignment.) As usual, search for the string "YOUR CODE HERE" in the code to find places where your code must go. You may also want to consult the lecture notes on UDP socket.

Task 1: Server Side

Your first task is to write the server code. Search for the string "Lab5 Task 1" in socks.cpp and imgdb.cpp to find places where "Task 1" related code must be filled in. Fill in the couple lines of code to create a UDP socket in function socks.cpp:socks_servinit(). So that you don't have to re-type your code for Lab 6, the imgdb.cpp released for Lab 5 already includes instructions/comments for Lab 6 tasks, so be careful when you search for "Task 1" that you work only on "Lab5 Task 1" for this lab.

The server does not have any required command-line option. Upon start up, the server initializes a UDP socket and obtains an ephemeral port number from the kernel which it prints out to the console. Then it waits for a query packet from the client, loads the requested image from file from the same folder/directory it is launched from, sends the dimensions of the image to the client, and transfers the image to the client.

% udpdb [ -d <prob> ]

The optional -d command line argument allows you to change the probability that the server drops a segment instead of sending it to the client. The data type of the probability value is float. You can set it to any value from 0.011 to 0.11, or higher, all the way to 1.0. The default value is set to NETIMG_PDROP (in netimg.h). Play with different values and see how the drop probability affects the resulting image. To disable segment dropping, set the value to -1.0. The implemenation of this option is provided to you.

The definition of the query packet, iqry_t, is depicted in the following figure and can be found in netimg.h.

For this lab, the query packet must be of type NETIMG_SYNQRY, also defined in netimg.h. In addition to the filename of the image the client is searching for, the query message carries the maximum segment size (mss), the receiver's window size (rwnd), and the forward error correction window size, the latter two to be used in Lab 6 and PA3. The function imgdb::recvqry() receives a query packet, checks that the packet is of the correct version and type and returns one of the NETIMG error codes defined in netimg.h. Unlike in previous assignments, this imgdb::recvqry() calls the recvfrom() socket API to receive a UDP packet. Upon successful return from recvfrom(), the client's address and port number must be recorded in imgdb::client member variable. You are to write the call to recvfrom(). It shouldn't take more than 2-3 lines of code.

Upon returning from imgdb::recvqry(), imgdb::handleqry() calls imgdb::readimg() to load the requested image from file. If the image is found, handleqry() stores the mss, rwnd, and fwnd information to the eponymous imgdb class member variables. The remainder of the imgdb::handleqry() function behaves similarly to the equivalent function in Lab1. However, while we called the send() socket API to send packets using TCP in previous assignments, for this assignment, you are to write the imgdb::sendpkt() function to use the sendto() socket API to send packets. The destination of the sendto() call should be the client stored in the imgdb::client member variable, which you've initialized in your earlier call to recvfrom(). In this lab, imgdb::sendpkt() is a simple wrapper function of not more than 1 or 2 lines of code for the call to sendto(). It becomes more complicated in PA3.

Finally, the function imgdb::sendimg() sends the image contained in the provided image argument. The local variable ip points to the start of the image. Unlike in previous assignments, we will be encapsulating chunks of our image data in packets with header ihdr_t attached. The definition of ihdr_t is shown in the figure below and can also be found in netimg.h.

As usual, the first field is the version field, which must be of value NETIMG_VERS. The type field follows and must be of value NETIMG_DATA. Next comes the size field, which records the size of the attached data, in bytes, not including the header. The last field is the sequence number corresponding to the first byte of the attached data. We will use the byte offset from the start of the image as the sequence number of each byte. So the first byte of the image has sequence number 0. The k-th byte has sequence number k-1. A packet carrying a chunk of an image starting at the k-th byte of the image has sequence number k-1. The sequence number field in ihdr_t is an unsigned int, but we use the highest order bit to indicate special sequence numbers. The largest image we can transfer is thus 2GB.

In imgdb::sendimg(), prior to sending the image, you must ensure that the sender buffer can hold at least one packet of size imgdb::mss, as specified by the client in its iqry_t message. The size of the sender buffer is a socket option that you can query and set using the getsockopt() and setsockopt() socket APIs respectively. Checking and setting sender buffer size take about 5 lines of code in total.

To send the image, you must divide up the image into chunks of datasize, where datasize has been computed for you as mss - sizefo(ihdr_t) - NETIMG_UDPIP. Obviously, the last chunk of the image may be smaller than datasize. To marshall together a packet consisting of an ihdr_t header followed by a chunk of data, you are NOT ALLOWED to make any copy of the image data. Instead, you are REQUIRED to use the sendmsg() socket API. See the comments in imgdb::sendimg() for further instructions. This task should take about 20 lines of code. Since we cannot tell from observing the behavior of your code whether you have used sendmsg() and whether you have made copies of the image data, this task will be graded by inspecting your code. If you don't know how to use sendmsg() or how to implement this task without copying, please consult the teaching staff. Please remember that if you cannot implement a required task, you must inform the teaching staff in your writeup for PA3 and that if you turn in a different implementation, it will be considered cheating.

That's all for Task 1. It should take about 30 lines of code in total; of which, only the call to sendmsg() should be unfamiliar to you.

Task 2: Client Side

Your second task is to write the client code. The client takes two required command line arguments, exactly the same as in previous assignments:

% udpimg -s <server>:<port> -q <image>.tga [ -m <mss> -w <rwin> -d <prob> ]

where the -s option specifies the server's name and port number and the -q option specifies the image file name. The client also behaves the same as in previous assignments, except that instead of using TCP, it uses UDP to transfer file. The optional -m argument sets the client's maximum segment size, in bytes. The maximum segment size includes all headers, including the UDP/IP headers of 28 bytes, and must be at least NETIMG_MINMSS size. The default value is NETIMG_MSS. Both are defined in netimg.h. The optional -w argument sets the client's receiver window size, in terms of number of maximum-size segments. The default value is NETIMG_RCVWIN. The -d argument sets the probability of dropping an acknowledgement packet sent back to the server. It is used only when testing Go-Back-N in PA3. The implementations of all of these optional arguments are provided to you.

You can search for the string "Lab5 Task 2" in socks.cpp and netimg.cpp to find places where "Task 2" related code must be filled in. As with task 1, so that you don't have to re-type your code in Lab 6, the netimg.cpp released includes instructions/comments for Lab 6 tasks, so be careful when you search for "Task 2" that you work only on "Lab5 Task 2" for this lab.

The function socks.cpp:socks_clntinit(sname, port, rcvbuf) creates a UDP socket and connects to the server whose name is provided in sname, at the specified port. Since we are working with a UDP socket, the call to connect() in this function simply stores the server's address and port number such that subsequent calls to send and receive packets do not need to provide the server address. You are only asked to copy over the 2 line socket creation code from your socks_servinit() function here. Also in this function, write another 3-6 lines of code to set and confirm that the size of the socket receive buffer is at least rcvbuf bytes. Different systems set different maximum limit on the receive buffer. It is set to 1.5 MB on CAEN's eecs489 hosts. [If you're running Ubuntu on your own machine and want to change the system's maximum limit but don't know how to, feel free to ask. On Mac OS X and Windows, you shouldn't have problem with the system maximum set too small.]

The function netimg::sendqry() has been provided to you. In addition to the filename of the image the client is searching for, the query message also carries the receiver's window size (rwnd) and maximum segment size (mss). The function netimg::recvimsg() is not changed from previous assignments and is also provided to you.

If the image is found, the client initializes the graphics library and put the socket in non-blocking mode before going into an infinite loop, receiving image data from the network and displaying it. We'll be relying on the socket being non-blocking in subsequent assignments related to this lab.

Finally, in netimg::recvimg() the client receives the image data, one packet at a time. Recall that each data packet consists of an ihdr_t header, followed by a chunk of data. You are NOT allowed to make any copy of the image data. Instead, you are REQUIRED to use the recvmsg() socket API. See the comments in netimg::recvimg() for further instructions. This task should take about 20 lines of code similar and inverse to the ones you wrote for imgdb::sendimg(). Again, this task will be graded by inspecting your code. Please note in your writeup for PA3 if you are not able implement this task using recvmsg().

That's it for Task 2. You will write a total of about 30 lines of code, most of which are very similar to the ones you wrote for Task 1.

Testing Your Code

In addition to the skeletal code and Makefile, we've also provided a reference implementation of both server and client that run on the CAEN eecs489 hosts. You should test your code with various sizes of receiver window and/or maximum segment. When the receiver buffer is too small, most of the arriving UDP packets will be dropped and the displayed image will only be partially completed. You should also play with the server's drop probability and observe how higher drop probability causes more gaps/streaks in the displayed image. Display of incomplete images is the expected outcome of this lab. Do not be alarmed. Setting -w to 33 or larger, with the default mss and dropping disabled at server, would normally allow for the ShipatSea.tga image to be displayed in full over the loopback interface.

Your home network firewall may block UDP packets, preventing you from running your client and/or server to connect to the server/client running on the CAEN eecs489 hosts. You could use "ssh -Y" to connect to the CAEN eecs489 hosts and have X-window events forwarded to your local host. This works on Linux and Mac OS X (with XQuartz installed). On Windows, you can use MobaXterm. I would recommend against using VNC. Students have reported incompatibilities between OpenGL and VNC. The remote display of images could be unbearably slow, depending on your Internet access bandwidth (worse with VNC). If your home network firewall blocks UDP, my suggestion is to test your client and server over the loopback interface (i.e., on localhost) and test run them both on the CAEN eecs489 hosts, including interoperability testing against the reference implementations, only after you've debugged your code.

Submission Instructions

To incorporate publicly available code in your solution, or to pass off the implementation of an algorithm as that of another are both considered cheating in this course. If you can not implement a required algorithm, you must inform the teaching staff when turning in your assignment.

Your submission must compile and run without errors on CAEN eecs489 hosts using the provided Makefile, unmodified, without any additional libraries or compiler options.

Your "Lab5 files" comprises your socks.cpp, netimg.cpp, and imgdb.cpp files.

To turn in your Lab5, upload a zipped or gzipped tarball of your Lab5 files to the CTools Drop Box. Keep your own backup copy! The timestamp on your uploaded file is your time of submission. If this is past the deadline, your submission will be considered late. You are allowed multiple "submissions" without late-policy implications as long as you respect the deadline. We highly recommend that you use a private third party repository such as github or M+Box or Dropbox or Google Drive to keep the back up copy of your submission. Local timestamps can be easily altered and cannot be used to establish your files' last modification times (-10 points). Be careful to use only third-party repository that allows for private access. To put your code in publicly accessible third-party repository is an Honor Code violation.

Turn in ONLY the files you have modified. Do not turn in support code we provided that you haven't modified (-4 points). Do not turn in any binary files (object, executable, dll, library, or image files) with your assignment (-4 points). Your code must not require other additional libraries or header files other than the ones listed in the Makefile (-10 points).

Do remove all printf()'s or cout's and cerr's and any other logging statements you've added for debugging purposes. You should debug using a debugger, not with printf()'s. If we can't understand the output of your code, you will get zero point.