Chapter 14. A real world problem, and its solution.
In this chapter:
Over the past couple of years I have been writing a distributed raytracer. This uses TCP/IP to send descriptions of scenes to be rendered across a network from a central server to a collection of clients. The clients render the image, and then return the data to the server. Some beta testers were interested in trying the program out, but mentioned that they did not have a TCP/IP stack loaded on their machine. I decided that it would be useful to write some code that emulated TCP sockets, allowing communication between two applications (both client and server) on the local machine.
Various potential solutions were investigated. The most promising at first seemed to be to use named pipes. Unfortunately a problem soon cropped up: The protocols I was using on top of TCP/IP assumed that connection semantics could be performed on a strictly peer to peer basis: either program could initiate a connection to the other, and either program could disconnect at any time. Both connection and disconnection were perfectly symmetrical: The protocols used on top of TCP performed a three way handshake over and above that performed at the TCP layer to negotiate whether a connection could be closed, and that having occured, either end could close the connection. Unfortunately, named pipes did not provide the correct disconnection semantics, and they did not cope well with various error situations.
I do not intend to explain the solution in detail, but more advanced readers may find the code interesting reading. In the end, I decided to use shared memory for data transfer, and to implement all synchronisation from the ground up. The solution was implemented in 3 stages.
- A DLL was written which provided a bidirectional blocking pipe between two applications.
- A pair of reader and writer threads were written to allow asynchronous access to the blocking pipes.
- A wrapper around the threads was written to provide an asynchronous interface similar to nonblocking sockets.
The pipe DLL and interface files.
This DLL is similar to the bounded buffer example found in chapter 9. Looking back on this code, I can only presume that I'd written it after a couple of weeks frantic hacking in C at work, because it's far more convoluted than it needs to be. One point of interest is that the semaphores used for blocking operations do not assume that the bounded buffers are any particular size; instead state is kept on whether the reader or writer threads are blocked or not.
The reader and writer threads.
The pipe threads are exactly analogous to the reader and writer threads in the BAB in chapter 10. Notifications are not used for write operations, instead, the writer thread buffers the data internally. This was allowable given the semantics of higher layer protocols.
A socket based interface.
This is a not-quite-pure socket interface, and should be reasonably obvious to those familiar with TCP sockets programming. Since this implementation was designed to work specifically with some other protocols I wrote, it is worthwhile including the transaction layer of the overlying protocols so you can see how the socket fits into the scheme of things