Last
updated: 30/12/04
Exercise 4: Implementing an "Echo" server
In this C exercise you will write an ECHO server implementing the ECHO
protocol following all the steps of the ECHO RFC.
Content:
Introduction:
The ECHO service is a standard TCP/IP service:
an echo server accepts connection requests from clients on some port. After
the connection has been established, the server simply echoes back all the
data arriving from the client, until the client disconnects. You will write
a ECHO server while following all the steps in the
ECHO RFC862 that are
relevant for implementing the protocol over TCP. The purpose of the exercise
is to learn sockets in C, the select system call and non-blocking
I/O.
The program must be written in C.
Synopsis:
Each node is implemented by a process. You should write a program called
echoserver .c that implements a node.
The program synopsis (you can assume the input is correct but you should print a
usage in case the program is executed with no parameters):
echoserver <port>
Basic requirements of the server
design:
- The server handles all connections in a single-threaded
process. It creates neither new processes nor new threads.
Whenever a client connects to the server, a new socket is opened to
communicate with the client. All these sockets are managed by a single
process.
The server will handle concurrency using UNIX I/O multiplexing,
provided by system call select(2).
- The server must provide for a design that guarantees:
- fare share of the server resources among all active clients (for
example, the situation when the server gets stuck waiting for data from
one of the clients, while other clients are waiting for the service, is
unacceptable).
- a misbehaving client cannot disrupt service to other clients. In
particular, the server must keep running properly no matter what
(malicious) clients do.
Details:
When designing the server, think about the following points:
- You may refuse or fail to serve new clients only when
the system resources are exhausted (e.g. too many sockets are already open).
- After the server has entered the main loop (waiting for connection
request, accepting connection and serving it) successfully, it must keep
running properly forever, no matter what clients do.
- Since the server serves all the clients in a single process, this
process becomes a single point of failure: if the process crashes, ALL
clients are cut off the services. Thus, one must beware that one misbehaving
client may disrupt the service for all other clients.
- If the server uses blocking I/O, and a client just doesn't
read what the server sends it, the server sooner or later will get stuck in
write(2) to this client, and all other clients will be stuck as well,
which is violation of our basic requirements to the server.
Therefore, our server must use non blocking I/O for all its
communications with clients.
- Since TCP is a byte-stream protocol, read()/write() may input or
output fewer bytes than requested, but this is not an error.
If you are interested in exactly the requested amount of data to be
input/output, all that is required is for the caller to invoke read()
or write() function again, to input or output remaining bytes.
- Dispose immediately of unnecessary resources (memory, open files/sockets
etc) to avoid the server running out of the system resources.
- You cannot assume that on return select() modifies
the last argument to hold unslept time.
- Use sigaction() function instead of signal() when
handling signals.
Testing Your Program:
You will probably need to write a client programs for the ECHO service provided by the server. Use this clients to test
your server.
- First, run a single client on the same machine where the server runs.
- Run server and a client on different machines.
- Run a set of clients simultaneously.
Remember: many bugs in a server implementation express themselves only when the
server is highly loaded.
Relevant/interesting Man Pages:
- The Sockets API system calls
- socket(2), bind(2), listen(2), accept(2), connect(2), read(2),
write(2), send(2), recv(2), shutdown(2), close(2)
- Auxiliary functions
- gethostbyname(3), getprotobyname(3)
- Device/Descriptor Manipulation
- ioctl(2), fcntl(2), getsockopt(2), setsockopt(2)
- Signal handling
- kill(2), alarm(3), ualarm(3), raise(3), sigaction(2),
sigprocmask(2), sigpending(2), sigsuspend(2)
- Synchronous I/O multiplexing
- Time handling functions
Error Handling:
- In all cases of errors, the program must print an informative error
message to stderr (use perror(3) when needed).
- Check thoroughly the command line arguments.
- Check EVERY system call return value.
- In case of an unrecoverable error (e.g., if 'server' can't open a server
socket), the program must exit with status 1.
- After the server have entered the main loop (waiting for connection
request, accepting connection and serving it) successfully, it must keep
running properly forever, no matter what clients do.
- If a client's request cannot be fulfilled, and the connection still
exists, 'server' closes the connection without response.
General:
- Write this ex in C.
- The server should be implemented in a single file named
echoserver.c. Don't submit any other source/header files.
- Do not submit any client you write.
- Submit your source echoserver.c, a README, and a
Makefile that prepares one target: echoserver.
Policies, Tips and Gradings:
- The grading of this exercise will be as follows:
ü
85%: Correctness.
ü
10%: Code structure.
ü
5%: Submission.
com1@cs.huji.ac.il