EECS 489 Lab 1: Remote Image Display

This assignment is due on Friday, 15 Jan 2016, 6 pm.

tl;dr

As this is our first assignment involving programming, this description is a bit long. Please read the whole thing carefully as it sets up how labs and programming assignments work in this course: how you can access the support code, how to set command line options, how to test your code, what you're expected to turn in (and not turn in), and how to submit your code, these will all remain more or less the same for the rest of the term.

Introduction

This lab is a refresher for socket programming. I assume you have done some socket programming prior to this course, either as a project in EECS 482 or equivalent experience. I further assume that you know how to build a socket program on your development environment of choice, as covered in Lab 0. If your socket programming skill is rusty or shaky or if you're still unsure how to build a socket program using your favorite build tool, this lab gives you a chance to shore up both. In completing this lab, you may consult the sample code server.c and client.c from Lab 0.

In this lab, we will be building a simple client-server remote image viewer. When completed, the server will provide an image to the client, which the client then displays using OpenGL. This lab should provide you with direct feedback on its fitness: a correctly completed project will display a recognizable image on the client. Unfinished or incorrectly written projects probably will not. We will use this remote image display tool throughout the term to help you visualize the effects of various network protocols. The visualization tool could also help you detect some bugs in your code.

You're provided with a skeleton code consisting of:

  1. netimg.cpp and netimglut.cpp for the client, netimg
  2. imgdb.cpp and imgdb.h for the server, imgdb,
  3. a common header file, netimg.h,
  4. a common socket wrapper library, socks.cpp and socks.h, and
  5. a reference Linux binary executable of the server refimgdb.
You should be able to run your client against the provided server. The provided Makefile builds imgdb and netimg. You can download the support code from the Course Folder.

You can search for the string "YOUR CODE HERE" in the code to find places where your code must go. However, you are required to read all comments in the source files. There is information in the comments on the expected behavior of the functions, and assumptions about the structures and variables used, that is not necessarily spelled out in this specification document. You may also want to read this specification document all the way to the end before writing your first line of code. You may find the section on how to test your code useful, for example.

Task 1: Client Side

Your first task is to write the client code. The client takes a single required command line argument:

% netimg -s <server>:<port> -q <image>.tga [-v <version>]

where '%' indicates a Unix C-shell prompt, which you don't type in (if you're using bash, the prompt will likely be '$" instead of '%'). The -s option tells netimg which server to connect to. Unless your DNS resolver has been set up to search for the domain of the server (as shown in lecture), you must provide the fully qualified domain name (FQDN) or IP address of the server, along with the port number the server is listening on, separated by a colon. The angle brackets ("< >") indicate that you are to provide the actual values for the required arguments. You don't enter the angle brackets themselves when you run the program. See the testing section for a sample test case.

The -q option tells the netimg program which image file to query the server for. You replace "<image>" with the name of your TGA image file, again you don't enter the angle brackets themselves. The file must be a TGA image file. Two sample TGA image files are provided for your use. You can view them using Apple's Preview, GIMP, Photoshop, or other image viewing tool. If you have a JPEG or PNG or other image format you would like to use, you need to convert them to a TGA file first. For example, the graphics tool convert that is part of the ImageMagick package that runs on both Linux and Mac OS X can do this conversion. If your image is displayed upside down, convert can also flip it for you.

The -v option allows you to change the version number of the iqry packet sent. You should use it to test whether your server function imgdb::recvqry() is checking the version field of all incoming iqry packets correctly. The square brackets "[ ]" in the command line specification above indicate that the second argument is optional, you don't enter the square brackets when running the program.

You can search for the string "Task 1" in socks.cpp and netimg.cpp to find places where "Task 1" related code must be filled in. The function socks_clntinit(server, sname, reuse, self) connects to the server, either at the IPv4 address given in the sin_addr field of the server argument, or, if the sin_addr field is 0, at the host name given by the sname argument. One of IPv4 address or host name MUST be specified. In either case, the sin_port of the server argument must be valid and must contain the port number of the server. This is a pretty straightforward task not much different from the code in client.c of Lab 0. First create a new TCP socket. Set the socket option so that the socket will linger for NETIMG_LINGER amount of time upon closing, to give time for outstanding data to arrive at the destination. If the server's IPv4 address is not provided, obtain its address from the server's name, which must be provided in sname. Next initialize the socket with the server's IPv4 address and port number. Finally connect to the server and return the connected socket descriptor to caller. If the call to connect() fails, close the newly created socket and return SOCKS_UNINIT_SD. For any other error, terminate the process. The socks_clntinit() function has been commented such that it should be clear where you need to make which socket API call. The third argument of the function, reuse is used in Lab 2, and the fourth argument self is used in PA1, you can ignore them for now. Depending on the amount of error checking you do, this part of the lab should take about 16 to 20 lines of code. If you're using Windows, the API to close a socket is called closesocket() instead of just close(). The provided socks_close() wrapper function makes the system-appropriate call to close a socket.

Next write the netimg_recvimsg() function to receive the return packet from the server. The return packet must be of data type imsg_t. Store the packet in the global variable imsg. The structure imsg_t shown here is defined in netimg.h.

It stores the specifics of the image: width, height, etc., which we will need to display the image. You must check the version number of the incoming packet. If it is not NETIMG_VERS, you must return NETIMG_EVERS (both defined in netimg.h). (Your programming assignment will be tested for checking of packet version number, so get into the habit of checking it now.) This function should take about 9 lines of code.

Finally, you're to write 3 to 5 lines of code in netimg_recvimg() to receive the image file itself. You'll be using the global variables img_size and img_offset to complete this task.

That's it for Task 1. You will write a total of about 28 to 33 lines of code. After completing Task 1, you should test your code before continuing to Task 2. See the Testing section below for some guidelines on testing your code using the reference implementation of the server.

As you will notice, we use OpenGL/GLUT to display the image received from the server. Needless to say, you're not required to know OpenGL. All your code deal only with network programming. Nevertheless, you need to know how to build OpenGL/GLUT program. If you build your program using the provided Makefile, all you need to do is type make (on Linux and Windows systems, the first time you build an OpenGL/GLUT program, you may need to first install GLUT). If you want to build using an IDE, you need to add the files netimg.cpp, netimglut.cpp, socks.cpp, socks.h, and netimg.h to your project (and wingetopt.h and wingetopt.c if you're on Windows). Don't add the other files. Then you need to tell your IDE that you want to link with OpenGL libraries. If you don't know how to install GLUT or how to link with OpenGL libraries, please follow the instructions in the EECS 487 course note, Building OpenGL/GLUT Programs. If you're curious about the OpenGL code, feel free to ask (or take EECS 487 ;-).

Task 2: Server Side

Your second task is to write the server code. On the command line, the server is run simply by typing imgdb. The server does not have any required command-line option. Upon start up, the server initializes a TCP socket and obtain 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.

You can search for the string "Task 2" in socks.cpp and imgdb.cpp to find places where "Task 2" related code must be filled in. Fill in the function socks_servinit() by first creating a TCP socket. Then bind the socket with the self argument passed into socks_servinit(). This argument MUST have its sin_addr and sin_port variables set by the caller. Once the socket is created and bound, listen for connection, with listen queue length set to the macro NETIMG_QLEN. If the provided sin_port is 0, the OS will assign a random, ephemeral port to the socket. Obtain from the socket the ephemeral port number assigned to it and update the provided self variable with the assigned port number. Since the socket was bound to INADDR_ANY, you can't use getsockname() to find out the address of the socket (it will return 0's). Instead, to obtain the address of the current host is to call gethostname() and then call gethostbyname(). Store the FQDN returned by gethostname() in the provided variable "sname". Finally, return the socket descriptor to the caller. The function socks_servinit() has been commented such that it should be clear where you need to make which socket API call. As with socks_clntinit(), the reuse argument of the function is used in Lab 2, you can ignore it for now. It should take about 7 to 13 lines of code to complete this function. It is not that different from server.c of Lab 0.

The function socks_accept(sd) accepts the connection on the provided socket sd. Set the socket option so that the socket will linger for NETIMG_LINGER amount of time upon closing, to give time for the image to arrive at the client. If the argument remote is not NULL, store the connected client's address and port number in it (this feature will be used in Lab 2 and PA1). Return the socket descriptor for the accepted connection. This part should take about 7 to 9 lines of code.

The function imgdb::recvqry() receives a query packet, checks that the packet is of the correct size, of the expected version number and type (NETIMG_QUERY), and that queried image name is not longer than NETIMG_MAXFNAME. The definition of the query packet, iqry_t, shown here, and the error code to send corresponding to the aforementioned errors can be found in netimg.h.

The query packet carries the filename of the image the client is querying. You may want to consult the function netimg.cpp:netimg_sendqry() in writing this function. It should take about 10 lines of code.

Finally, the function imgdb::sendimsg(td, imsg) sends the image specification packet imsg to the client. Most of the content of this packet has been filled in for you by imgdb::marshall_imsg(), called by imgdb::handleqry(). But all the integer fields are still in host by order. So you need to convert all integer fields of imsg to network byte order. You MUST also set the im_vers field to NETIMG_VERS. Note that the im_type field of the packet has been set by the caller and should not be modified in imgdb::sendimsg(). Next, the function imgdb::sendimg(td, image, imgsize, numseg) sends the image contained in image to the client. In sending the image, instead of sending it in one go, which would be too fast to observe, you are required to send it slowly, one chunk every NETIMG_USLEEP microseconds, for a total of numseg(+1) chunks (computed in the function for you), each of which is of size segsize. The local variable ip points to the start of the image. Completing these two functions should take 6 to 8 lines of code.

That's all for Task 2. It should take about 30-40 lines of code in total. If you want to build using an IDE, you need to add the files imgdb.cpp, socks.cpp, netimg.h, socks.h, ltga.h, and ltga.cpp to your project (and wingetopt.h and wingetopt.c if you're on Windows). Don't add netimg.cpp nor netimglut.cpp. The two tasks together should take no more than 70 to 75 lines of code.

Testing Your Code

You can develop your code on either Linux, Mac OS X, or Windows platform. The provided code has been built and run on Linux, Mac OS X, and Windows machines. Support for Winsock is included in the source code. To build the code on Windows platform, you need to add the files wingetopt.h and wingetopt.c to your Visual Studio project.

The easiest way to test your code is to run both the server and client on your local host, e.g., your laptop or the desktop in CAEN labs. For example, run imgdb on the command line if you're running Mac OS X or Linux and are using the Makefile to build the program. If you're using an IDE, add the command line option to the IDE before building and running your code. (If you don't know how to do this, consult the course note, Command-line Options and Third-party Libraries.) If you are prompted by your security software whether to allow imgdb access to the network, just say yes. Then run netimg -s localhost:port# -q Panda.tga where localhost should be used instead of the host name, and "port#" is the port number reported by your imgdb. Again, if you're using an IDE, you need to enter the client's command line option to your IDE before running the server. The advantage of testing your code on your own machine is that you can run tcpdump or wireshark and see all the packets sent out from, or arriving to, your program, or none at all, as the case may be.

In addition to the skeletal code and Makefile, we've also provided an executable binary of imgdb, called refimgdb ("ref" for "reference"), that runs on the following four CAEN's linux hosts: eecs489p1.engin.umich.edu up to p4. If you set up your DNS resolver to search for the engin.umich.edu domain, you only need to enter eecs489p1 up to p4 to address these four hosts.

The reference server is available in the Course Folder /afs/umich.edu/class/eecs489/w16/lab1/. It is a Red Hat 7 executable, so don't try to download and run it on your Mac OS X, Windows, or other Linux flavors such as Ubuntu. To test against the provided reference server, run the server on one of the four eecs489 hosts that CAEN has set up for this course. These four hosts allow hosts on UMVPN, MWireless, or the CAEN Labs network to connect to any of their ports. If you have problem accessing these eecs489 hosts over UMVPN, MWireless, or from any of the CAEN Labs, please let the instructor know immediately. Don't run the server on any other CAEN hosts, such as the login servers (caen-vnc*) as their security settings have not been set to allow for connection to random ports. You won't be able to connect to the CAEN eecs489 hosts from your home or work computer unless you're running UMVPN. You also won't be able to connect from the MGUEST wireless network. Don't try to make the client on ITCS machines as they don't have OpenGL installed.

If you get the following messages when running your netimg client:

libGL error: No matching fbConfigs or visuals found
libGL error: failed to load driver: swrast

If the image displayed despite these messages, you can either ignore them or set your shell environment variable LIBGL_ALWAYS_INDIRECT to 1.

You should test your code as soon as you completed Task 1. Connect your netimg to refimgdb. For example, ssh to eecs489p1.engin.umich.edu, then run:

eecs489p1% cd /afs/umich.edu/class/eecs489/w16/lab1
eecs489p1% ./refimgdb

You should see something like the following printed on the window:

imgdb address is eecs48p1.engin.umich.edu:54539

Note the hostname and port number refimgdb prints out, i.e., eecs489p1.engin.umich.edu:54539 in this case. Then on your local machine open up another window and run:

localhost% ./netimg -s eecs489p1.engin.umich.edu:54539 -q Panda.tga

You need to replace eecs489p1.engin.umich.edu:54539 with whatever was actually printed out for you by refimgdb. A window should now pop up and a grayscale image of a panda displayed slowly, one chunk at a time from top to bottom. (To see the actual download speed without our artificial slow down, set NETIMG_NUMSEG to 1 in netimg.h.) Depending on your system, the image may show up upside down, it's ok (or you could set the macro UPSIDE_DOWN to 1 in netimglut.cpp). For Task 2, you MUST test your imgdb running on one of the CAEN eecs489 hosts with your netimg running on your localhost.

The above is a very simple test case to check that your two pieces of code are communicating with each other. You should further test them with other test cases of your own.

Submission Instructions

To incorporate publicly available code in your solution is considered cheating in this course. To pass off the implementation of an algorithm as that of another is also considered cheating. For example, if the assignment asks you to implement sort using heap sort and you turn in a working program that uses insertion sort in place of the heap sort, it will be considered cheating. If you can not implement a required algorithm, you must inform the teaching staff when turning in your assignment.

Do NOT use any libraries or compiler options that are not already used in the provided Makefile. Doing so would likely make your code not portable and if we can't compile your code, you will be heavily penalized. Test your compilation on CAEN eecs489 hosts! Your submission must compile and run without errors on CAEN eecs489 hosts using the provided Makefile, unmodified.

Your "Lab1 files" comprises your netimg.cpp, imgdb.cpp, socks.cpp files.

To turn in your Lab1, upload a zipped or gzipped tarball of your Lab1 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 Google Drive or Dropbox to keep the back up copy of your submission. Not only does it save you from losing your files should something untoward happen to your local computing environment, it also provides third-party timestamps on your files. 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 however. 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 external 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.