The Proton toolkit is built around the concept of a protocol engine. The idea behind a protocol engine is to capture all the complex details of implementing a given protocol in a way that is as decoupled as possible from OS details such as I/O and threading models, resulting in a highly reusable/embeddable component that provides a full protocol implementation. To see how this works in the general case, it helps to start out with some simple examples of engines and build up to the full model used by Proton.
A common first step in writing networking code is to debug the network I/O portion independently from the rest of the application by using a much simpler protocol than the application will ultimately demand. Often the simple protocol chosen is the echo protocol. Anything sent to an echo server will be copied directly back to the client. The exact details of implementing an echo server might vary depending on what kind of I/O framework you are using, but it's inevitable that somewhere in the heart of an echo server there is some kind of data structure, either implicit or explicit, serving as a byte queue aka a circular buffer. If you define an abstract interface around this byte queue, you have something that captures the admittedly trivial semantics of the echo protocol, but is a pure data structure, and therefore doesn't need to interact with the OS at all. We now have the echo engine depicted below. A simple circular buffer or byte queue, but with a slightly fancier title and because the interface is abstract, we can start tweaking the implementation independently from the rest of our server.
Gliffy Diagram |
---|
size | L |
---|
name | Echo Engine |
---|
align | center |
---|
version | 2 |
---|
|
Now let's try to make this example do something a little more interesting by adding a query interface to our engine as depicted below. The I/O portion of our application still views the engine as a pure circular buffer, however we can now add more sophisticated logic based on inspecting the stream of bytes that flows through the engine. We could, for example, log exactly how many bytes pass through, or decode some or all of the stream and provide an interface that would allow the application to log the semantic data encoded within.
Gliffy Diagram |
---|
size | L |
---|
name | Echo Engine with Query Interface |
---|
align | center |
---|
version | 2 |
---|
|
This is about as far as we can go with an echo protocol, so lets now consider modifying our example to be a simple request/response protocol. Something where every response is a predefined function of each request. As depicted below, with the transformation function entirely encapsulated inside the engine, the interface presented to the I/O portion of the application is completely identical to our original echo server. However, unlike the echo server, we now have the ability to capture a large class of protocols inside this kind of engine, and furthermore as the engine is still essentially a pure data structure (if not quite a simple one anymore) it remains entirely decoupled from aspects of the OS such as I/O and threading.
Gliffy Diagram |
---|
size | L |
---|
name | Simple Request-Response Engine |
---|
align | center |
---|
version | 2 |
---|
|
Now just as we did with the echo server, we can also extend the simple request/response scenario with a query interface as depicted below.
Gliffy Diagram |
---|
size | L |
---|
name | Simple Request-Response Engine with Query Interfac |
---|
align | center |
---|
version | 2 |
---|
|
At this point even though we could capture a wide variety of protocols with this class of engine, there are still significant limitations. For example if the response transformation requires accessing a file as is the case with classic HTTP, our engine is no longer decoupled from the OS, and could in fact block on file I/O, or suppose in the case of modern HTTP the request might actually be invoking application logic and we simply can't pre-define the result within the confines of our library. To accommodate these scenarios, while still keeping the engine as a simple data structure that is entirely decoupled from the OS, we can add a compliment to the query interface that allows the application to control the response as depicted below. Note that in this case, the I/O portion of our server still views the engine as a simple circular buffer. It need not know what kind of processing happens to the byte stream as it passes through the engine.
Gliffy Diagram |
---|
size | L |
---|
name | Request-Response Engine |
---|
align | center |
---|
version | 3 |
---|
|
While the above design nicely captures a very general class of protocol, it is still limited to protocols that use only request/response style interactions. The output bytes are always a function of the input bytes, even though the nature of that function may be influenced by the control interface. The final step to a fully general purpose protocol engine is to define the output bytes as a function of both the input bytes and the control interface as depicted below. This allows for fully general protocols whose interactions are not so regimented as request/response.
It is important to note with the general purpose engine depicted below, while the I/O portion of the application is almost identical to our original echo server, there is one important difference. For the echo protocol pushing bytes into the engine is the only thing that will cause them to be produced, whereas for the general purpose engine, any access to the control interface may cause bytes to be produced.
Gliffy Diagram |
---|
size | L |
---|
name | General Engine |
---|
align | center |
---|
version | 3 |
---|
|
A further refinement of the general purpose engine is to separate the control interface into a distinct entity with its own lifecycle that can be bound to a given transport and then unbound. This separation has a number of nice features. For one thing it partitions the engine interface into a bottom half that can keep a very simple I/O oriented view of the world, and a top half that reflects the high level application interface to the protocol. This separation also permits the control interface to carry state across multiple physical connections, which can allow for a much more sophisticated control entity that can provide a simpler interface to more sophisticated protocols.
Gliffy Diagram |
---|
size | L |
---|
name | Seperate Control Interface Lifcycle |
---|
align | center |
---|
version | 2 |
---|
|