我们如何将Haskell数据结构序列化和反序列化为Bytestring?

时间:2019-01-09 17:08:58

标签: haskell networking tcp

我有一个用C ++编写的TCP Server,它希望以以下格式发出请求:

struct Header {
   int headerField1;
   int headerField2;
}

struct Request {
   Header header;
   char[14] uniqueID;
   char[12] password;
}

我想实现一个客户端以将此请求发送到Haskell中的服务器

我已经尝试过Data.Binary.encode,但无法解决问题。 我更困惑如何在Haskell中使用任意大小的类型。即char [12];

Haskell代码:

data Header = Header 
  {
     headerField1   :: Word32
  ,  headerField2   :: Word32
  } deriving (Generic)
instance Binary Header

data Request = Request 
  {
     header         :: Header
  ,  uniqueID       :: ByteString -- I am not sure which data type to use here.
  ,  password       :: ByteString -- Same as above, as length is defined 12 bytes which is arbitrary.
  } deriving (Generic)
instance Binary Request

我已经向数据解析器写入了一个自定义字节串,由于没有任意大小的类型,因此它对于标头非常有用

parseHeader = do
    Header <$> 
        getWord32le <*>
        getWord32le

我正在寻找一种Haskell方法来对定义为ByteString的数据包结构进行序列化和反序列化(反之亦然),以及一种创建任意大小的数据类型的方式-char [12]

1 个答案:

答案 0 :(得分:1)

要首先解决主要问题,可以使用getByteString(或getLazyByteString)解析已知长度的字节串。因此,Request的二进制解析器可能是:

parseRequest :: Get Request
parseRequest =
  Request
    <$> parseHeader
    <*> getByteString 14
    <*> getByteString 12

如果您还有一个序列化器,例如putRequest,则可以将其与解析器一起放在Binary实例中,从而可以方便地使用库的其他功能(但您不能不必)。

instance Binary Request where
  get = parseRequest
  put = putRequest

为避免混淆密码和ID,将它们包装成新类型似乎是一个好主意:

newtype UniqueID = MkUniqueID ByteString  -- length 14
newtype Password = MkPassword ByteString  -- length 12

在对它们执行操作时,请确保它们不会构造错误长度的值。然后,您可以在导出类型时隐藏构造函数,以使用户无法破坏这些不变式。

这些类型的解析器是您指定所需长度的地方:

parseUniqueID :: Get UniqueID
parseUniqueID = MkUniqueID <$> getByteString 14

parsePassword :: Get Password
parsePassword = MkPassword <$> getByteString 12

现在,这使Request的定义更具描述性,在Haskell代码中混合使用密码和ID的唯一方法是在序列化/反序列化中弄错顺序,因此这减少了在其他地方出错的可能性。

data Request = Request
  { header   :: Header
  , uniqueID :: UniqueID
  , password :: Password
  }

parseRequest :: Get Request
parseRequest =
  Request
    <$> parseHeader
    <*> parseUniqueID
    <*> parsePassword