【问题标题】:Sending data to socket (after reading) only occurs when the socket connection ends仅在套接字连接结束时才向套接字发送数据(读取后)
【发布时间】:2018-07-15 22:05:50
【问题描述】:

我正在尝试用 Haskell 编写一个非常基本的网络服务器。这是我的代码:

{-# LANGUAGE OverloadedStrings #-}

import Network (withSocketsDo, listenOn, PortID(..))
import Network.Socket (Socket, accept, close, setSocketOption, SocketOption(..))
import Network.Socket.ByteString (send, sendAll, recv) 
import Control.Concurrent.Async (async)
import Control.Monad (forever)
import Data.ByteString.Char8 (unpack)
import Request

main = withSocketsDo $ do
  sock <- listenOn $ PortNumber 3000
  putStrLn "Listening on port 3000..."
  forever $ do
    (conn, _) <- accept sock
    async $ handleAccept conn

handleAccept :: Socket -> IO ()
handleAccept sock = do
  putStrLn $ "Connected!"
  rawReq <- recv sock 4096
  let req = parseRawRequest $ unpack rawReq -- returns Maybe Request
  putStrLn $ show req
  handleRequest sock req

handleRequest :: Socket -> Maybe Request -> IO ()
handleRequest sock Nothing = do
  putStrLn "Closing..."
handleRequest sock req = do
    sendAll sock "In handleRequest!" -- Doesn't appear until server is killed.

这是我预期会发生的:

  1. 启动服务器。
  2. "Listening on port 3000..." 打印在服务器端。
  3. curl localhost:3000
  4. "Connected!" 在服务器端打印。
  5. 请求在服务器端打印。
  6. "In handleRequest!" 已打印。

实际会发生什么:

  1. 启动服务器。
  2. "Listening on port 3000..." 打印在服务器端。
  3. curl localhost:3000
  4. "Connected!" 在服务器端打印。
  5. 请求在服务器端打印。
  6. 我耐心等待
  7. 我用CTRL+C杀死了服务器
  8. "In handleRequest!" 打印客户端。

我怀疑这与recv 中可能的懒惰有关,尽管我随后立即使用该值(我将原始请求解析为Request 类型),因此理论上应该对其进行评估。

如果我将sendAll sock "Yadda yadda 放在handleAccept 的末尾,一切正常。当我将此行为转移到一个新函数handleRequest 时,事情就变得很奇怪了。

有什么想法吗?我是 Haskell 的新手,所以我很感激任何关于这个问题的 cmets,或者我的代码。

干杯。

编辑:

这太奇怪了!我“修复”了它,但我不知道为什么会这样。

这是我杀死服务器后才出现的那一行:

handleRequest sock req = do
    sendAll sock "In handleRequest!" -- Doesn't appear until server is killed.

如果我在发送后有意关闭套接字,它会起作用:

handleRequest sock req = do
    sendAll sock "In handleRequest!" -- Now appears without killing the server
    close sock

所以它会在连接关闭时发送。这与之前的行为一致,因为连接会在服务器被终止时自动关闭。

现在是令人困惑的部分。如果我将其替换为:

handleRequest sock req = do
    sendAll sock "In handleRequest!\n" -- Works perfect

这可以在不关闭连接的情况下工作!它符合我的预期,只需添加一个换行符。为什么会出现这种情况?

到底是什么?这是我的终端的打印问题,而不是代码? (OSX iTerm2)

编辑 2:

被要求提供我的Request 模块的代码:

import Data.List (isInfixOf)
import Data.List.Split (splitOn)

data RequestType = GET | PUT
  deriving Show

data Request =
    Request {
    reqType :: RequestType,
        path    :: String,
        options :: [(String, String)]
  } deriving Show

-- Turn a raw HTTP request into a request
-- object.
parseRawRequest :: String -> Maybe Request
parseRawRequest rawReq =
  Request <$> parseRawRequestType rawReq
            <*> parseRawRequestPath rawReq
                    <*> parseRawRequestOps  rawReq

-- Turn an (entire) raw HTTP request into just
-- the request type.
parseRawRequestType :: String -> Maybe RequestType
parseRawRequestType rawReq = 
    case typ of
    "GET" -> Just GET
    "PUT" -> Just PUT
    _     -> Nothing
  where typ = (head . words . head . lines) rawReq

-- Turn an (entire) raw HTTP request into just
-- the path.
parseRawRequestPath :: String -> Maybe String
parseRawRequestPath = Just . (!! 1) . words . head . lines

-- Turn an (entire) raw HTTP request into just
-- a lookup table of their options.
parseRawRequestOps :: String -> Maybe [(String, String)]
parseRawRequestOps rawReq = Just [("One", "Two")] -- Test impl

【问题讨论】:

  • 我对 Hashell 的线索完全为零,但很明显它正在缓冲套接字输出,您需要在尝试读取响应之前刷新它。
  • 我认为可能是这样的,但我在文档中找不到任何内容:hackage.haskell.org/package/network-2.7.0.2/docs/…hackage.haskell.org/package/network-2.7.0.2/docs/…
  • 能否包含请求导入的代码或让我们知道包的名称?
  • @therewillbecode 完成,虽然如果问题出在哪里我会感到惊讶,但谁知道呢!
  • @haz 包含所有代码不仅仅是关于错误在哪里,而是让那些试图帮助运行测试和实验的人无需重新创建代码。跨度>

标签: sockets haskell networking


【解决方案1】:

我有一个答案和一个建议。

建议你在接受后关闭naggle算法:

setSocketOption conn NoDelay 1

答案是您的sendAll 正在发送数据,但 curl 没有打印它。例如,您可以使用 netcat 确认这一点。我注释掉了您的 Nothing 案例,这样无论我在 netcat 中输入什么,我都一定会收到 "In handleRequest!" 消息:

服务器:

% ghc so.hs && ./so
Listening on port 3000...
Connected!
Nothing

客户:

% nc localhost 3000
test                  ; My input, with a newline
In handleRequest!     ; Printed out, no newline

或者,您可以使用 curl 的 -N 选项来禁用缓冲。

%  curl -N localhost:3000
In handleRequest!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-12
    • 2012-11-29
    • 2011-06-12
    • 2017-01-17
    • 1970-01-01
    • 2020-11-06
    • 2018-09-13
    相关资源
    最近更新 更多