Low Level Networking - Haskell

Welcome to the Functional Programming Zulip Chat Archive. You can join the chat here.

James King

I've been struggling with getting network and binary to play nicely together and I think it might have something to do with strict vs. lazy bytestrings but I don't know for sure. I have written several type-correct programs that utterly fail at run-time: either the program consumes one byte then hangs and does nothing... or I get it to consume a number of bytes, hang and do nothing or I get it to wait and consume all of the input without parsing anything but that also fails as well.

I need to consume 9 bytes of input, exactly, and parse it. According to the protocol the first byte is a char and the other 8 are two unsigned 32 bit, big-endian integers. Simple enough, right?

I've been banging my head against this problem of buffering up 9 bytes of data to pass off to my binary parser. Is there a library I'm missing that makes writing parsers with binary and reading data from socket IO straight-forward?

Markus Läll

Is there any code you could share to test with?

James King

I think I'm missing a streaming abstraction. This minimal example:

{-# LANGUAGE OverloadedStrings #-}

module Protohacker.Servers.Proto2 where

import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString as B
import Network.Run.TCP
import Network.Socket
import Network.Socket.ByteString

start :: IO ()
start = do
  runTCPServer Nothing "3000" tickerServer

tickerServer :: Socket -> IO ()
tickerServer s = do
  msgs <- recvExactly s 9
  forM_ msgs $ \msg -> do
    print msg

-- | Buffer all content on the socket and return it to the caller in
-- `maxBytes` chunks.
recvExactly :: Socket -> Int -> IO [(ByteString, SockAddr)]
recvExactly socket maxBytes = undefined

Basically needs to buffer up 9 byte chunks using IO into a list. If you try to implement recvExactly using the recvFrom function from Network.Socket.ByteString you can tell it to receive 9 bytes but it will only guarantee that it will receive up to and including 9 bytes. So if you try to recur successive calls to build up a buffer you'll need a way to maintain that buffering state or otherwise lazily hand off chunks to the list somehow :thinking:

For example, on the first call recvFrom returns immediately with a single byte and we recur since it was not empty. On the recur we get all 9 bytes. If we're threading through a ByteString argument as our buffer and we combine the 1 from before and 9 bytes now we have 10 bytes, 9 of which we want to pass on to the caller and the rest we hold on to and recur again.

James King

I don't know how to do a lazy list embedded in IO though :sweat_smile: Although maybe this is already solved in something like conduit? :thinking:

James King

Especially since list is probably not the right approach if the input size from the client is large :sweat_smile: