This assignment is intended to introduce you to using sockets in a client-server system. The server processes communicate with shared memory and synchronize by using semaphores. Each client process communicates to a server via a socket. You are to implement the program described below on any CCC Alpha machines (reno, bert, ernie, scooter, wpi ...)
You are to make a multi-player game of computer
handball
(handball is a made-up variation of
pong
that will allow 2+ people play the same
ball-bouncing type of game with paddles). Your handball
game must have:
Here is a screen-shot of a possible game in action:
Freddy: 3 Billy: 8 Marky: 6 Freddy's Turn /-------------------------------------- \ | | | | | | | | | | | | | * | | | | | | | | | | | | | | | | ===== | | | | | j- left, k- right, space- stop
(Coming soon! You can try out the above game at:
/cs/cs3013/server
and
/cs/cs3013/handball
.)
You are free (and encouraged) to be creative with any of the game aspects, including speed, direction, paddle size, characters, keyboard controls, scoring ...
The game is made up of clients and servers. The servers share the game state between them. The clients access the servers to get the game state and then display it on the screen. The clients also send paddle and ball movement (when appropriate) to the servers.
The server "listens" on a pre-chosen port on it's host
workstation. As an analogy, if the host workstation is a street name
(say, Institute Road), then the port is the house number on that
street (say, 100). The port is specified as an integer. You should
choose a port number between 5001 - 9999. Together, a
<host,port>
pair gives the address for the server.
You can assume the server is started first. Using the host and the
port, the client will connect to the server.
When the server receives a connection, it does a
fork()
to create a child process to service the request.
The client and the child will then communicate along the connected
socket. The child will retrieve any needed information for the
client, and send that information back along the connected socket (the
communication is 2-way). When the child is finished, it sends an
"exit" message to the client and terminates. The parent, meanwhile,
has returned to listening on the original socket and port, waiting for
connections from other clients.
Technically, there is no output to a terminal required by the server, but you will probably find it helpful to have the server display status messages for logging and debugging.
In all cases, the server should handle errors such as validating the data sent by the clients, socket connections, fork requests, etc.
As in project 2, you will use curses to help with the text-based aspects of the program. You can also use shared memory and semaphores to synchronize among the server processes, and signals for alarm handlers and graceful cleanup of resources.
Note that the shared memory wrappers from project 2 limit you to
2K. You can modify this by changing a value in the shm.h
header file. If you do so, please use care. Also, the shared memory
wrappers will only let you create 1 shared memory segment at a time.
This is a limitation of the wrappers, not of Unix shared memory. If
you want more than one shared memory segment, you will have to modify
the wrapper source code.
You will use sockets for your client-server communication. To
simplify the use of sockets, you may use some code wrappers written
especially for this project. The source file containing these
wrappers are sock.c
and a header
file containing prototype definitions is sock.h
. The routines provided in
sock.h
are:
/* sockcreate -- Create socket from which to read. Takes port as argument. Return socket descriptor if ok, -1 if not ok. */ int sockcreate(int port); /* sockaccept -- accept connection on a socket. Takes socket descriptor (from sockcreate) as argument. Return new socket descriptor if ok, -1 if not ok. */ int sockaccept(int sock); /* sockconnect -- create socket to host at port. Takes host name (ex- "reno") and port as argument. Return socket if ok, -1 if no ok. */ int sockconnect(char *host, int port);
You will also use the non-wrapped system calls of send
and recv
. In particular, the socket descriptors returned
by sockcreate()
and sockconnect()
are
parameters to send()
and recv()
. Do a
man 2 send
and a man 2 recv
for more
information. See the Samples
section for an example of how these wrappers might be used.
As with the shared memory and semaphore wrappers, you must compile
the file sock.c
yourself. Do this by downloading both
the sock.c
and sock.h
files to your
directory and using the command gcc -c sock.c
. Better
still, use a Makefile (see Makefiles in project 2 for more
information) to compile.
Note, unlike message queues, shared memory and semaphores, sockets are removed for you by the OS when your process terminates.
Pseudo-code for your handball server might look like:
create shared memory create semaphore set signal handler to catch C-c while (1) { accept connections fork child if (parent) then continue make sure not too many players receive message with players name add player to game while (!done) { /* play game */ receive message (command) move paddle (if appropriate) send message (board) } remove player exit child process } remove shared mem and sem
Pseudo-code for your handball client might look like:
connect to server set up signal handler for sending messages on Control-C send login information /* name */ while(!done) { send read request /* to get game state */ draw on screen /* chat */ if (my turn) get paddle move move ball send update sleep until alarm } send logout information end
Answer the following questions when you turn in your project:
...There are some network protocols that do not guarantee that all
the bytes you send()
will arrive at the other process'
recv()
call. In other words, if you send a paddle move
or a board update, it may not arrive. Briefly describe how you would
modify your client and server to handle such a network protocol.
There were two architectural solutions to this project that
were discussed in class: forked server where the server process
forks a child to handle each client, and select server where a
single server process handles all clients with the help of the
select()
call. Which did you use? Describe one advantage of
your approach. Describe one advantage of the alternate approach.
Send all questions to the TA mailing list.