Don't see the menu?
EECS 489 Lab 5: Best Effort Netimg
EECS 489 Lab 5: Best Effort Netimg
This assignment is due on Friday,
18 Mar 2016, 6 pm.
Plain Page for Printing
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.
You're provided with a skeleton code
and a reference Linux binary executable of the
client, refudpimg, and the server, refudpdb
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
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::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
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
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
% 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
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.
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
Your "Lab5 files" comprises your socks.cpp,
netimg.cpp, and imgdb.cpp files.
To turn in your Lab5, upload a zipped
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
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.