This is a tutorial introduction to Network.Transport
. To follow along,
you should probably already be familiar with Control.Concurrent
; in
particular, the use of fork
and MVar
s. The code for the tutorial can
be downloaded as tutorial-server.hs
and tutorial-client.hs.
Network.Transport is a network abstraction layer which offers the following concepts:
EndPoint
s. These are heavyweight stateful objects.EndPoint
has an EndPointAddress
.EndPoint
to another using the EndPointAddress
of the remote end.EndPointAddress
can be serialised and sent over the network, where as EndPoint
s and connections cannot.EndPoint
s are unidirectional and lightweight.Connection
object that represents the sending end of the connection.EndPoint
are collected via a shared receive queue.EndPoint
s are notified of other Event
s such as new connections or broken connections.In this tutorial we will create a simple “echo” server. Whenever a client opens a new connection to the server, the server in turns opens a connection back to the client. All messages that the client sends to the server will echoed by the server back to the client.
Here is what it will look like. We can start the server on one host:
then start the client on another. The client opens a connection to the server,
sends “Hello world”, and prints all the Events
it receives:
The client receives three Event
s:
Note that the server prints its address (“192.168.1.108:8080:0”) to the
console when started and this must be passed explicitly as an argument to
the client. Peer discovery and related issues are outside the scope of
Network.Transport
.
We will start with the client (tutorial-client.hs), because it is simpler. We first need a bunch of imports:
The client will consist of a single main function. withSocketsDo may be needed for Windows platform with old versions of network library. For compatibility with older versions on Windows, it is good practice to always call withSocketsDo (it’s very cheap).
When we start the client we expect three command line arguments. Since the client will itself be a network endpoint, we need to know the IP address and port number to use for the client. Moreover, we need to know the endpoint address of the server (the server will print this address to the console when it is started):
Next we need to initialize the Network.Transport layer using createTransport
from Network.Transport.TCP
(in this tutorial we will use the TCP instance of
Network.Transport
). The type of createTransport
is:
(where N
is an alias for Network.Socket
). For the sake of this tutorial we
are going to ignore all error handling, so we are going to assume it will return
a Right
transport:
Next we need to create an EndPoint for the client. Again, we are going to ignore errors:
Now that we have an endpoint we can connect to the server, after we convert
the String
we got from getArgs
to an EndPointAddress
:
ReliableOrdered
means that the connection will be reliable (no messages will be
lost) and ordered (messages will arrive in order). For the case of the TCP transport
this makes no difference (all connections are reliable and ordered), but this may
not be true for other transports.
Sending on our new connection is very easy:
(send
takes as argument an array of ByteString
s).
Finally, we can close the connection:
Function receive
can be used to get the next event from an endpoint. To print the
first three events, we can do
Since we’re not expecting more than 3 events, we can now close the transport.
That’s it! Here is the entire client again:
The server (tutorial-server.hs) is slightly more complicated, but only slightly. As with the client, we start with a bunch of imports:
We will write the main function first:
This is very similar to the main
function for the client. We get the
hostname and port number that the server should use and create a transport
and an endpoint. Then we fork a thread to do the real work. We will write
echoServer
next; for now, suffices to note that echoServer
will signal
on the MVar serverDone
when it completes, so that the main thread knows
when to exit. Don’t worry about onCtrlC
for now; it does what the
name suggests.
The goal of echoServer
is simple: whenever somebody opens a connection to us,
open a connection to them; whenever somebody sends us a message, echo that message;
and whenever somebody closes their connection to us, we are going to close
our connection to them.
Event
is defined in Network.Transport
as
(there are few other events, which we are going to ignore). ConnectionId
s help us
distinguish messages sent on one connection from messages sent on another. In
echoServer
we are going to maintain a mapping from those ConnectionId
s to the
connections that we will use to reply:
Finally, when we receive the EndPointClosed
message we signal to the main
thread that we are doing and terminate. We will receive this message when the
main thread calls closeTransport
(that is, when the user presses Control-C).
This implements almost exactly what we described above. The only complication is that we want to avoid blocking the receive queue; so for every message that comes in we spawn a new thread to deal with it. Since is therefore possible that we receive the Received
event before an outgoing connection has been established, we map connection IDs to MVars containing connections.
Finally, we need to define onCtrlC
; p onCtrlC q
will run p
; if this is interrupted by Control-C we run q
and then try again:
In this tutorial, we have implemented a small echo client and server
to illustrate how the Network.Transport
abstraction layer can be used.
See the Network.Transport
wiki page for more details.