我们正在Haskell (HMB)中编写消息代理.因此,在从socket(Network.Socket)接收消息后,必须解析消息(Data.Binary).到目前为止,我们一直在测试环回(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
Run Code Online (Sandbox Code Playgroud)
这是输入被解析的部分(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
Run Code Online (Sandbox Code Playgroud)
现在我想通了,如果解析过程有点延迟(参见第一个代码块中的threadDelay),一切正常.这基本上意味着,解析器不会等待从套接字接收的字节.
这是为什么?为什么解析器不等待套接字有足够的字节?我们的设置是否存在普遍错误?
我敢打赌,问题与解析器无关,而是由于UNIX套接字的阻塞语义.
虽然环回接口可能直接将数据包从发送方传递到接收方,但以太网接口可能需要拆分数据包以适应链路的最大传输单元(MTU).这称为数据包碎片.
系统调用的len
参数recv仅仅是接收长度的上限(例如目标缓冲区的大小); 呼叫可能产生的数据少于您的要求.引用联机帮助页,
如果套接字上没有消息可用,则接收调用等待消息到达,除非套接字是非阻塞的(请参阅fcntl(2)),在这种情况下返回值-1并将外部变量errno设置为EAGAIN或EWOULDBLOCK.接收呼叫通常会返回任何可用的数据,直到请求的数量,而不是等待收到所请求的全部金额.
因此,您可能需要多次recv调用才能检索整个数据包.如果您延迟recv操作系统可以重新组装原始数据包,则您的示例有效,因为所有片段在请求时已到达.
正如meiersi指出的那样,在Haskell世界中已经开发了各种流I/O库来解决这个问题.这些措施包括pipes,conduit,io-streams,和其他人.根据您的目标,这可能是处理此问题的自然方式.