我正在用Websockets在Haskell中编写一个游戏服务器。我需要为玩家 Action 计时,并用超时 Action 更新游戏,因为套接字在指定的时间内没有收到来自客户端的 Action 。
我遇到了一个零星的问题,有时执行超时会导致抛出ConnectionClosed异常。如果未捕获到此异常,则该异常会杀死线程的套接字并断开客户端的连接。
但是,即使我按照客户端套接字下面的代码捕获了异常,也仍然未连接。这让我感到困惑。
我在使用的websockets库的跟踪器上发现了类似的问题:
If a thread is killed while receiveData or similar is blocking, the TCP connection is closed and a ConnectionClosed exception is thrown. https://github.com/jaspervdj/websockets/issues/101
handleSocketMsg :: MsgHandlerConfig -> MsgIn -> IO ()
handleSocketMsg msgHandlerConfig@MsgHandlerConfig {..} msg = do
print $ "parsed msg: " ++ show msg
msgOutE <- runExceptT $ runReaderT (gameMsgHandler msg) msgHandlerConfig
either
(\err -> sendMsg clientConn $ ErrMsg err)
(handleNewGameState serverStateTVar)
msgOutE
-- This function processes msgs from authenticated clients
authenticatedMsgLoop :: MsgHandlerConfig -> IO ()
authenticatedMsgLoop msgHandlerConfig@MsgHandlerConfig {..}
= do
(catch
(forever $ do
msg <- WS.receiveData clientConn
print msg
let parsedMsg = parseMsgFromJSON msg
print parsedMsg
for_ parsedMsg $ handleSocketMsg msgHandlerConfig
return ())
(\e -> do
let err = show (e :: WS.ConnectionException)
print
("Warning: Exception occured in authenticatedMsgLoop for " ++
show username ++ ": " ++ err)
return ()))
-- takes a channel and if the player in the thread is the current player to act in the room
-- then if no valid game action is received within 30 secs then we run the Timeout action
-- against the game
tableReceiveMsgLoop :: TableName -> TChan MsgOut -> MsgHandlerConfig -> IO ()
tableReceiveMsgLoop tableName channel msgHandlerConfig@MsgHandlerConfig {..} =
forever $ do
print "tableReceiveMsgLoop"
dupChan <- atomically $ dupTChan channel
chanMsg <- atomically $ readTChan dupChan
sendMsg clientConn chanMsg
if True
then let timeoutMsg = GameMove tableName Timeout
timeoutDuration = 5000000 -- 5 seconds for player to act
in runTimedMsg timeoutDuration msgHandlerConfig tableName timeoutMsg
else return ()
catchE :: TableName -> WS.ConnectionException -> IO MsgIn
catchE tableName e = do
print e
return $ GameMove tableName Timeout
-- Forks a new thread to run the timeout in then updates the game state
-- with either the resulting timeout or player action
runTimedMsg :: Int -> MsgHandlerConfig -> TableName -> MsgIn -> IO ()
runTimedMsg duration msgHandlerConfig tableName timeoutMsg =
withAsync
(catch
(awaitTimedMsg duration msgHandlerConfig tableName timeoutMsg)
(catchE tableName)) $ \timedAction -> do
playerActionE <- waitCatch timedAction
let playerAction = fromRight timeoutMsg playerActionE
handleSocketMsg msgHandlerConfig playerAction
return ()
-- If the timeout occurs then we return the default msg
awaitTimedMsg :: Int -> MsgHandlerConfig -> TableName -> MsgIn -> IO MsgIn
awaitTimedMsg duration msgHandlerConfig@MsgHandlerConfig {..} tableName defaultMsg = do
maybeMsg <- timeout duration (WS.receiveData clientConn)
return $ maybe defaultMsg parseWithDefaultMsg maybeMsg
where
timeoutDuration = 5000000
parseWithDefaultMsg = (fromMaybe defaultMsg) . parseMsgFromJSON
最佳答案
通常,当网络库引发某种异常以指示连接已关闭时,这与您控件之外的事件有关。客户端由于某种原因断开连接-例如,也许他们关闭了计算机。
您将收到有关该事实的通知,因此您可以做出适当的 react ,例如,清理分配给该连接的服务器端资源,或者通过警告其他可能关心此问题的客户端。您无法重新打开客户端的计算机并强制他们继续使用您的服务。连接已关闭,您无能为力。
您描述的行为与您引用的文档完全匹配。 TCP连接已关闭,然后您收到异常,让您可以根据自己的喜好进行响应。您认为应该发生什么变化?
关于multithreading - 如何在Websocket中使用超时,同时又不影响应用程序安全性?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51236380/