通过管道获取流媒体网络协议的结果

时间:2014-12-28 11:37:29

标签: haskell conduit network-conduit

我正在用管道实现一个简单的网络协议;协议是一个消息流,每个消息都以一个描述消息长度的uint32为前缀。 (然后消息数据有更多的内部结构,但这并不重要,因为我可以在解析它之前将整个消息读入内存,因为预期的消息大小很小)。该协议在两个方向上是相同的,客户端向服务器发送包含请求的消息,并且服务器返回包含响应的消息(没有操作的并发)。

我的想法是在两个简单的管道之上构建代码,从Message(我自己的类型描述各种可能的消息)到ByteString,反之亦然:

import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB

data Message = ...
parseMessage :: LB.ByteString -> Message
serializeMessage :: Message -> LB.ByteString

messageReceiver :: Conduit B.ByteString IO Message
messageReceiver = loop
  where
    loop = do
      lenBytes <- takeCE 4 =$= sinkLazy
      message <- takeCE (runGet getWord32be' lenBytes) =$= sinkLazy
      yield $ parseMessage message
      loop

messageSender :: Conduit Message IO B.ByteString
messageSender = concatMapC $ \message ->
  let messageBytes = serializeMessage message
      lenBytes = runPut $ putWord32be' (LB.length messageBytes)
  in map LB.toStrict [lenBytes, messageBytes]

到目前为止,这么好;或者至少是代码类型检查,虽然我确信这是一种更优雅的方式来编写它(特别是messageReceiver中的循环)。现在我想写一些东西来连接到服务器,发送请求,获得响应和断开连接。我写了这个:

runOneCommand request = do
  yield request
  response <- await
  return response

但是,我不确定如何实际将其连接到网络客户端源并以我获得&#34;响应&#34;价值回来。我试过这个:

appSource agent $$ messageReceiver =$= runOneCommand =$= messageSender =$= appSink agent

无法编译:

Couldn't match type `Data.Maybe.Maybe SSH.Agent.Message' with `()'
Expected type: conduit-1.2.3.1:Data.Conduit.Internal.Conduit.Conduit
                 SSH.Agent.Message ghc-prim:GHC.Types.IO SSH.Agent.Message
  Actual type: conduit-1.2.3.1:Data.Conduit.Internal.Conduit.ConduitM
                 SSH.Agent.Message
                 SSH.Agent.Message
                 ghc-prim:GHC.Types.IO
                 (Data.Maybe.Maybe SSH.Agent.Message)
In the return type of a call of `Main.runOneCommand'
In the first argument of `(conduit-1.2.3.1:Data.Conduit.Internal.Conduit.=$=)', namely
  `Main.runOneCommand SSH.Agent.RequestIdentities'
In the second argument of `(conduit-1.2.3.1:Data.Conduit.Internal.Conduit.=$=)', namely
  `Main.runOneCommand SSH.Agent.RequestIdentities
   conduit-1.2.3.1:Data.Conduit.Internal.Conduit.=$=
     Main.messageSender
     conduit-1.2.3.1:Data.Conduit.Internal.Conduit.=$=
       Data.Conduit.Network.appSink agent'
Couldn't match type `Data.Maybe.Maybe SSH.Agent.Message' with `()'
Expected type: conduit-1.2.3.1:Data.Conduit.Internal.Conduit.Conduit
                 SSH.Agent.Message ghc-prim:GHC.Types.IO SSH.Agent.Message
  Actual type: conduit-1.2.3.1:Data.Conduit.Internal.Conduit.ConduitM
                 SSH.Agent.Message
                 SSH.Agent.Message
                 ghc-prim:GHC.Types.IO
                 (Data.Maybe.Maybe SSH.Agent.Message)
In the return type of a call of `Main.runOneCommand'
In the first argument of `(conduit-1.2.3.1:Data.Conduit.Internal.Conduit.=$=)', namely
  `Main.runOneCommand SSH.Agent.RequestIdentities'
In the second argument of `(conduit-1.2.3.1:Data.Conduit.Internal.Conduit.=$=)', namely
  `Main.runOneCommand SSH.Agent.RequestIdentities
   conduit-1.2.3.1:Data.Conduit.Internal.Conduit.=$=
     Main.messageSender
     conduit-1.2.3.1:Data.Conduit.Internal.Conduit.=$=
       Data.Conduit.Network.appSink agent'

假设我在这里正确地遵循了这些类型,这是失败的,因为网络客户端的接收器需要返回类型(),而不是Message,所以我想我需要这里有一些其他形式的导管组合,但我不知道是什么。

1 个答案:

答案 0 :(得分:0)

发布此问题后,我发现https://stackoverflow.com/a/23925496/31490指出了我fuseUpstream的方向:

  response <- appSource agent $$ messageReceiver =$= runOneCommand RequestIdentities `fuseUpstream` messageSender `fuseUpstream` appSink agent

似乎关于该答案中fuseUpstream类型的可怕性的警告不再适用(因为管道类型被简化了?);比较:

(=$=) :: Monad m => Conduit a m b -> ConduitM b c m r -> ConduitM a c m r
fuseBoth :: Monad m => ConduitM a b m r1 -> ConduitM b c m r2 -> ConduitM a c m (r1, r2)
fuseUpstream :: Monad m => ConduitM a b m r -> Conduit b m c -> ConduitM a c m r