Assignment 1: Writing a Thread-per-Request Web Client and Server
The purpose of this assignment is to acquaint you with using the ACE C++ toolkit to perform
network IPC between two host machines. We will actually be writing
two programs: (1) a simple web client and (2) a simple web server. In
brackets are some hints about what kinds of ACE classes you might want
to look up to do these, which are described in the ACE books and ACE online
documentation. The only components that you must use for
this assignment are the ACE C++ socket
wrapper facades and the ACE_Thread_Manager. The
other hints are simply for your convenience.
Web Client
The simple web client program should do the following activities:
- Read the URL and the server port number from the command
line. Create a socket that is connected to the server machine at the
specified port (e.g., HTTP port 80) [ACE_INET_Addr,
ACE_SOCK_Connector, ACE_SOCK_Stream].
- Send a request to the web server using the HTTP protocol format.
This will look something like this:
GET /index.html HTTP/1.0\n\n
Note that it's very important to include the two trailing newlines --
they are required by the HTTP protocol.
- Read all the data from the HTTP connection and write it to a
temporary file created in your web cache (e.g., using
ACE_FILE_Connector::connect()) on the local host
[creat,read/write].
- The following portion of the web client is just for the
graduate students in the class. Spawn an external viewer to display the
file. You can determine the type of viewer to spawn in two ways:
- Client-side file suffix -- The client can use the
file suffix (e.g., *.ps should spawn ghostview, *.gif should spawn xv,
an html file should spawn lynx, and a regular text file should spawn
/usr/ucb/more, etc.). If the file is compressed (e.g., *.gz, *.Z, or
*.zip) then uncompress it before viewing it.
- Server-side MIME content type information -- A more
robust way to determine what type of the viewer to spawn is to
utilize the Content-type: header returned by the server.
For instance, the GET /index.html HTTP/1.0\n\n request
from the client will return the following header:
HTTP/1.0 200 Document follows
Date: Wed, 11 Sep 1996 23:28:40 GMT
Server: NCSA/1.5.2
Content-type: text/html
Your client can simply parse this information and use it to determine
which viewer to spawn. BTW, this solution is the one used by web
browsers.
The client should simply print out the appropriate error message
[perror] and exit with a return status of 1 if any of the system calls
fail to work properly. If everything works correctly, the program
should exit with a return status of 0.
Web Server
The concurrent multi-threaded server program should do the following
activities:
- Create an Internet domain ``passive-mode'' stream socket on a
port number specified on the command line
[ACE_SOCK_Acceptor] and wait in the main thread for connections to
arrive from clients.
- When a client request arrives, accept the connection
[ACE_SOCK_Acceptor::accept, ACE_SOCK_Stream] and spawn a worker thread
[ACE_Thread_Manager::spawn]. The main thread should go back to listening for
incoming client requests in the event loop while the new thread
carries out the client request. Your worker thread should
perform the data transfer itself, rather than using cat.
- The new thread should read in one HTTP request such as
GET /index.html HTTP/1.0
and try to open the file in your local web directory
[ACE_FILE_Connector].
- Assuming open succeeds, the thread should transfer the file back
to the client.
- The server should not exit unless you explicitly kill it via a
signal (e.g., SIGINT). You should add a signal handler
[ACE_Sig_Action] that performs any termination code necessary to
gracefully shutdown the server upon receipt of a signal. It is
important to devise a portable scheme for handling signals and
shutting down tasks in multi-threaded programs.
Make sure that all resources you allocate in the program, including
memory, socket, and files, are deallocated before your program exits
to avoid leaks.
Comments
Although this assignment is fairly easy to describe, there are some
subtleties involved in making it work correctly. In particular, you
will need to learn about many details such as network addressing,
remote host identification, signal handling, thread creation and
termination, etc.
In addition, please note that although we will use the ACE C++ socket
wrapper facades, you can actually communicate between processes on the
same host for testing purposes. However, the same programs will work
for communicating between processes on two separate host machines,
i.e., only the addresses will be different!
References