Helios 2.0 APIs
The general workflow for developers intending to consume Helios 2.0 is as follows:
- Define a Helios
IChannelobject, which represents a duplex channel for interacting with an arbitrary socket implementation (ITransport.)IChannels can be configured and bootstrapped to support different transports with different types of configuration options, including TLS support. - An
IChannelwill contain anIChannelPipeline, which defines a duplex pipeline of variousIChannelHandlerimplementations who will be invoked in sequential order during events emanating from or TO the socket, depending on how theIChannelobject 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, ServerChannels 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.
IChannels 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
IChannelAdapterimplementation and have it automatically be thread-safe; - We can safely add or remove
IChannelHandlers from theIChannelPipelineon a per-host basis; for instance, if a user authenticates we can remove theAuthenticationChannelHandlerfrom the pipeline and replace it with aAuthenticatedChannelHandlergoing 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
IChannelfires itsChannelRead()operation, which passes the 200 bytes to aLengthFrameDecoder(implementsIChannelHandler) step in theIChannelPipeline. - The
LengthFrameDecoderpulls each of the individual frames out of the 200 byte buffer and passes those messages one call at a time to the nextIChannelHandlerin the pipeline, aJsonSerializerstep. - The
JsonSerializerdeserializes a message into a usable C# object, and calls the next step in the pipeline -a user-definedIChannelHandlerimplementation. - The user-defined
IChannelHandlerdoes 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 IChannelHandlers 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
IChannelHandlerinterface, 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
}