This assignment serves as an introduction to client-server programming using the TCP/IP protocols. In this assignment, you will write both a client and a server. Your client and server will communicate using TCP, and your server may implement any service you choose. The ground rules are simple: the server reads and writes data to and from the TCP connection. The server may prompt the client for input, or simply print a random message. Once you have debugged the server, it executes in background (even after you log out) waiting for service requests from a client.
To access a service, the client opens a TCP connection to the server, sends and receives data, and then closes the connection. Conceptually, the client acts as a pipe between your terminal and the server, copying data sent by the server to standard output and sending data read from standard input to the server. The client terminates the connection with the server when it receives an end-of-file from either the server or standard input.
The above scenario omits a key aspect of client-server programming: How does the client find out where the server is? That is, what transport-level address (Internet address and port number) should the client connect to?
One solution is to use a name server that dynamically maps service names into their transport-level addresses. We will supply you with a such a server, called oracle, allowing you to register the service you provide and advertise it to other students in the class. Conceptually, the oracle is like the white pages in your phone book. A server registers the name and transport address of its service in the phone book, and clients use the phone book to map service names to transport addresses.
When your server starts, the operating system will assign it an unused port number (e.g., XXX) on which it can wait for incoming connection requests. The server then advertises the availability of its service by sending a short message to the oracle containing the name of the service (e.g., ``daytime'') together with the transport address (host number and port XXX). The oracle server records the name-to-address mapping in its local database.
When a client wishes to connect to a server, it first sends a message containing the desired service name (e.g., ``daytime'') to the oracle, and the oracle returns a message with the appropriate transport address (e.g., XXX). The client then opens a TCP connection to that service. Exact details for communicating with the oracle are described below.
The client accepts the following set of commands from standard input:
When the client wishes to connect to a server, it traces the following steps:
The server traces the following steps when making a service available:
Oracle resides on machine garden at well-known UDP port netoracle. All communication with oracle is through UDP messages containing a structure called an om (for ``oracle message'', pronounced ``ohhmm''), whose definition can be found in the file oracle.h in /cs/cs4514/pub/lib. The file is reproduced below:
# define luid 8
# define cchMaxServ 10
# define cchMaxDesc 40
# define verCur 'C'
enum cmd {
cmdErr, /* An error occurred. See sbDesc for details */
cmdGet, /* Get the address of a service */
cmdAckGet, /* ACK for cmdGet message */
cmdEnd, /* Last response to a cmdGet message */
cmdPut, /* Register a new service */
cmdAckPut, /* ACK for cmdPut message */
cmdClr, /* Unregister a service */
cmdAckClr /* ACK for cmdClr message */
};
struct om { /* oracle message */
char ver; /* version number of this structure */
enum cmd cmd; /* command/reply code */
char sbDesc[cchMaxDesc]; /* description of service (or error reply) */
char uid[luid]; /* user id (login id) of requester/provider */
char sbServ[cchMaxServ]; /* name of service requested/provided */
struct sockaddr_in sa; /* socket addr where service is available */
unsigned long ti; /* time of registration */
};
# define lom (sizeof (struct om))
To find a service, your client program fills in the fields of the om structure as follows:
In response to a cmdGet message, oracle returns two or more messages. Response messages have a cmd type of cmdAckGet, and the end of cmdAck responses is signaled by a cmdEnd message. CmdEnd messages do not contain the name of a service; they simply signal the end the last response. If only one service matches the client's request, the server will return two messages: a cmdAckGet, followed by a cmdEnd. Each cmdAck message contains the following fields:
When the server wishes to register a service with the oracle, it sends an om message with the following fields:
In response to a cmdPut message, oracle returns a message of type cmdAckPut if the registration succeeds. In the case of errors, the oracle returns a message of type cmdErr, and the sets the field sbDesc to contain a short explanation of the error.
The basic objective of the assignment is to build a client that can obtain a list of services from the oracle server and connect to a simple service (one that just returns output such as daytime). Your server should be able to register itself with the oracle server and return information when a client connects to it.
Completion of the basic objectives is worth 20 of the 30 points for the assignment. For the additional points of the project, your client will need to be able to handle connect to multiple services within a session (in a serial manner, not in parallel). Your client should also work well both with services that require interaction (both input and output) as well as simple services just producing output. To obtain additional credit for your server, it must be interactive in that it both requires input and produces output.
Nearly all system calls and library routines return some form of error code if the operation was not successful. You must check the return value from every routine for an error code.
We suggest the following steps:
You have two weeks in order to complete the project. There are three parts to the project you should have each part done in less than one week to stay on schedule. In other words your client routine should be able to obtain a listing of all services no later than November 4th.
As an aid to debugging, as well as to trace use of your server, you should print trace output when connections are made. For example, you might print the host name and port number of every user that connects to your server, along with the time of the connection.
Requests to the oracle are actually a bit more general than we've described so far. Regular expressions can be used as service names or users ids to effect a kind of ``wild-search'' for services. For example, specifying a service name of ``.*'' matches all services. Specifying a user name of ``...'' matches all services provided by users with three-character ids. The format of regular expressions is the same as that of ed(1).
Use your imagination and creativity in designing server programs. ``Neat'' servers are popular for testing by clients written by your peers.
The following library procedures and system calls will be helpful: clearerr(3), gethostname(2), listen(2), close(2), getpwent(3), getuid(2), send(2), recv(2), getservbyname(3), gethostbyname(3), gethostbyaddr(3), getsockname(2), bind(2), socket(2), connect(2), accept(2), listen(2) and select(2).
For more information on how the Unix networking-related library routines and system calls, see the handout from class, an excerpt from the book Unix Networking, which explains how to use the Unix library routines.