Protocols

The following protocols are available globally.

  • The core Channel methods that are for internal use of the Channel implementation only.

    Warning

    If you are not implementing a custom Channel type, you should never call any of these.

    Note

    All methods must be called from the EventLoop thread.

    See more

    Declaration

    Swift

    public protocol ChannelCore : AnyObject
  • A Channel is easiest thought of as a network socket. But it can be anything that is capable of I/O operations such as read, write, connect, and bind.

    Note

    All operations on Channel are thread-safe.

    In SwiftNIO, all I/O operations are asynchronous and hence all operations on Channel are asynchronous too. This means that all I/O operations will return immediately, usually before the work has been completed. The EventLoopPromises passed to or returned by the operations are used to retrieve the result of an operation after it has completed.

    A Channel owns its ChannelPipeline which handles all I/O events and requests associated with the Channel.

    See more

    Declaration

    Swift

    public protocol Channel : AnyObject, ChannelOutboundInvoker
  • You should never implement this protocol directly. Please implement one of its sub-protocols.

    See more

    Declaration

    Swift

    public protocol ChannelHandler : AnyObject
  • Untyped ChannelHandler which handles outbound I/O events or intercept an outbound I/O operation.

    Despite the fact that write is one of the methods on this protocol, you should avoid assuming that outbound events are to do with writing to channel sources. Instead, outbound events are events that are passed to the channel source (e.g. a socket): that is, things you tell the channel source to do. That includes write (write this data to the channel source), but it also includes read (please begin attempting to read from the channel source) and bind (please bind the following address), which have nothing to do with sending data.

    We strongly advise against implementing this protocol directly. Please implement ChannelOutboundHandler.

    See more

    Declaration

    Swift

    public protocol _ChannelOutboundHandler : ChannelHandler
  • Untyped ChannelHandler which handles inbound I/O events.

    Despite the fact that channelRead is one of the methods on this protocol, you should avoid assuming that inbound events are to do with reading from channel sources. Instead, inbound events are events that originate from the channel source (e.g. the socket): that is, events that the channel source tells you about. This includes things like channelRead (there is some data to read), but it also includes things like channelWritabilityChanged (this source is no longer marked writable).

    We strongly advise against implementing this protocol directly. Please implement ChannelInboundHandler.

    See more

    Declaration

    Swift

    public protocol _ChannelInboundHandler : ChannelHandler
  • A RemovableChannelHandler is a ChannelHandler that can be dynamically removed from a ChannelPipeline whilst the Channel is operating normally. A RemovableChannelHandler is required to remove itself from the ChannelPipeline (using ChannelHandlerContext.removeHandler) as soon as possible.

    Note

    When a Channel gets torn down, every ChannelHandler in the Channel‘s ChannelPipeline will be removed from the ChannelPipeline. Those removals however happen synchronously and are not going through the methods of this protocol.
    See more

    Declaration

    Swift

    public protocol RemovableChannelHandler : ChannelHandler
  • ByteToMessageDecoders decode bytes in a stream-like fashion from ByteBuffer to another message type.

    Purpose

    A ByteToMessageDecoder provides a simplified API for handling streams of incoming data that can be broken up into messages. This API boils down to two methods: decode, and decodeLast. These two methods, when implemented, will be used by a ByteToMessageHandler paired with a ByteToMessageDecoder to decode the incoming byte stream into a sequence of messages.

    The reason this helper exists is to smooth away some of the boilerplate and edge case handling code that is often necessary when implementing parsers in a SwiftNIO ChannelPipeline. A ByteToMessageDecoder never needs to worry about how inbound bytes will be buffered, as ByteToMessageHandler deals with that automatically. A ByteToMessageDecoder also never needs to worry about memory exclusivity violations that can occur when re-entrant ChannelPipeline operations occur, as ByteToMessageHandler will deal with those as well.

    Implementing ByteToMessageDecoder

    A type that implements ByteToMessageDecoder must implement two methods: decode and decodeLast.

    decode is the main decoding method, and is the one that will be called most often. decode is invoked whenever data is received by the wrapping ByteToMessageHandler. It is invoked with a ByteBuffer containing all the received data (including any data previously buffered), as well as a ChannelHandlerContext that can be used in the decode function.

    decode is called in a loop by the ByteToMessageHandler. This loop continues until one of two cases occurs:

    1. The input ByteBuffer has no more readable bytes (i.e. .readableBytes == 0); OR
    2. The decode method returns .needMoreData.

    The reason this method is invoked in a loop is to ensure that the stream-like properties of inbound data are respected. It is entirely possible for ByteToMessageDecoder to receive either fewer bytes than a single message, or multiple messages in one go. Rather than have the ByteToMessageDecoder handle all of the complexity of this, the logic can be boiled down to a single choice: has the ByteToMessageDecoder been able to move the state forward or not? If it has, rather than containing an internal loop it may simply return .continue in order to request that decode be invoked again immediately. If it has not, it can return .needMoreData to ask to be left alone until more data has been returned from the network.

    Essentially, if the next parsing step could not be taken because there wasn’t enough data available, return .needMoreData. Otherwise, return .continue. This will allow a ByteToMessageDecoder implementation to ignore the awkward way data arrives from the network, and to just treat it as a series of decode calls.

    decodeLast is a cousin of decode. It is also called in a loop, but unlike with decode this loop will only ever occur once: when the ChannelHandlerContext belonging to this ByteToMessageDecoder is about to become invalidated. This invalidation happens in two situations: when EOF is received from the network, or when the ByteToMessageDecoder is being removed from the ChannelPipeline. The distinction between these two states is captured by the value of seenEOF.

    In this condition, the ByteToMessageDecoder must now produce any final messages it can with the bytes it has available. In protocols where EOF is used as a message delimiter, having decodeLast called with seenEOF == true may produce further messages. In other cases, decodeLast may choose to deliver any buffered bytes as leftovers, either in error messages or via channelRead. This can occur if, for example, a protocol upgrade is occurring.

    As with decode, decodeLast is invoked in a loop. This allows the same simplification as decode allows: when a message is completely parsed, the decodeLast function can return .continue and be re-invoked from the top, rather than containing an internal loop.

    Note that the value of seenEOF may change between calls to decodeLast in some rare situations.

    Implementers Notes

    /// ByteToMessageHandler will turn your ByteToMessageDecoder into a ChannelInboundHandler. ByteToMessageHandler also solves a couple of tricky issues for you. Most importantly, in a ByteToMessageDecoder you do not need to worry about re-entrancy. Your code owns the passed-in ByteBuffer for the duration of the decode/decodeLast call and can modify it at will.

    If a custom frame decoder is required, then one needs to be careful when implementing one with ByteToMessageDecoder. Ensure there are enough bytes in the buffer for a complete frame by checking buffer.readableBytes. If there are not enough bytes for a complete frame, return without modifying the reader index to allow more bytes to arrive.

    To check for complete frames without modifying the reader index, use methods like buffer.getInteger. You MUST use the reader index when using methods like buffer.getInteger. For example calling buffer.getInteger(at: 0) is assuming the frame starts at the beginning of the buffer, which is not always the case. Use buffer.getInteger(at: buffer.readerIndex) instead.

    If you move the reader index forward, either manually or by using one of buffer.read* methods, you must ensure that you no longer need to see those bytes again as they will not be returned to you the next time decode is called. If you still need those bytes to come back, consider taking a local copy of buffer inside the function to perform your read operations on.

    The ByteBuffer passed in as buffer is a slice of a larger buffer owned by the ByteToMessageDecoder implementation. Some aspects of this buffer are preserved across calls to decode, meaning that any changes to those properties you make in your decode method will be reflected in the next call to decode. In particular, moving the reader index forward persists across calls. When your method returns, if the reader index has advanced, those bytes are considered consumed and will not be available in future calls to decode. Please note, however, that the numerical value of the readerIndex itself is not preserved, and may not be the same from one call to the next. Please do not rely on this numerical value: if you need to recall where a byte is relative to the readerIndex, use an offset rather than an absolute value.

    Using ByteToMessageDecoder

    To add a ByteToMessageDecoder to the ChannelPipeline use

    channel.pipeline.addHandler(ByteToMessageHandler(MyByteToMessageDecoder()))
    
    See more

    Declaration

    Swift

    public protocol ByteToMessageDecoder
  • Some ByteToMessageDecoders need to observe writes (which are outbound events). ByteToMessageDecoders which implement the WriteObservingByteToMessageDecoder protocol will be notified about every outbound write.

    WriteObservingByteToMessageDecoder may only observe a write and must not try to transform or block it in any way. After the write method returns the write will be forwarded to the next outbound handler.

    See more

    Declaration

    Swift

    public protocol WriteObservingByteToMessageDecoder : ByteToMessageDecoder
  • An EventLoop processes IO / tasks in an endless loop for Channels until it’s closed.

    Usually multiple Channels share the same EventLoop for processing IO / tasks and so share the same processing NIOThread. For a better understanding of how such an EventLoop works internally the following pseudo code may be helpful:

    while eventLoop.isOpen {
        /// Block until there is something to process for 1...n Channels
        let readyChannels = blockUntilIoOrTasksAreReady()
        /// Loop through all the Channels
        for channel in readyChannels {
            /// Process IO and / or tasks for the Channel.
            /// This may include things like:
            ///    - accept new connection
            ///    - connect to a remote host
            ///    - read from socket
            ///    - write to socket
            ///    - tasks that were submitted via EventLoop methods
            /// and others.
            processIoAndTasks(channel)
        }
    }
    

    Because an EventLoop may be shared between multiple Channels it’s important to NOT block while processing IO / tasks. This also includes long running computations which will have the same effect as blocking in this case.

    See more

    Declaration

    Swift

    public protocol EventLoop : EventLoopGroup
  • Provides an endless stream of EventLoops to use.

    See more

    Declaration

    Swift

    public protocol EventLoopGroup : AnyObject
  • A MulticastChannel is a Channel that supports IP multicast operations: that is, a channel that can join multicast groups.

    Note

    As with Channel, all operations on a MulticastChannel are thread-safe.
    See more

    Declaration

    Swift

    public protocol MulticastChannel : Channel
  • A protocol that covers an object that does DNS lookups.

    In general the rules for the resolver are relatively broad: there are no specific requirements on how it operates. However, the rest of the code assumes that it obeys RFC 6724, particularly section 6 on ordering returned addresses. That is, the IPv6 and IPv4 responses should be ordered by the destination address ordering rules from that RFC. This specification is widely implemented by getaddrinfo implementations, so any implementation based on getaddrinfo will work just fine. In the future, a custom resolver will need also to implement these sorting rules.

    See more

    Declaration

    Swift

    public protocol Resolver
  • This protocol defines an object, most commonly a Channel, that supports setting and getting socket options (via setsockopt/getsockopt or similar). It provides a strongly typed API that makes working with larger, less-common socket options easier than the ChannelOption API allows.

    The API is divided into two portions. For socket options that NIO has prior knowledge about, the API has strongly and safely typed APIs that only allow users to use the exact correct type for the socket option. This will ensure that the API is safe to use, and these are encouraged where possible.

    These safe APIs are built on top of an unsafe API that is also exposed to users as part of this protocol. The unsafe API is unsafe in the same way that UnsafePointer is: incorrect use of the API allows all kinds of memory-unsafe behaviour. This API is necessary for socket options that NIO does not have prior knowledge of, but wherever possible users are discouraged from using it.

    Relationship to SocketOption

    All Channel objects that implement this protocol should also support the SocketOption ChannelOption for simple socket options (those with C int values). These are the most common socket option types, and so this ChannelOption represents a convenient shorthand for using this protocol where the type allows, as well as avoiding the need to cast to this protocol.

    Note

    Like the Channel protocol, all methods in this protocol are thread-safe.
    See more

    Declaration

    Swift

    public protocol SocketOptionProvider