检测 TCP 连接何时无法打开或终止
Detecting when TCP connection fails to open or is terminated
我正在通过双工句柄 h 读写 TCP 套接字。
在客户端:
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
connect sock $ addrAddress addr
h <- getSocketHandle conn
在服务器中:
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
setSocketOption sock ReuseAddr 1
withFdSocket sock setCloseOnExecIfNeeded
bind sock $ addrAddress addr
listen sock 1024
forever $ do
(conn, _) <- accept sock
h <- getSocketHandle conn
forkFinally (server h) (const $ hClose h)
其中:
getSocketHandle :: Socket -> IO Handle
getSocketHandle conn = do
h <- socketToHandle conn ReadWriteMode
hSetBinaryMode h True
hSetNewlineMode h NewlineMode {inputNL = CRLF, outputNL = CRLF}
hSetBuffering h LineBuffering
return h
当客户端和服务器都可用时,一切正常,但是当 TCP 连接终止时,例如因为客户端或服务器退出,没有任何反应 - hGetLine
/hPutStrLn
只是阻塞线程。
如何让它们抛出异常?
类似地,如果客户端无法连接(例如,因为服务器没有响应),connect
也会阻塞。
我怀疑我应该以某种方式在套接字上设置超时,但是尝试套接字选项并没有太大改变 - 我试过 SO_LINGER、SO_KEEPALIVE、SO_USER_TIMEOUT 没有系统似乎支持它(当我尝试获取它时它会阻塞),SO_RCVTIMEO / SO_SNDTIMEO 似乎不受 Network.Socket 支持 - 至少文档是这么说的...
对于 connect
/hPutStrLn
我可以使用 System.Timeout
中的 timeout
(这似乎是一个黑客,但它会起作用),但对于 hGetLine
/hGet
它不会工作 - 它只能等待用户输入,所以它应该只在连接终止时抛出异常。
我可能应该读一下网络编程 - 我希望能快速弄明白...非常感谢任何帮助。
编辑:看来我应该使用较低级别的 send
/sendAll
和 recv
,对代码中的行进行缓冲和切割(或逐字节读取 -可能它会按照 listen
中的设置进行缓冲,并将 recv
的 0 长度 returns 视为断开连接。有没有办法通过具有更高级别功能的句柄使其工作?
感谢K. A. Buhr的建议,我发现问题与handle/socket无关,确实抛出了异常。
有效的最小 client/server 设置:https://gist.github.com/epoberezkin/d6063c0d8f6e95d9e606652565113413
它基于 Network.Socket 包中的示例,但处理多个已解析的地址并使用 read/write 套接字的句柄。
真正的问题是由于在某些情况下不正确地处理异常,而不是像这样:
race (send h) (receive h) `finally` disconnected
我做到了
race (send h) (receive h) >> disconnected
我正在通过双工句柄 h 读写 TCP 套接字。
在客户端:
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
connect sock $ addrAddress addr
h <- getSocketHandle conn
在服务器中:
sock <- socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)
setSocketOption sock ReuseAddr 1
withFdSocket sock setCloseOnExecIfNeeded
bind sock $ addrAddress addr
listen sock 1024
forever $ do
(conn, _) <- accept sock
h <- getSocketHandle conn
forkFinally (server h) (const $ hClose h)
其中:
getSocketHandle :: Socket -> IO Handle
getSocketHandle conn = do
h <- socketToHandle conn ReadWriteMode
hSetBinaryMode h True
hSetNewlineMode h NewlineMode {inputNL = CRLF, outputNL = CRLF}
hSetBuffering h LineBuffering
return h
当客户端和服务器都可用时,一切正常,但是当 TCP 连接终止时,例如因为客户端或服务器退出,没有任何反应 - hGetLine
/hPutStrLn
只是阻塞线程。
如何让它们抛出异常?
类似地,如果客户端无法连接(例如,因为服务器没有响应),connect
也会阻塞。
我怀疑我应该以某种方式在套接字上设置超时,但是尝试套接字选项并没有太大改变 - 我试过 SO_LINGER、SO_KEEPALIVE、SO_USER_TIMEOUT 没有系统似乎支持它(当我尝试获取它时它会阻塞),SO_RCVTIMEO / SO_SNDTIMEO 似乎不受 Network.Socket 支持 - 至少文档是这么说的...
对于 connect
/hPutStrLn
我可以使用 System.Timeout
中的 timeout
(这似乎是一个黑客,但它会起作用),但对于 hGetLine
/hGet
它不会工作 - 它只能等待用户输入,所以它应该只在连接终止时抛出异常。
我可能应该读一下网络编程 - 我希望能快速弄明白...非常感谢任何帮助。
编辑:看来我应该使用较低级别的 send
/sendAll
和 recv
,对代码中的行进行缓冲和切割(或逐字节读取 -可能它会按照 listen
中的设置进行缓冲,并将 recv
的 0 长度 returns 视为断开连接。有没有办法通过具有更高级别功能的句柄使其工作?
感谢K. A. Buhr的建议,我发现问题与handle/socket无关,确实抛出了异常。
有效的最小 client/server 设置:https://gist.github.com/epoberezkin/d6063c0d8f6e95d9e606652565113413
它基于 Network.Socket 包中的示例,但处理多个已解析的地址并使用 read/write 套接字的句柄。
真正的问题是由于在某些情况下不正确地处理异常,而不是像这样:
race (send h) (receive h) `finally` disconnected
我做到了
race (send h) (receive h) >> disconnected