On this page we describe the TCP Transport as an example for developers who wish to write their own instantiations of the Transport layer. The purpose of any such instantiation is to provide a function
For instance, the TCP transport offers
This could be the only function that
Network.Transport.TCP exports (the only reason it exports more is to provide an API for unit tests for the TCP transport, some of which work at a lower level). Your implementation will now be guided by the
Network.Transport API. In particular, you will need to implement
newEndPoint, which in turn will require you to implement
The following picture is a schematic overview of how the Network.Transport concepts map down to their TCP counterparts.
Transportinstances. At the top we have two Transport instances on the same TCP node 198.51.100.1. These could be as part of the same Haskell process or, perhaps more typically, in separate processes. Different Transport instances on the same host must get a different port number.
EndPoints. A Transport can have multiple EndPoints (or none). EndPointAddresses in the TCP transport are triplets
TCP host:TCP port:endpoint ID.
Chanon which all endpoint
Events are posted.
We briefly discuss the implementation of the most important functions in the Transport API.
When a TCP transport is created at host 188.8.131.52, port 1080,
createTransport sets up a socket, binds it to 184.108.40.206:1080, and then spawns a thread to listen for incoming requests. This thread will handle the connection requests for all endpoints on this TCP node: individual endpoints do not set up their own listening sockets.
This is the only set up that
Network.Transport.TCP.createTransport needs to do.
In the TCP transport the set up of new endpoints is straightforward. We only need to create a new
Chan on which we will output all the events and allocate a new ID for the endpoint. Now
receive is just
address is the triplet
Consider the situation shown in the diagram above, and suppose that endpoint 198.51.100.1:1081:0 (lets call it A) wants to connect to 198.51.100.2:1080:1 (B). Since there is no TCP connection between these two endpoints yet we must set one up.
connectto A we know that a TCP connection to A is already available.
ConnectionRequestAcceptedand spawn a thread to listen for incoming messages on the newly created TCP connection.
At this point there is a TCP connection between A and B but not yet a Network.Transport connection; at this point, however, the procedure is the same for all connection requests from A to B (as well as as from B to A):
RequestConnectionIdmessage to B across the existing TCP connection.
ConnectionOpenedon B’s endpoint.
A complication arises when A and B simultaneously attempt to connect each other and no TCP connection has yet been set up. In this case two TCP connections will temporarily be created; B will accept the connection request from A, keeping the first TCP connection, but A will reply with
ConnectionRequestCrossed, denying the connection request from B, and then close the socket.
Note that connection IDs are locally unique. When A and B both connect to C, then C will receive two
ConnectionOpened events with IDs (say) 1024 and 1025. However, when A connects to B and C, then it is entirely possible that the connection ID that A receives from both B and C is identical. Connection IDs for outgoing connections are however not visible to application code.
To send a message from A to B the payload is given a Transport header consisting of the message length and the connection ID. When B receives the message it posts a
To close a connection, A just sends a
CloseConnection request to B, and B will post a
When there are no more Transport connections between two endpoints the TCP connection between them is torn down.
Actually, it’s not quite that simple, because A and B need to agree that the TCP connection is no longer required. A might think that the connection can be torn down, but there might be a
RequestConnectionIdmessage from B to A in transit in the network. A and B both do reference counting on the TCP connection. When A’s reference count for its connection to B reaches zero it will send a
CloseSocketrequest to B. When B receives it, and its refcount for its connection to A is also zero, it will close its socket and reply with a reciprocal
CloseSocketto A. If however B had already sent a
RequestConnectionIdto A it will simply ignore the
CloseSocketrequest, and when A receives the
RequestConnectionIdit simply forgets it ever sent the
In the TCP transport
createTransport needs to do some setup,
newEndPoint barely needs to do any at all, and
connect needs to set up a TCP connection when none yet exists to the destination endpoint. In Transport instances for connectionless protocols this balance of work will be different. For instance, for a UDP transport
createTransport may barely need to do any setup,
newEndPoint may set up a UDP socket for the endpoint, and
connect may only have to setup some internal datastructures and send a UDP message.
Network.Transport API functions should not throw any exceptions, but declare explicitly in their types what errors can be returned. This means that we are very explicit about which errors can occur, and moreover map Transport-specific errors (“socket unavailable”) to generic Transport errors (“insufficient resources”). A typical example is
connect with type:
TransportError is defined as
Exception instances so that application code has the option of
throwing returned errors. Here is a typical example of error handling in the TCP transport; it is an internal function that does the initial part of the TCP connection setup: create a new socket, and the remote endpoint ID we’re interested in and our own address, and then wait for and return the response:
Note how exceptions get mapped to
mapExceptionID, which is defined in
Moreover, the original exception is used as the
String part of the
TransportError. This means that application developers get transport-specific feedback, which is useful for debugging, not cannot take use this transport-specific information in their code, which would couple applications to tightly with one specific transport implementation.