Helios 2.0 APIs
The general workflow for developers intending to consume Helios 2.0 is as follows:
- Define a Helios
IChannel
object, which represents a duplex channel for interacting with an arbitrary socket implementation (ITransport
.)IChannel
s can be configured and bootstrapped to support different transports with different types of configuration options, including TLS support. - An
IChannel
will contain anIChannelPipeline
, which defines a duplex pipeline of variousIChannelHandler
implementations who will be invoked in sequential order during events emanating from or TO the socket, depending on how theIChannel
object is called. - All operations called on the socket will themselves be asynchronous and will return a
ChannelTask
, which is really just a wrapper forTask<ChannelContext>
.
Here's a diagram that illustrates what this duplex pipeline would look like under a typical scenario:
What sorts of things will IChannelHandler
do?
An IChannelHandler
implementation can be anything that occurs during the normal process of working with a socket:
- Decoding and encoding messages (framing;)
- Encrypting and decrypting messages;
- Serializing and deserializing messages;
- Authentication and authorization; and
- Performing any arbitrary application-level work with deserialized messages.
An IChannelHandler
can be anything involved in the process of working with each remote host connected to the underlying ITransport
- and Helios will provide a number of built-in IChannelHandler
implementations for performing routine functions such as message framing.
How many IChannel
instances are created per ITransport
?
In server-side scenarios, ServerChannel
s have the ability to create child IChannel
instances for each remote host - this allows server-side developers to write their IChannelHandler
implementations for working with a single host, thus an AuthenticationChannelHandler
only needs to worry about authenticating a single user, versus authenticating potentially thousands.
This is, fundamentally, a much simpler programming model.
On the client-side, a developer can open a single SocketChannel
and it will have precisely one IChannel
instance.
IChannel
s run on a single thread
Each IChannel
instance processes one batch of messages at a time, which gives us the following benefits as developers:
- We can maintain state that is unique to the remote host on the other end of the socket inside each
IChannelAdapter
implementation and have it automatically be thread-safe; - We can safely add or remove
IChannelHandler
s from theIChannelPipeline
on a per-host basis; for instance, if a user authenticates we can remove theAuthenticationChannelHandler
from the pipeline and replace it with aAuthenticatedChannelHandler
going forward.
IChannel
won't begin reading new messages from the socket until its finished processing its last read
Consider the following scenario:
- 200 bytes arrive on a TCP socket between reads, and each contains 10 framed messages (4 byte header + 16 byte message.)
- The
IChannel
fires itsChannelRead()
operation, which passes the 200 bytes to aLengthFrameDecoder
(implementsIChannelHandler
) step in theIChannelPipeline
. - The
LengthFrameDecoder
pulls each of the individual frames out of the 200 byte buffer and passes those messages one call at a time to the nextIChannelHandler
in the pipeline, aJsonSerializer
step. - The
JsonSerializer
deserializes a message into a usable C# object, and calls the next step in the pipeline -a user-definedIChannelHandler
implementation. - The user-defined
IChannelHandler
does something with the C# object passed into it from earlier, and the operation is complete for that individual message. - This process gets repeated 9 more times for each of the messages that were decoded by the
LengthFrameDecoder
.
Before we can read any additional bytes from the socket, we have to finish processing all of the individual messages decoded by the LengthFrameDecoder
(or write our own IChannelHandler
s that can use them in batch.)
Here's what that process might look like, animated:
All IChannelHandler
implementations can respond to multiple types of channel events
There are lots of different types of events that may occur in within the channel:
- Read
- Write
- Open
- Bind
- Close
- Disconnect
- User-defined events.
Each IChannelHandler
must have an implementation for each of the event handlers for these types of events, although some IChannelHandler
implementations may choose not to do much of anything during some of these events. What's a LengthFrameEncoder
going to do during a close event?
The IChannelHandler
interface looks like this:
public interface IChannelHandler{
void ChannelRead(ChannelHandlerContext context, object msg);
void Write(ChannelHandlerContext context, object msg, ChannelPromise promise);
void Connect(ChannelHandlerContext context, IpAddress address, ChannelPromise promise);
void Disconnect(ChannelHandlerContext context, ChannelPromise promise);
}
N.B. This is not the final design of the
IChannelHandler
interface, but an approximation of it for the sake of documenting the spec.
An implementation of this interface that didn't handle any outbound writes, such as a decoder / deserializer, might have the following implementation:
public class DownStreamChannelHandler : IChannelHandler{
public void Write(ChannelHandlerContext context, object msg, ChannelPromise promise){
context.Write(msg, promise); //forward msg onto the next handler in the pipeline
}
// other IChannelHandler implementations
}