Problem Statement

Telcom 2300
Homework #9 - Simple Socket Program

1.  Goal:

To use network data communications and understand basic client-server concepts.
 

2.  Background:

There are many methods two programs can use to communicate with each other.
One common method is to connect the two programs with a TCP connection, which
allows a stream of data to be sent from each program to the other.

The de facto standard interface between a user's program and the operating
system's networking software is the Berkeley socket interface.

There is a library which is available to us which encapsulates the Berkeley
socket interface into a simpler (although more limiting) interface.  It is
called 'libutil'.

The functions in 'libutil' are described more fully in 'Internetworking with
TCP/IP Volume III' by Comer & Stevens.
 

2.1  The functions:

These are the functions that 'libutil' provides that we are interested in for
this homework:

   int passiveTCP(char service[], int qlen);
   int connectTCP(char host[], char service[]);

In addition, one of the functions from the Berkeley socket interface will be
needed:

   int accept(int msock, struct sockaddr *fsin, int alen);

And we will still need to use the Unix I/O functions 'read' and 'write':

   int read(int desc, char buffer[], int len);
   int write(int desc, char buffer[], int len);
 

2.2 The Functions Used By the Server side:

The 'passiveTCP' function creates a passive TCP socket.  The prototype for
this function is:

   int passiveTCP(char service[], int qlen);

It returns a descriptor to the passive socket (also called the master socket).
If there are problems, then the return value is negative and the return value
is not a valid descriptor.

The first parameter describes the service or port number that is associated
with the master socket.  We will use an ASCII character string holding the
port number assigned in the class meeting,  for example "5100".  Note that
this is not an integer 5100.

The other parameter is the number of clients that can be queued for connection
before operating system refuses any more connections.  We can use 5 for this
parameter.
 

The 'accept' function waits for a connection from a client.   When it is called,
the program blocks (waits indefinitely) for a connection from a client.  The
prototype for this function is:

   int accept(int msock, struct sockaddr *fsin, int *alen);

When a client connects, 'accept' returns a descriptor to an active socket
which is connected to the client.  This descriptor can be used in the 'read'
and 'write' calls just like a file descriptor.  This descriptor *cannot* be used
with the 'lseek' call.

The first parameter is the passive socket which was returned by 'passiveTCP'.

The second parameter is the address of a structure which will be filled in with
the IP address of the client machine (we will not be needing the information
returned in this structure).

The third parameter is related to the second parameter.  When called, the
'alen' variable should hold the size of a 'struct sockaddr' structure.  When
'accept' returns, it holds the number of bytes which were filled into the
'fsin' parameter by 'accept'.  As with the second parameter, we will not be
needing the value returned by this parameter.

More information is available about 'accept' from the usual sources: man
pages (man -s 3socket accept), the Answer Book and others.
 

2.3 The Functions used by the Client side:

The 'connectTCP' function initiates a connection from the client to the
server.  The prototype for this function is:

   int connectTCP(char host[], char service[]);

When 'connectTCP' is called, one of two things will occur:

If the server is up (it has called 'passiveTCP' and is waiting in 'accept')
then the connection is made, and 'connectTCP' returns a descriptor to an
active socket which is connected to the server.  Note that at the same time,
the server will return from 'accept' with a descriptor which is connected to
the client side.  At this point communication can occur between the two
programs.

If the server is not up (it is not running or for some other reason it has
not called 'passiveTCP'), then after some time 'connectTCP' will return a
negative value to indicate that it has not successfully established a
connection with the server.

The first parameter is the name of the computer that is running the server
program - for example "paradox.sis.pitt.edu" or "rotor.sis.pitt.edu".

The second parameter describes the service or port number that is associated
with the server.  The client uses the same ASCII character string as the server,
which represents the port number assigned in the class meeting - for example
"5100".  As above, note that this is not an integer 5100.
 

2.4 Functions used by both the Server side and the Client side.

Once a connection is established between the client and server programs, then
each of them can use the 'read' and 'write' function calls to exchange data.
The descriptor of the active socket is used just like the file descriptor is
used when we read and write from a file.  Note that we *cannot* use 'lseek'
which is specific to files.

After the client calls 'write', the data in the buffer gets sent to the
server.  The server can call 'read' to get the data.

Similarly, after the server calls 'write', the data in its buffer gets sent
to the client.  The client can call 'read' to get the data.

The read function is blocking.  If one side calls 'read' before the other side
calls 'write', then there is no data to be read.  The 'read' call will block (wait
indefinitely) until some data arrives after the other side calls 'write'.

After the client and server are finished communicating, they each should call
'close' to properly terminate the connection between them.
 

3. Requirements:

You are to write two programs which exchange information from an RSVP list.
One of the programs will be the client side and the other will be the server
side.

The server side program will wait indefinitely for a client to connect to it.
When the client connects, it will read one name record from the client. 
Then it will open an RSVP list file and append the new record to

the end of the file.  Finally, it will send the entire contents of the RSVP
list to the client.  It can then close the active socket and return to waiting
for another client.

The client side program will read a name record from a file.  It will open
a connection to the server and then send that record to the server.

The client will then receive all of the records from the server into a buffer.
The client side will print the records from the server to the screen.

This illustrates how the programs will interact:

1.  The server has no RSVP list entries on its first startup.

2.  The client sends one entry to the server.  The server adds it to the empty
file and returns the entire file (one record) to the client.  The client
prints one record to the screen and exits.

3.  The client sends another entry to the server.  The server appends it to
the file and returns the entire file (now two records) to the client.  The
client prints two records to the screen.

etc.

When the server exits, its RSVP list file should hold all of the names sent to
it by the clients.

The server program should take one command line argument - the port number.
It should use this parameter when calling 'passiveTCP'.

The client program should take three command line arguments - the file name
which holds the name record, the name of the machine where the server
program is running and the port number used by the server.
 

Hints:

You can start with the skeleton program (server-form.c) for the server side, which
was discussed in the class meeting.  You would need to implement the 'dataxfr'
function which is called after a successful call to 'accept'.  This function does
all of the data transfer with the client, and handles the file.

The server side does not need to parse the records.  It can just receive the
new record from the client, append the new record to a file, and then send
everything in the file to the client.

The client side does not need to parse the record it sends to the server.  It
can just read it from the file and send it to the server.   

Pay close attention to the lengths when you use 'read' and 'write':

- Your client must tell 'write' exactly how many bytes to send to the server.

- When you are calling 'read', you don't know how many bytes you will read.
You should always be ready to read more bytes than you expect.   As with
a file, 'read' will tell you exactly how many bytes it has actually read.

You must decide if you want to use a NULL character when you send a record
from the client to the server.   If you do this, you need to be careful how you handle
it when the server writes the record to the file.

If you are working in the lab and run your server program on the machine that you
are logged in to, then the name of that machine is the name you should give to your
client.  In this case you should not have your client connect to paradox.  You can
get the name of the lab machines by looking at the label that is attached to each
of them.

4. Deliverables:

Email your source code files, any header files and a shell script which builds
the executable to the GSA.
 


Test Input Files

a.cdb
b.cdb
c.cdb
d.cdb
e.cdb
f.cdb

libutil library

libutil-v2.tar
 

Server Skelton

server-form