我发布了一个在线(基于网格)的视频游戏,它使用TCP协议来确保服务器 - 客户端网络拓扑中的可靠通信.我的游戏工作得相当好,但遭遇的延迟高于预期(类似的TCP游戏似乎在将延迟保持在最低限度方面做得更好).
在调查时,我发现运行Microsoft Windows(而不是Mac OS X客户端)的客户端的延迟仅意外高.此外,我发现如果Windows客户端设置TcpAckFrequency=1
在注册表中并重新启动他们的计算机,他们的延迟就会变得正常.
看来我的网络设计没有考虑延迟确认:
不考虑延迟确认,Nagle算法和Winsock缓冲的交互的设计可以极大地影响性能.(http://support.microsoft.com/kb/214397)
但是,我发现几乎不可能在我的游戏(或任何游戏)中考虑延迟确认.根据MSDN,Microsoft TCP堆栈使用以下标准来决定何时在接收的数据包上发送一个ACK:
如果在延迟计时器到期(200ms)之前接收到第二数据包,则发送ACK.
如果在接收到第二数据包之前有数据要在与ACK相同的方向上发送并且延迟计时器到期,则ACK与数据段搭载并立即发送.
当延迟计时器到期(200ms)时,发送ACK.
(http://support.microsoft.com/kb/214397)
阅读本文,可以假设Microsoft TCP堆栈上延迟确认的解决方法如下:
禁用Nagle算法(TCP_NODELAY).
禁用套接字的发送缓冲区(SO_SNDBUF
= 0),以便send
可以预期调用发送数据包.
在呼叫时send
,如果不希望立即发送更多数据,则send
再次使用将被接收器丢弃的单字节数据进行呼叫.
利用这种方法,接收器将在与先前数据分组大致相同的时间接收第二数据分组.因此,ACK
应该立即从接收方发送到发送方(模拟TcpAckFrequency=1
注册表中的内容).
但是,从我的测试来看,这个改进的延迟只有注册表编辑的一半左右.我错过了什么?
答:我选择了TCP,因为我发送的每个数据包都需要到达(并按顺序); 如果丢失(或变得无序),则没有值得重传的数据包.只有当数据包可以被丢弃/无序时,UDP才能比TCP更快!
自Windows Vista起,TCP_NODELAY选项必须在调用之前设置connect
,或者(在服务器上)在调用之前设置listen
.如果你TCP_NODELAY
在调用后设置connect
,它实际上不会禁用Nagle算法,但GetSocketOption
会说Nagle已被禁用!这一切似乎都没有记载,并且与该主题教授的许多教程/文章相矛盾.
随着Nagle实际禁用,TCP延迟确认不再导致延迟.