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
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
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.
© Martin Harvey