从Socket解析ByteString失败

时间:2015-05-21 13:59:02

标签: sockets haskell

我们正在Haskell (HMB)中编写消息代理。因此,在从套接字(Data.Binary)收到消息后,必须解析消息(Network.Socket)。到目前为止,我们一直在测试环回(localhost) - 用于生成和解析消息。这很安静。如果我们通过从另一台机器生成消息进行基准测试,那么我们就会遇到问题:突然,解析器没有足够的字节来解析。

每条消息的前4个字节定义消息的长度,从而描述要解析的消息。如上所述,我们使用Data.Binary进行解析 - 所以这是懒惰的。出于测试目的,我们使用谷物库将前4个字节的解析切换为严格。这同样的问题。我们现在甚至试图用谷物完全解析请求,问题也仍然存在。

在代码中,您会看到我们正在进行线程化。但是,我们也尝试了没有通道(单线程设置),但这也没有解决问题。

这是代码(Thread1)的一部分,其中接收的字节被写入要进一步使用/解析的通道。 (如上所述,如果省略通道并直接解析输入,则没有任何变化):

runConnection :: (Socket, SockAddr) -> RequestChan -> Bool -> IO()
runConnection conn chan False = return ()
runConnection conn chan True = do
    r <- recvFromSock conn
    case (r) of
        Left e -> do
            handleSocketError conn e
            runConnection conn chan False
        Right input -> do
            threadDelay 5000 -- THIS FIXES THE PROBLEM!?
            writeToReqChan conn chan input
            runConnection conn chan True

以下是输入正在解析的部分(Thread2):

runApiHandler :: RequestChan -> ResponseChan -> IO()
runApiHandler rqChan rsChan = do
  (conn, req) <- readChan rqChan
  case readRequest req of -- readRequest IS THE PARSER
    Left (bs, bo, e) -> handleHandlerError conn $ ParseRequestError e
    Right (bs, bo, rm) -> do
      res <- handleRequest rm
      case res  of
        Left e -> handleHandlerError conn e
        Right bs -> writeToResChan conn rsChan bs
  runApiHandler rqChan rsChan

现在我想通了,如果解析过程有点延迟(参见第一个代码块中的threadDelay),一切正常。这基本上意味着,解析器不会等待从套接字接收的字节。

为什么?为什么解析器不等待套接字有足够的字节?我们的设置是否存在一般错误?

3 个答案:

答案 0 :(得分:5)

我敢打赌,问题与解析器无关,而是由于UNIX套接字的阻塞语义。

虽然是环回 接口可能会直接将数据包从发送方传递到接收方, 以太网接口可能需要拆分数据包以适应最大值 链路的传输单元(MTU)。这称为数据包碎片。

len recv系统调用的参数仅仅是 接收长度的上限(例如目标缓冲区的大小);该 呼叫可能产生的数据少于您的要求。引用联机帮助页,

  

如果套接字上没有可用消息,则接收呼叫等待a   消息到达,除非套接字是非阻塞的(参见fcntl(2)),其中   case返回值-1,外部变量errno设置为   EAGAIN或EWOULDBLOCK。接收呼叫通常会返回任何数据   可用,达到要求的金额,而不是等待收到   要求的全额。

因此,您可能需要多次recv次调用才能检索整个数据包。如果您延迟recv,您的示例将起作用,因为操作系统可以重新组合原始数据包,因为所有片段在请求时已到达。

正如meiersi指出的那样,在Haskell世界中已经开发了各种流I / O库来解决这个问题。其中包括pipesconduitio-streams等。根据您的目标,这可能是处理此问题的一种自然方式。

答案 1 :(得分:2)

您可能希望尝试the socket support in conduit-extrabinary-conduit结合使用,以正确处理分块流式传输的解析,这是由于bgamari指出的原因而发生的。

答案 2 :(得分:1)

首先,认为自己很幸运能够观察到这一点。在许多平台上,也许只有千分之一的数据包出现这种行为,导致很多这样的(对不起)糟糕的网络代码很少和随机地失败。

问题是您在数据准备好之前就开始处理了。而不是threadDelay(它引入了永久延迟,并且可能在所有情况下都不够长),解决方案是确保在开始处理之前至少要处理一个项目/消息/数据包。第一个32位字包含长度的协议是完美的。读取数据,直到至少有4个字节(长度)。然后读取数据,直到获得所需的字节数。如果对recvFromSock的任何调用返回的值小于所需的数量,请再次调用它以获得更多。记得还要处理0字节的情况,这意味着对方关闭了连接。

我已经为类似的协议实现了这个(SMPP,数据包也从长度开始)并且它完美地工作。