【问题标题】:Game server in HaskellHaskell 中的游戏服务器
【发布时间】:2012-08-26 21:34:59
【问题描述】:

我在 Haskell 中使用 NetworkGloss 作为游戏服务器。它工作正常,除了客户端必须关闭服务器才能接收它发送的数据。我敢打赌这是一个懒惰的例子......

极简服务器:

import Network
import System.IO

main = do
    sock <- listenOn (PortNumber (fromIntegral 12345))
    loop sock

loop sock = do
    (hIn, _, _) <- accept sock
    str <- hGetContents hIn
    print str
    loop sock

极简客户端:

import Network
import System.IO
import Graphics.Gloss.Interface.IO.Game

main = playIO
    (InWindow "Test Multi" (500, 500) (500, 500))
    white
    60
    Nothing
    draw
    (\_ x -> return x)
    advance

draw Nothing = return blank
draw (Just x) = return (Text (show x))

advance _ Nothing = do
    hOut <- connectTo "000.000.0.0" (PortNumber (fromIntegral 12345))
    hSetBuffering hOut NoBuffering
    hPutStr hOut "Hello!"
    return (Just hOut)
advance _ x = return x

我启动服务器,等待 10 秒,然后启动客户端,等待 15 秒,看到服务器上没有任何反应,关闭客户端,看到“你好!”突然出现在服务器上。 我想要“你好!”在客户端运行时出现在advance 调用中,否则我无法制作多人游戏(呜咽)!


但是,如果我将客户端的代码更改为

main = loop Nothing
loop x = do
    x' <- advance 0 x
    getLine

服务器立即显示“你好!”客户正在等待我的输入。


按照另一个问题中的建议,我尝试使用爆炸模式和hClose

-- ...
!str <- hGetContents hIn
hClose hIn
-- ...

这使输出立即出现,而无需关闭客户端。那太棒了。但是,我打算使用字节串,因为我发送到服务器的数据是序列化的,所以我import qualified Data.ByteString as B并将hGetContents更改为B.hGetContents,这使得问题再次出现。


问题确实是懒惰。 hGetContents 懒惰地读取Handle 的所有内容,所以它只有在它关闭时才完成,当客户端中止连接时。相反,我使用了hGetLine,它在每次遇到\n 时返回内容,我将其用作end-of-message 标记。

【问题讨论】:

  • 这可能与 playIO 功能有关吗?当我删除绘图代码时,我能够让服务器回显客户端的消息,但是会有几秒钟的延迟。
  • 我也是这么想的。更准确地说,使用递归的客户端代码有这个问题,而我在问题中添加的代码使用getLine 工作正常。
  • 即使我将 draw 重写为 draw _ = return Blank 并等待 30 秒(同时写此消息:P),“你好!”直到我关闭才写。我用这种方式写了draw,这样我们就可以真正看到与服务器的连接成功了。
  • 这是重复的,请看:stackoverflow.com/questions/5373883/…
  • 你是对的。我编辑了问题。

标签: haskell network-programming lazy-evaluation


【解决方案1】:

我可能完全错了,但问题不是 hGetContents 吗?当然,这应该等到通过您的套接字发送的 整个 内容在下一行(打印...)开始之前到达。 hGetContents 设计 为您提供在套接字关闭之前发送的所有内容。像 hGetLine 这样的东西可以立即终止,您可以让套接字保持打开状态,以便稍后发送更多数据。然后,您的客户可以使用 hPutStrLn 而不是 hPutStr。

【讨论】:

  • 相关文档:hGetContents。 (顺便说一句。我知道这里使用了System.IOhGetContents,但它的文档不清楚。)
  • hPutStrLn 得到了相同的结果。这不是必需的,因为我使用了hSetBuffering hOut NoBuffering,它会添加一个我不需要的\n,因为我会使用encodedecode 当这工作时。
  • 我的主要观点是,您应该尝试用 服务器中的 hGetLine 替换 hGetContents。 (hPutStrLn 只是镜像 hGetLine。)。我相信服务器将停止等待,直到套接字关闭,但会逐行打印它接收到的内容。
  • 如果您不想使用'\n' 向您的服务器发出信号,它应该开始处理,我认为您需要使用 hPut 和 hGet,但我仍然认为 hGetContents 不是你的意思。
  • hGetContents 可能不合适。如果我使用 bang 模式并关闭句柄,那很好,但我刚刚发现我可以保留句柄并从客户端获取新数据。我应该想办法停止阅读; hGetLine 还不错。
【解决方案2】:

它默认为行缓冲输出,这就是为什么hPutStr(不提供行结尾)在刷新缓冲区之前不输出任何内容。有两种方法可以解决这个问题:

a) 在您想要刷新输出的任何时候手动调用hFlush stdout

b) 使用hSetBuffering 将缓冲设置为NoBuffering

所有这些功能都可以在 System.IO 模块中找到。

编辑:没关系,我刚刚看到你在客户端中做了什么。我很抱歉收回我的回答。

【讨论】:

  • 在客户端,我已经有hSetBuffering hOut NoBuffering。我试着把它也放在服务器中,在str &lt;- hGetContents hIn 之前,我把hFlush stdout 放在它之后,但它仍然是一样的。编辑:没关系;你让我发现了hFlush :)。
  • @L01man 实际上,尝试在服务器的main 中调用hSetBuffering stdout NoBuffering 看看会发生什么。
  • 没有任何改变。我尝试了另一个客户端程序,我很确定这是有罪的部分。我在原始帖子中添加了一些代码。
【解决方案3】:

您可能需要禁用algorithm Nagle。 试试这个代码:

import Network.Socket

setSocketOption sock NoDelay 1

【讨论】:

  • 可能与 OP 无关。但这让我克服了一个我已经坚持了两天的错误!谢谢指点。
猜你喜欢
  • 2012-06-16
  • 1970-01-01
  • 2011-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-06
相关资源
最近更新 更多