CSS 432
Program 1: Sockets
Instructor: Joe McCarthy
Due date: See the syllabus
1. Purpose
This assignment will give you an opportunity to
- exercise the use of various socket-related system calls
- 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
- a client process
- establishes a connection to a server
- sends data or requests for data
- closes the connection
- a server process
- sends back responses or acknowledgments to the client
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
- Receive a server's port number (server_port) and IP name
(server_name) as Linux shell command arguments.
- Retrieve a hostent structure corresponding to this IP name
by calling gethostbyname().
struct hostent* host = gethostbyname( server_name );
- 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
- Open a stream-oriented socket()
with the Internet address family.
int clientSd = socket( AF_INET, SOCK_STREAM, 0 );
- Connect this socket to the server by calling
connect() and
passing the following arguments:
- the socket descriptor (clientSd)
- the sockaddr_in structure defined above
- the size of the sockaddr_in structure
(obtained via the sizeof() function)
connect( clientSd, (sockaddr *) &sendSockAddr, sizeof( sendSockAddr ) );
- Use the write()
or writev() system call
to send data (see below).
- Use the read() system call
to receive a response from the
server.
- Close the socket by calling close().
close( clientSd );
Server
- 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 );
- Open a stream-oriented socket() with the Internet address family.
int serverSd = socket( AF_INET, SOCK_STREAM, 0 );
- 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 ) );
- Bind this socket to its local address by calling
bind() as
passing the following arguments:
- the socket descriptor (serverSd)
- the sockaddr_in structure defined above
- the size of the sockaddr_in structure
bind( serverSd, (sockaddr *) &acceptSockAddr, sizeof( acceptSockAddr ) );
- Instruct the operating system to listen for up to 5 client connection
requests at a time by calling
listen().
listen( serverSd, 5 );
- 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 );
- Use the read() system call to receive data from the
client. (Use newSd rather than serverSd in the above code example.)
- Use the write() system call to send back a response to the
client. (Use newSd rather than serverSd in the above code example.)
- 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:
- port: a server port number (last 5 digits of your student ID)
- nreps: the number of repetitions for sending a set of data buffers
- nbufs: the number of data buffers
- bufsize: the size of each data buffer (in bytes)
- serverIp: a server IP name (e.g., uw1-320-18.uwb.edu or localhost)
- 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:
- 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
- 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
- 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:
- Open a new socket and establish a connection to a server.
- Allocate databuf[nbufs][bufsize].
- Start a timer by calling gettimeofday().
- Initiate nreps data transfers, each based
on type such as 1: multiple writes, 2: writev, or
3: single write
- Lap the timer by calling gettimeofday(), where lap - start =
data-sending time.
- Receive from the server an integer acknowledgment that shows how
many times the server called read().
- Stop the timer by calling gettimeofday(), where stop - start
= round-trip time.
- Print out the statistics as shown below:
Test 1: data-sending time = xxx usec, round-trip time = yyy usec, # reads = zzz
- Close the socket.
Server
Your server program must receive the following two arguments:
- port: a server port number
- nreps: the number of repetitions of reading data from the client into databuf[BUFSIZE]
The main function should be:
- Accept a new connection.
- 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.]
- 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:
- Allocate databuf[BUFSIZE], where BUFSIZE = 1500.
- Start a timer by calling gettimeofday().
- 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.
- Stop the timer by calling gettimeofday(), where stop - start
= data-receiving time.
- Send the number of read() calls made, (i.e., the value of count in the
code above) as an acknowledgment.
- Print out the statistics as shown below:
data-receiving time = xxx usec
- Close this connection.
- Terminate the server process by calling exit( 0 ).
You must conduct performance evaluation over both 100Mbps and 1Gbps
network.
- 100Mbps between any two of uw1-320-01 ~ uw1-320-15
- 1Gbps between any two of uw1-320-16 ~ uw1-320-31
For each network, your performance evaluation should cover the
following nine test cases:
- nreps = 20000
- Three combinations of nbufs * bufsize
= 15 * 100, 30 * 50, and 60 * 25
- 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
- correct tcp socket creation
- three different data-sending scenarios by the client
- appropriate signal-driven data-receving code at the server
- use of gettimeofday() and correct performance evaluating code
- 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
- 100Mbps versus 1Gbps actual throughputs
- a comparison of multi-writes, writev and single-write performance over
100Mbps
- a comparison of multi-writes, writev and sinle-write performance over 1Gbps
- 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.