Haskell 服务器没有回复客户端
Haskell server does not reply to client
我尝试根据 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)
问题是在我手动关闭客户端之前服务器不会进入让步阶段,因此永远不会响应它。我注意到从客户端删除并发并只保留它向服务器发送数据的部分,一切正常。
那么,如何在不中断流程的情况下保留客户端的接收部分?
您遇到了死锁:客户端在关闭连接之前正在等待服务器响应,但服务器不知道客户端已发送完数据并正在等待更多数据。这基本上就是 https://cr.yp.to/tcpip/twofd.html:
中描述的问题
When the generate-data
program finishes, the same fd is still open in the consume-data
program, so the kernel has no idea that it should send a FIN.
对于您的情况,修复需要在客户端进行。一旦 conduit
通过它发送 input.txt 的内容,您需要在套接字上调用 shutdown
with ShutdownSend
。
这是一种方法(我不确定是否有更好的方法):
{-# 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
旁注:在这种情况下,您实际上并不需要客户端的并发性,因为在您完成对服务器的写入之前,永远不会有任何内容可从服务器读取。您可以在写入和关机后进行读取。
我尝试根据 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)
问题是在我手动关闭客户端之前服务器不会进入让步阶段,因此永远不会响应它。我注意到从客户端删除并发并只保留它向服务器发送数据的部分,一切正常。
那么,如何在不中断流程的情况下保留客户端的接收部分?
您遇到了死锁:客户端在关闭连接之前正在等待服务器响应,但服务器不知道客户端已发送完数据并正在等待更多数据。这基本上就是 https://cr.yp.to/tcpip/twofd.html:
中描述的问题When the
generate-data
program finishes, the same fd is still open in theconsume-data
program, so the kernel has no idea that it should send a FIN.
对于您的情况,修复需要在客户端进行。一旦 conduit
通过它发送 input.txt 的内容,您需要在套接字上调用 shutdown
with ShutdownSend
。
这是一种方法(我不确定是否有更好的方法):
{-# 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
旁注:在这种情况下,您实际上并不需要客户端的并发性,因为在您完成对服务器的写入之前,永远不会有任何内容可从服务器读取。您可以在写入和关机后进行读取。