仅在套接字连接结束时才将数据发送到套接字(读取后)

时间:2018-07-15 08:20:35

标签: sockets haskell networking

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

{-# 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的新手,因此,对于此问题或我的代码,我将不胜感激。

干杯。

编辑:

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

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

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

1 个答案:

答案 0 :(得分:3)

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

建议您在接受后关闭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!