CSS 432
Program 1: Sockets

Instructor: Joe McCarthy
Due date: See the syllabus


1. Purpose

This assignment will give you an opportunity to
  1. exercise the use of various socket-related system calls
  2. evaluate the dominant overheads in point-to-point communication over 100Mbps and 1Gbps networks

2. Client-Server Model

In all your programming assignments through to the final project, your program will use the client-server model where The simple examples client and server applications using sockets provided on pages 41-44 of the textbook can be found in the files socket-client.c and socket-server.c on the uw1-320-lab network, in the ~css432/examples directory.

3. TCP Communication

HW1 focuses on basic connection-oriented socket (SOCK_STREAM) communication between a client and a server process.

To establish such a connection, those processes should perform the following sequence of operations:

Client

  1. Receive a server's port number (server_port) and IP name (server_name) as Linux shell command arguments.
  2. Retrieve a hostent structure corresponding to this IP name by calling gethostbyname().
        struct hostent* host = gethostbyname( server_name );
    
  3. Declare a sockaddr_in structure (defined in ip, the Linux IPv4 protocol implementation), zero-initialize it by calling bzero(), and set its data members as follows (see inet_network for descriptions of inet_ntoa(), which converts the Internet host address in network byte order to a string in IPv4 dotted-decimal notation, and and inet_addr(), which converts an Internet host address from IPv4 dotted-decimal notation into binary data in network byte order; htons() converts an unsigned short integer from host byte order to network byte order):
        int port = YOUR_ID;  // the last 5 digits of your student id
        sockaddr_in sendSockAddr;
        bzero( (char *) &sendSockAddr, sizeof( sendSockAddr ) );
        sendSockAddr.sin_family      = AF_INET; // Address Family Internet
        sendSockAddr.sin_addr.s_addr =
          inet_addr( inet_ntoa( *(struct in_addr *) *host->h_addr_list ) );
        sendSockAddr.sin_port        = htons( server_port );  // convert host byte-order
    
  4. Open a stream-oriented socket() with the Internet address family.
        int clientSd = socket( AF_INET, SOCK_STREAM, 0 );
    
  5. Connect this socket to the server by calling connect() and passing the following arguments:
        connect( clientSd, (sockaddr *) &sendSockAddr, sizeof( sendSockAddr ) );
    
    
  6. Use the write() or writev() system call to send data (see below).
  7. Use the read() system call to receive a response from the server.
  8. Close the socket by calling close().
         close( clientSd );
    

Server

  1. Declare a sockaddr_in structure, zero-initialize it by calling bzero(), and set its data members as follows (htonl() converts an unsigned long from host byte order to network byte order):
        int port = YOUR_ID;  // the last 5 digits of your student id
        sockaddr_in acceptSockAddr;
        bzero( (char *) &acceptSockAddr, sizeof( acceptSockAddr ) );
        acceptSockAddr.sin_family      = AF_INET; // Address Family Internet
        acceptSockAddr.sin_addr.s_addr = htonl( INADDR_ANY );
        acceptSockAddr.sin_port        = htons( port );
    
  2. Open a stream-oriented socket() with the Internet address family.
        int serverSd = socket( AF_INET, SOCK_STREAM, 0 );
    
  3. Set the SO_REUSEADDR option (defined in the socket() interface).
    (Note this option is useful to prompt OS to release the server port as soon as your server process is terminated.)
        const int on = 1;
        setsockopt( serverSd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof( int ) );
    
  4. Bind this socket to its local address by calling bind() as passing the following arguments:
        bind( serverSd, (sockaddr *) &acceptSockAddr, sizeof( acceptSockAddr ) );
    
  5. Instruct the operating system to listen for up to 5 client connection requests at a time by calling listen().
        listen( serverSd, 5 );
    
  6. Receive a request from a client by calling accept(), which will return a new socket descriptor specific to each connection request.
        sockaddr_in newSockAddr;
        socklen_t newSockAddrSize = sizeof( newSockAddr );
        int newSd = accept( serverSd, (sockaddr *) &newSockAddr, &newSockAddrSize );
    
  7. Use the read() system call to receive data from the client. (Use newSd rather than serverSd in the above code example.)
  8. Use the write() system call to send back a response to the client. (Use newSd rather than serverSd in the above code example.)
  9. Close the socket by calling close().
        close( newSd );
    
You will need to include the following header files to call the OS functions listed above:
    #include <sys/types.h>    // socket, bind
    #include <sys/socket.h>   // socket, bind, listen, inet_ntoa
    #include <netinet/in.h>   // htonl, htons, inet_ntoa
    #include <arpa/inet.h>    // inet_ntoa
    #include <netdb.h>        // gethostbyname
    #include <unistd.h>       // read, write, close
    #include <string.h>       // bzero
    #include <netinet/tcp.h>  // SO_REUSEADDR
    #include <sys/uio.h>      // writev

Socket.h and Socket.cpp

You are encouraged to work out this assignment given the instructions and hints above. However, if you find yourself moving out of your stretch zone into your panic zone, you may choose to take advantage of Socket.h and Socket.cpp files that the instructor has made available in ~css432/hw1/ on the uw1-320-lab network. In this case, you don't need to include the header files listed above. However, if you choose this path, you should make sure you understand how all the code in those files work.

4. Statement of Work

Write Client.cpp and Server.cpp to establish a TCP connection between a client and server, repeatedly send a set of data buffers (each with the same size) in three different scenarios (described below) from the client to the server, and send back an acknowledgment from the server to the client.

Client.cpp

Your client program must receive the following six arguments:
  1. port: a server port number (last 5 digits of your student ID)
  2. nreps: the number of repetitions for sending a set of data buffers
  3. nbufs: the number of data buffers
  4. bufsize: the size of each data buffer (in bytes)
  5. serverIp: a server IP name (e.g., uw1-320-18.uwb.edu or localhost)
  6. type: the type of transfer scenario: 1, 2, or 3 (see below)
From the above parameters, you need to allocate data buffers below:
     char databuf[nbufs][bufsize]; // where nbufs * bufsize = 1500
The three transfer scenarios are:
  1. multiple writes: invokes the write() system call for each data buffer, thus invoking as many write() operations as the number of data buffers, (i.e., nbufs).
         for ( int j = 0; j < nbufs; j++ )
           write( sd, databuf[j], bufsize );    // sd: socket descriptor
    
  2. writev: allocates an array of iovec data structures, each having its *iov_base field point to a different data buffer as well as storing the buffer size in its iov_len field; and thereafter calls writev() to send all data buffers at once.
         struct iovec vector[nbufs];
         for ( int j = 0; j < nbufs; j++ ) {
           vector[j].iov_base = databuf[j];
           vector[j].iov_len = bufsize;
         }
         writev( sd, vector, nbufs );           // sd: socket descriptor
    
  3. single write: allocates an nbufs-sized array of data buffers, and thereafter calls write() to send this array, (i.e., all data buffers) at once.
         write( sd, databuf, nbufs * bufsize ); // sd: socket descriptor
    
The client program should execute the following sequence of code:
  1. Open a new socket and establish a connection to a server.
  2. Allocate databuf[nbufs][bufsize].
  3. Start a timer by calling gettimeofday().
  4. Initiate nreps data transfers, each based on type such as 1: multiple writes, 2: writev, or 3: single write
  5. Lap the timer by calling gettimeofday(), where lap - start = data-sending time.
  6. Receive from the server an integer acknowledgment that shows how many times the server called read().
  7. Stop the timer by calling gettimeofday(), where stop - start = round-trip time.
  8. Print out the statistics as shown below:
         Test 1: data-sending time = xxx usec, round-trip time = yyy usec, # reads = zzz
    
  9. Close the socket.

Server

Your server program must receive the following two arguments:
  1. port: a server port number
  2. nreps: the number of repetitions of reading data from the client into databuf[BUFSIZE]
The main function should be:
  1. Accept a new connection.
  2. Change this socket into an asynchronous connection using signal() and fcntl().
         signal( SIGIO, your_function ); // you need to define your_function a priori
         fcntl( fd, F_SETOWN, getpid() );
         fcntl( fd, F_SETFL, FASYNC );
    
    [Update: signal() has been deprecated; for an example of using sigaction(), see sigactiondemo.c and socket-server3.c in ~css432/examples.]
  3. Let this server sleep forever.
Server.cpp must include your_function (whatever the name is) which is called to service the I/O interrupt (a.k.a. a signal in Unix). This function receives just an integer argument (the signal identifier, SIGIO), which you will not need to use, and returns void. This function should:
  1. Allocate databuf[BUFSIZE], where BUFSIZE = 1500.
  2. Start a timer by calling gettimeofday().
  3. Repeat (nreps times) reading data from the client into databuf[BUFSIZE]. Note that the read() system call may return without reading the entire data buffer if the network is slow, so you should repeatedly call read() until you've read all the data, e.g.,
           for ( int nRead = 0; 
                 ( nRead += read( sd, buf, BUFSIZE - nRead ) ) < BUFSIZE; 
                 ++count );
    
    Read the manual page for read() carefully.
  4. Stop the timer by calling gettimeofday(), where stop - start = data-receiving time.
  5. Send the number of read() calls made, (i.e., the value of count in the code above) as an acknowledgment.
  6. Print out the statistics as shown below:
         data-receiving time = xxx usec
    
  7. Close this connection.
  8. Terminate the server process by calling exit( 0 ).
You must conduct performance evaluation over both 100Mbps and 1Gbps network.
  1. 100Mbps between any two of uw1-320-01 ~ uw1-320-15
  2. 1Gbps between any two of uw1-320-16 ~ uw1-320-31
For each network, your performance evaluation should cover the following nine test cases:
  1. nreps = 20000
  2. Three combinations of nbufs * bufsize = 15 * 100, 30 * 50, and 60 * 25
  3. Three test scenarios such as type = 1, 2, and 3
You should repeat each performance evaluation several times and average elapsed times over the different runs, to minimize the effect of potential spikes in network congestion.

You can use /sbin/ifconfig and the inet addr field of eth0 to see the IP address of whatever machine you are logged into.

You can use netstat -a to see which ports are currently being used on the machine you are logged into.

5. What to Turn in

The homework is due at the beginning of class on the due date via the Blackboard assignment page.
Criteria Points (pct)
Documentation of your algorithm including explanations and illustrations in one or two pages 2 pts (10%)
Source code that demonstrates good modularization, coding style, and an appropriate amount of comments. The source code is graded in terms of
  1. correct tcp socket creation
  2. three different data-sending scenarios by the client
  3. appropriate signal-driven data-receving code at the server
  4. use of gettimeofday() and correct performance evaluating code
  5. comments
7 pts (35%)
Execution output showing your server and client code running. You can use script outputfilename (e.g., script ~/css432/hw1/server.output) to start recording everything appearing on the screen into outputfilename and then use CTRL-D to stop recording. Use man script to learn more about using this tool. Name your execution output files server.output and client.output respectively. 1 pts (5%)
Performance evaluation that summarizes performance data in two tables, one each for 100Mbps and 1Gbps. 2 pts (10%)
Discussion of key issues relating to this assignment, including
  1. 100Mbps versus 1Gbps actual throughputs
  2. a comparison of multi-writes, writev and single-write performance over 100Mbps
  3. a comparison of multi-writes, writev and sinle-write performance over 1Gbps
  4. an explanation of why we had to use asynchrnoous reads rather than blocking reads in the server
8 pts (40%)
Total 20 pts (100%)

6. FAQ

This FAQ page may address some question you may have. Click here

7. Acknowledgment

This assignment is closely modeled on an assignment earlier specified by Professor Fukuda.