【问题标题】:Haskell server does not reply to clientHaskell 服务器不回复客户端
【发布时间】:2019-05-16 22:48:10
【问题描述】:

我尝试在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)

问题在于,在我手动关闭客户端之前,服务器不会进入 yield 阶段,因此永远不会响应它。我注意到从客户端删除并发并只保留它向服务器发送数据的部分,一切正常。

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

【问题讨论】:

    标签: haskell conduit network-conduit


    【解决方案1】:

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

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

    在您的情况下,修复需要在客户端进行。一旦conduit 完成通过它发送input.txt 的内容,您需要在套接字上调用shutdownShutdownSend

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

    {-# LANGUAGE OverloadedStrings #-}
    
    import Control.Concurrent.Async (concurrently)
    import Data.Functor (void)
    import Data.Foldable (traverse_)
    
    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 = traverse_ (`shutdown` ShutdownSend) . appRawSocket
    

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

    【讨论】:

    • 非常感谢您的澄清。我还有一个问题,如果不是从文件中读取,而是从用户的输入中读取呢?用runConduit $ stdinC 替换runConduitRes $ sourceFile "input.txt" 还不够吗?我和以前有同样的问题......
    • @mennetorelli 是的,只需在程序中进行切换就足够了。请记住,使用标准输入时,您需要在完成输入时按 Ctrl+D(或在 Windows 上按 Ctrl+Z),否则它会认为您可能还有更多内容要输入。 (或者,您可以只读取一定数量的行而不是所有标准输入,然后不必这样做。)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 2013-10-29
    • 1970-01-01
    • 2020-08-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多