Сервер Haskell не отвечает клиенту - PullRequest
3 голосов
/ 17 мая 2019

Я попытался создать простую клиент-серверную программу, следуя этому руководству о библиотеке сетевого канала 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 Ответ

3 голосов
/ 17 мая 2019

У вас есть тупик: клиент ожидает ответа сервера, прежде чем он закроет соединение, но сервер не знает, что клиент завершил отправку данных и ожидает большего.Это в основном проблема, описанная в https://cr.yp.to/tcpip/twofd.html:

Когда программа generate-data завершает работу, тот же самый fd все еще открыт в программе consume-data, поэтому ядро ​​понятия не имеет, что оно должно отправитьFIN.

В вашем случае исправление должно идти на стороне клиента.Вам нужно вызвать shutdown с ShutdownSend на сокете, как только conduit завершит отправку содержимого input.txt через него.

Вот одинспособ сделать это (я не уверен, есть ли более хороший):

{-# 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

Примечание: вам не нужен параллелизм в клиенте в этом случае, так как никогда ничего не будетчитать с сервера, пока вы не закончите запись на сервер.Вы могли бы просто читать после записи и выключения.

...