In the client/server tutorials that we've done so far it was just a matter of sending a buffer of data to the peer. This was done with the send*() and recv*() methods of the ACE_SOCK* objects.
In a more robust system, one might want to process the data before sending it to a peer and "unprocess" it after reading from a peer. These processing steps might include encryption, compression, applying checksums or any number of other actions.
In this tutorial a Protocol_Stream object is created to encrypt and compress* data being sent between peers. Both client and server applications are presented as well. I present the application level code first and then go into the details of the protocol stream and it's helper objects. If the stream stuff in the application logic is confusing then just read on by and come back to it after the later discussions.
ACE_Message_Blocks are used to communicate between the client and the server across the Protocol Stream, which abstracts the protocol conformance details. The underlying SOCK_Stream is used to set up the connection using the ACE_SOCK_Connector class. Once the connector completes its job, the SOCK_Stream pointer is passed on to the Protocol Stream which now takes over. The Client has put() and get() methods to send and receive data from the server.
The server is implemented using the ACE_Acceptor to listen at the port for connections and a reactor for delegating events to the appropriate event handler. The handle_input () method of the handler simply allows the stream to receive the data and hand it over to the Handler_Task (a derivative of the ACE_Task) which will then process it.
The implementation of this Protocol Stream model is done using the ACE_Module class. The module for Xmit/Recv is shoved in first into the stream, followed by the encryption and compression modules. The optional Reader if defined is bundled with a dummy task (ACE_Thru_Task class) into a module. The get() and put() methods do the job of reading and writing to the Stream. Each module is made up of a pair of Protocol Tasks. A Protocol Task is a derivative of the ACE_Task and whose recv() and send() methods need to be filled to perform the appropriate task.
The Xmit object derives from the Protocol task and has a send() method which does the task of transmitting data to the underlying SOCK_Stream. Keeping the fragmentation and reassembly issues in mind, block-size is also sent across with the block of data. The Recv object uses a dummy Message Block to provoke the Protocol Task object to call the recv() on it. This is done by being foresighted about the use of mutliple threads instead of a single thread.
The compression/decompression is bundled in a single Protocol Task object with the send () method doing the compression and the recv() doing the decompression. Similarly, the encrption/decryption is done using a single Protocol Task object.
This tutorial provides a glimpse on how to design and implement a protocol in layers and also revises a lot of what has been learnt until now from the previous tutorials. (for instance, Message_Block, Task, Acceptor, Connector, Event_Handler etc.)
* Ok, I didn't really implement encryption and compression objects. I'll leave that as a thought exercise!