[Haskell-cafe] How to design an network client with user program.

Joey Adams joeyadams3.14159 at gmail.com
Tue Apr 16 04:49:29 CEST 2013


I've been struggling with a similar situation: a client and server that
communicate with binary-encoded messages, sending "heartbeats" (dummy
messages) every 30 seconds, and timing out the connection if no response is
received in 3 minutes.  The client sends data to the server, while also
listening for configuration changes from the server.  The connection needs
to interact with multiple threads on both sides.

See http://ofps.oreilly.com/titles/9781449335946/sec_conc-server.html (Parallel
and Concurrent Programming in Haskell, Chapter 9) for a thorough example of
using STM to write a simple networked application.  You may want to read
some of the previous chapters if you have trouble understanding this one.

If your program becomes an overwhelming tangle of threads, tackle it like
any other complexity: break your program into modules with simple
interfaces.  In particular, build your connection module in layers.  For
example, you could have a module that serializes messages:

    data Config = Config { host :: HostName, port :: PortNumber }

    data Connection = Connection
        { connBase :: Handle
        , connRecvState :: IORef ByteString
          -- ^ Leftover bytes from last 'recv'
        }

    connect :: Config -> IO Connection
    close :: Connection -> IO ()

    send :: Connection -> Request -> IO ()
    recv :: Connection -> IO (Maybe Response)

On top of that, timed messages:

    data Connection = Connection
        { connBase :: Base.Connection
        , connSendLock :: MVar SendState
        }

    data SendState = SendOpen | SendError SomeException

Here, both 'send' and a timer thread take the send lock.  If either fails,
it places 'SendError' in the MVar to prevent subsequent accesses.

MVar locking is pretty cheap: about 100 nanoseconds per withMVar, versus
several microseconds per network I/O operation.  Don't be afraid to stack
MVar locks if it makes your code easier to maintain.

@Michael Snoyman: The Connection example above is one case where resumable
conduits might be useful.  For Connection to use conduits, 'recv' and
'send' would have to feed conduits incrementally.  Otherwise, it'd need a
different interface (e.g. return Source and Sink).

I wrote a conduit-resumable package [1], but did not release it because of
a semantic issue regarding leftovers: if a conduit has leftovers, should
they go back to the source, or stay with the conduit?  conduit-resumable
does the former, but we'd want the latter here.

 [1]: https://github.com/joeyadams/hs-conduit-resumable
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20130415/5f7903a1/attachment.htm>


More information about the Haskell-Cafe mailing list