Beautiful Code [298]
Moreover, to leverage our existing design and code, it is preferable to have the log record processing logic inside the handle_data( ) method and to define a Thread_Args helper object that holds the peer returned from accept( ) and a pointer to the Logging_Server object itself. Our class interface will therefore look like the diagram in Figure 26-11.
Figure 26-11. Thread-per-connection server interface
The remainder of TPC_Logging_Server is straightforward to implement, requiring only that our thread entry point delegate processing to the virtual method handle_data( ) using the server_pointer contained within the Thread_Args helper object passed to the svc( ) method, as shown in Figure 26-12.
Figure 26-12. Thread-per-connection thread behavior
The following code implements a TPC_Logging_Server main program that uses the secure socket API and the readers/writer lock:
int main (int argc, char *argv[]) {
TPC_Logging_Server server.run (); return 0; } This main( ) function instantiates a TPC_Logging_Server that communicates using SSL connections, and uses an RW_Lock to synchronize the count_connections( ) function in the Logging_Server base class. Except for the name of the class we are instantiating, this main( ) function is identical to the one that was written earlier in this chapter for the Reactive_Logging_Server. This commonality is another beautiful aspect of our design: regardless of the particular combination of concurrency, IPC, and synchronization mechanisms we choose to use, the instantiation and invocation of our server remains the same. The thread-per-connection logging service addresses the scalability limitations with the sequential implementations described earlier in the section "Evaluating the Sequential Logging Server Solutions." The design of our OO framework makes it straightforward to integrate this concurrency model with minimal changes to the existing code. In particular, TPC_Logging_Server inherits implementations of open( ), count_request( ), and most importantly run( ), allowing this class to leverage bug fixes and improvements to our main event loop transparently. Moreover, adding the necessary synchronization around the request_count_ is simply a matter of parameterizing the TPC_Logging_Server with the RW_LOCK class. 26.4.2. A Process-per-Connection Logging Server The process-per-connection logging server described next is similar to the thread-per-connection design shown in Figure 26-10, except that instead of spawning a thread, we spawn a new process to handle incoming log records from each client. The choice of processes over threads for concurrency forces us to make design choices to accommodate the variations in process-creation semantics between platforms. There are two key semantic differences between the process APIs on Linux and Windows that our server design must encapsulate: In Linux (and other POSIX systems) the primary vehicle for creating new processes is the fork( ) system function, which generates an exact duplicate of the calling program image, including open I/O handles. The processes differ only in their return value from fork( ). At this point, child processes can choose to proceed from that point, or load a different program image using the exec*( ) family of system calls. Windows, however, uses the CreateProcess( ) API call, which is functionally equivalent to a POSIX fork( ), followed immediately by a call to one of the exec*( ) system functions. The impact of this difference is that in Windows you have an entirely new process that by default does not have access to I/O handles open in the parent. To use a connection accepted by the parent process, therefore, the handle must be explicitly duplicated and passed to the child on the command line. We therefore define a set of wrapper facades that not only hide the syntactic differences