Program 3

TCP Card Dealer - Non-Blocking Sockets and Polling


As I mentioned in program 2, there were some handy things about using UDP to implement the server. Probably the biggest one is that you didn't have to worry about dealing with multiple sockets at once. Suppose you had to use TCP instead of UDP, though? There are some reasons that might be required, other than your instructor just giving you a hard time. With UDP, there is no guarantee that a datagram will be delivered, and even if all datagrams are delivered, there is no guarantee that they will be in order. So if it is essential that all requests and responses are delivered in the correct order, you can either roll your own protocol on top of UDP to ensure that (a bad idea), or you can use TCP (much better idea).

So for this assignment, you're going to modify your card dealer server and client from the last assignment to use TCP. This should mean only minimal changes - call the open_tcp_srvr_sock() function instead of the open_udp_srvr_sock() function, right? Unfortunately, nothing is that easy. Once you've opened a TCP server socket, every time you accept a connection from another client, you've got another socket to handle. At any point in time after the first client has connected, the server can't afford to just call accept() on the main server socket, or recv() on a client connection, because the call would block and the server would be ignoring all the other sockets.

There are several different ways to handle this, and you'll get a chance to try some of them on for size in this and the next three assignments. For this one, we'll try a simple, but not terribly efficient, technique known as polling. This is sort of like the polling that we talked about for link level protocols; the server will just loop through all of its sockets on a regular basis, checking each one to see if it has anything happening.

In order to do this, it is important that the server doesn't block on any socket call. You make this happen by changing putting each socket in non-blocking mode. You can do this using the fcntl() call with the O_NDELAY or O_NONBLOCK flag (O_NDELAY is the old constant, and it is now #defined to be O_NONBLOCK). You can see an example of this in the section on Asynchronous Broadcast in Gary's Unix Network Programming Manual. This example is also working with asynchronous I/O, which we won't worry about now. Just look through the code for O_NDELAY - you should do something similar, without also setting the O_ASYNC flag.

Once you have a socket set to non-blocking mode, you can call accept() or recv() on it without getting stuck. So you can set up a loop in your program where you do an accept() on the main server socket then a recv() on each client connection socket, handling any of them that has anything to do, and then do it all over again. This is known as a poll loop. If you do just that, in a really tight loop, it's likely that you will suck up most of your machine's CPU doing just that. So after you've made a pass through all the sockets, you should put in a call to sleep(), so the program will take a break before polling again. A value of one second should work pretty well.

Modify your server as specified - you can give it a new name, but everything else should work just as it did for the previous program. Also create a modified client that uses TCP to connect to the server.

Note that now you are using TCP, so you will be able to call recv() and know when a client breaks the connection. When that occurs, you should close the client socket. You will still need to use <CTRL>-C to break out of the program when you are done running it, because it should always be polling the main server socket (using accept) while it is running.

Designing a Solution

The design you did for the UDP card dealer and the testing techniques you used should still be valid for this program. However, you are now going to need to figure out how you want to manage all the sockets that you will be manipulating. Write up a brief design document before you start hacking code that describes how you will maintain the sockets and list the socket calls that you will need to make. Once again, I suggest that you do this before you start coding.

Building a Code Library

You need to start assembling a library of your socket functions for this program. This library code should be in one or more source files separate from the main code for the card server. You should have one or more header files that define the interface to your library, and corresponding source files. Once you get everything arranged, debugged, and running, you can create an archive file, so that you don't need to recompile the library every time you want to use it.

Suppose your source is in the files my_net_clnt.c and my_net_srvr.c. (I'm not advocating this as the best way to divide things, just using it as an example.) You can use the following commands to create an archive file:

Now when you want to link this library code with your actual application code, you can do something like this:

Assignment Submission

NO hard copy, please. Everything should be submitted via email by the due date - mail everything to Anthony. Follow Anthony's specifications for assignment submissions. You need to include the design, your source for the main application, your source for your library, and the output from your server for two different test runs. For the first test run, start the server with one deck and choose one of the ports designated for the lab (40000-40007). If your server can't open a socket on that port, try another one; you should succeed before you exhaust all eight possibilities. Start a client in another window and request twenty cards, shuffle, then request five more cards. For the second run, start the server with four decks, choosing the port as in the previous run, then start two different clients and request twenty cards from each. (These are the same test runs as you did for the last program.)

You should include the following:

The first two things should just be in the body of the email - attach the design, source, and output as separate files.