我正在为Java游戏制作自己的自定义服务器软件(游戏和原始服务器软件是用Java编写的)。没有任何协议文档可用,因此我不得不使用Wireshark读取数据包。
当客户端连接服务器时,会以Gzip格式向其发送级别文件。在发送级别的大约94个数据包中,我的服务器使用ArrayIndexOutOfBoundsException崩溃客户端。根据原始服务器的捕获文件,它会在该点发送TCP窗口更新。什么是TCP窗口更新,以及如何使用SocketChannel发送一个?
答案 0 :(得分:57)
TCP窗口用于连接上的对等体之间的流控制。对于每个ACK数据包,主机将发送“窗口大小”字段。该字段表示主机在满之前可以接收的数据字节数。发件人不应发送超过该数量的数据。
如果客户端没有足够快地接收数据,窗口可能会变满。换句话说,当应用程序关闭除了从其套接字读取之外的其他操作时,TCP缓冲区可以填满。当发生这种情况时,客户端将发送一个设置了“窗口已满”位的ACK数据包。此时,服务器应该停止发送数据。发送到具有完整窗口的计算机的任何数据包将不被确认。 (这会导致行为不端的发送方重新发送。行为良好的发送方只会缓冲传出数据。如果发送方的缓冲区也填满,那么发送应用程序会在尝试向套接字写入更多数据时阻止!)
这是一个TCP停顿。它可能由于很多原因而发生,但最终它只是意味着发送方的传输速度比接收方正在读取的速度快。
一旦接收端的应用程序重新开始从套接字读取,它将消耗一些缓冲数据,从而释放一些空间。接收器然后将发送“窗口更新”分组以告知发送器它可以传输多少数据。发送方开始发送其缓冲数据,流量应正常流动。
当然,如果接收器一直很慢,你可能会反复失速。
我已经说过,好像发送方和接收方不同,但实际上,两个对等方都在与每个ACK数据包交换窗口更新,并且任何一方都可以填满其窗口。
总体消息是您不需要直接发送窗口更新数据包。欺骗一个人实际上是一个坏主意。
关于你看到的异常......它不太可能由窗口更新包引起或阻止。但是,如果客户端读取速度不够快,则可能会丢失数据。在您的服务器中,您应该检查Socket.write()调用的返回值。它可能小于您尝试写入的字节数。如果发送方的发送缓冲区已满,则会发生这种情况,这可能发生在TCP停顿期间。你可能会丢失字节。
例如,如果您尝试在每次写入调用时写入8192个字节,但其中一个调用返回5691,则需要在下次调用时发送剩余的2501个字节。否则,客户端将看不到该8K块的剩余部分,并且您的文件在客户端比在服务器端更短。
答案 1 :(得分:6)
这在TCP / IP堆栈中非常深入;在您的应用程序(服务器和客户端)中,您不必担心TCP窗口。错误必须是别的。
答案 2 :(得分:2)
TCP窗口更新与在发送方和接收方之间传递可用缓冲区大小有关。 ArrayIndexOutOfBoundsException不是可能的原因。最有可能的是,代码期望某种类型的数据没有得到(很可能在此之前它只是现在引用)。在没有看到代码和堆栈跟踪的情况下,很难再说些什么了。
答案 3 :(得分:2)
TCP WindowUpdate - 这表示该段是纯WindowUpdate段。当接收方的应用程序已消耗已从RX缓冲区接收的数据时,会发生WindowUpdate,导致TCP层向另一方发送WindowUpdate,以指示缓冲区中现在有更多可用空间。通常在TCP ZeroWindow条件发生后看到。一旦接收器上的应用程序从TCP缓冲区中检索数据,从而释放空间,接收方应通过发送一个广告当前窗口大小的TCP WindowUpdate通知发送方TCP ZeroWindow条件不再存在。
答案 4 :(得分:0)
您可以深入了解此网站http://www.tcpipguide.com/free/index.htm,了解有关TCP / IP的大量信息。
答案 5 :(得分:0)
答案 6 :(得分:0)
这通常只是一个触发器,而不是问题的原因。
例如,如果使用NIO选择器,则窗口更新可能会触发写入通道的唤醒。这反过来会触发代码中的错误逻辑。
获取堆栈跟踪,它将显示根本原因。