Haskell服务器未回复客户端

时间:2019-05-16 22:48:11

标签: haskell conduit network-conduit

我尝试根据this tutorial构建有关Haskell网络导管库的简单客户端服务器程序。

这是客户端,它同时向服务器发送文件并接收答案:

{-# LANGUAGE OverloadedStrings #-}

import Control.Concurrent.Async (concurrently)
import Data.Functor (void)

import Conduit
import Data.Conduit.Network

main = runTCPClient (clientSettings 4000 "localhost") $ \server ->
    void $ concurrently
        (runConduitRes $ sourceFile "input.txt" .| appSink server)
        (runConduit $ appSource server .| stdoutC)

这是服务器,它计算每个单词的出现并将结果发送回客户端:

{-# LANGUAGE OverloadedStrings #-}

import Data.ByteString.Char8 (pack)
import Data.Foldable (toList)
import Data.HashMap.Lazy (empty, insertWith)
import Data.Word8 (isAlphaNum)

import Conduit
import Data.Conduit.Network
import qualified Data.Conduit.Combinators as CC

main = runTCPServer (serverSettings 4000 "*") $ \appData -> do
    hashMap <- runConduit $ appSource appData 
        .| CC.splitOnUnboundedE (not . isAlphaNum)
        .| foldMC insertInHashMap empty
    runConduit $ yield (pack $ show $ toList hashMap)
        .| iterMC print
        .| appSink appData

insertInHashMap x v = do
    return (insertWith (+) v 1 x)

问题在于,直到我手动关闭客户端,服务器才进入良品阶段,因此从不对其做出回应。我注意到,从客户端删除并发并只保留它向服务器发送数据的部分,一切正常。

那么,如何在不中断流程的情况下保留客户端的接收部分?

1 个答案:

答案 0 :(得分:3)

您有一个死锁:客户端在关闭连接之前正在等待服务器响应,但是服务器不知道客户端已完成发送数据并且正在等待更多。这基本上是https://cr.yp.to/tcpip/twofd.html中描述的问题:

  

generate-data程序完成后,consume-data程序中仍打开了相同的fd,因此内核不知道应该发送FIN。

对于您而言,此修复程序需要在客户端进行。 conduit完成在其上发送input.txt的内容后,您需要在套接字上用shutdown调用ShutdownSend

这是这样做的一种方法(我不确定是否有更好的方法):

{-# LANGUAGE OverloadedStrings #-}

import Control.Concurrent.Async (concurrently)
import Data.Functor (void)

import Conduit
import Data.Conduit.Network

import Data.Streaming.Network (appRawSocket)
import Network.Socket (shutdown, ShutdownCmd(..))

main = runTCPClient (clientSettings 4000 "localhost") $ \server ->
    void $ concurrently
        ((runConduitRes $ sourceFile "input.txt" .| appSink server) >> doneWriting server)
        (runConduit $ appSource server .| stdoutC)

doneWriting = maybe (pure ()) (`shutdown` ShutdownSend) . appRawSocket

侧面说明:在这种情况下,您实际上并不需要客户端中的并发,因为在完成对服务器的写入之前,永远不会从服务器读取任何内容。您可以在写入和关闭后进行阅读。