使用TcpClient进行更快速的通信方式?

10

我正在使用C#编写一个客户端/服务器应用程序,目前一切都很顺利。现在,一切工作正常,而且非常健壮。我的问题是在发送数据包时遇到一些延迟。

在客户端,我正在执行以下操作:

NetworkStream ns = tcpClient.GetStream();

// Send packet

byte[] sizePacket = BitConverter.GetBytes(request.Length);
byte[] requestWithHeader = new byte[sizePacket.Length + request.Length];
sizePacket.CopyTo(requestWithHeader, 0);
request.CopyTo(requestWithHeader, sizePacket.Length);

ns.Write(requestWithHeader, 0, requestWithHeader.Length);

// Receive response

ns.Read(sizePacket, 0, sizePacket.Length);
int responseLength = BitConverter.ToInt32(sizePacket, 0);
byte[] response = new byte[responseLength];

int bytesReceived = 0;
while (bytesReceived < responseLength)
{
  int bytesRead = ns.Read(response, bytesReceived, responseLength - bytesReceived);
  bytesReceived += bytesRead;
}

(省略了一些异常捕获等内容) 服务器相反,即它在 NetworkStream.Read() 上阻塞,直到它有一个完整的请求,然后处理它并使用 Write() 发送响应。

Write()/Read() 的原始速度不是问题(即发送大数据包很快),但连续发送几个小数据包而不关闭连接可能会非常慢(延迟50-100毫秒)。奇怪的是,这些延迟在具有典型ping时间<1ms 的局域网连接上显示出来,但如果服务器在本地主机上运行,则不会发生这种情况,即使ping时间实际上是相同的(至少差异不应该达到100毫秒数量级)。如果我在每个数据包上重新打开连接,导致大量握手,则这对我来说是有意义的,但我并没有这样做。好像服务器进入等待状态会使它与客户端不同步,然后当它重新建立了基本上是失去的连接时,它就会有点蹒跚。

那么,我做错了吗?是否有一种方法可以保持TcpServer和TcpClient之间的连接同步,以便服务器总是准备接收数据? (反之亦然:有时从客户端处理请求需要几毫秒钟,然后客户端似乎在阻塞Read()之后需要一些时间才能准备好接收来自服务器的响应。)


1
你为什么不使用WCF呢?这个框架已经内置了你所需要的一切,甚至更多。 - Jay
你试过使用StreamReader吗?只是出于好奇。 - Conrad Frix
3
相反地,在我看来,WCF非常臃肿,大多数人只使用其中的一小部分。原始套接字通常很好,并且通常更有效率。 - Marc Gravell
我快速浏览了一下WCF,虽然我可以看出它在大型互联企业应用程序的巨大网格中可能会有用,但我真正需要的只是在网络上两台计算机之间发送简单的字节数组的能力。.NET具有许多非常好的功能(序列化、DeflateStream、XML等),这些功能扩展了它,使其对我的目的完全足够。无论如何,我现在已经解决了它,请参见下文。 - ReturningTarzan
Jay:我试过StreamReader,顺便说一下,没有什么区别。但还是谢谢。 - ReturningTarzan
@ReturningTarzan:我将你的更新移动到了你的答案中,并点赞了问题和答案。不错的侦探工作。 - user7116
1个回答

8

事实证明,我的服务器和客户端并不是完全对称的。我注意到了,但我认为这并不重要。显然,这是一个很大的问题。具体来说,服务器做了以下操作:

ns.Write(sizePacket, 0, sizePacket.Length);
ns.Write(response, 0, response.Length);

我将其改为这样:

// ... concatenate sizePacket and response into one array, same as client code above
ns.Write(responseWithHeader, 0, responseWithHeader.Length);

现在延迟完全消失了,或者至少无法以毫秒计量。所以这就是一个大约100倍的加速。 \o/
仍然很奇怪,因为它将完全相同的数据写入套接字,所以我猜套接字在写操作期间接收到了一些秘密元数据,然后以某种方式与远程套接字通信,后者可能会将其解释为休息的机会。要么如此,要么第一次写操作将套接字置于接收模式,导致在还没有接收任何内容时被要求再次发送时出现问题。
我想暗示是,你在固定大小的块中找到的所有示例代码(通常由描述要遵循的数据包大小的单个int前缀)都没有提到这样做会有非常严重的性能损失。

10
仅为解释延迟,我认为你在Windows套接字中触发了Nagle算法:小数据包将被放入缓冲区以提高网络整体性能。每个单独的数据包可能会在到达网络对等点之前看到200毫秒的延迟:http://support.microsoft.com/kb/214397/en-us http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.nodelay.aspx - Stephan B
1
好的,谢谢你提供这些信息,很有启发性。我希望微软能更好地将像MSDN这样的优秀文章链接起来。或者也许我只需要知道在哪里寻找。无论如何,我现在感觉更聪明了。仍然有点奇怪的是,我测量到了约100毫秒的延迟,与Winsock缓冲区的200毫秒超时完全不同,但也许我测量方法不对。 - ReturningTarzan
4
除了微软选择的算法之外,这并不是针对微软特定的问题。任何生产中的TCP堆栈都会倾向于发送大数据包以减少开销。因此,当您向TCP流写入少量数据时,它将被缓冲,以期望会发生其他写入。优秀的TCP堆栈算法努力平衡低延迟(通过立即发送)和高带宽(通过使用尽可能大的数据包大小)。大多数堆栈都提供了一种手动刷新命令缓冲区或为更好的性能在特定套接字上指定无延迟的方法。 - David
1
如果有人需要帮助:TcpClient 中有一个名为 NoDelay 的属性,您可以使用它来“修复”此问题。 - HDD
嗨@ReturningTarzan,我每100毫秒发送15字节的数据,在服务器端已经超时了800毫秒。我能够发送数据到3-5分钟,然后突然就超时了。你能帮我看看问题出在哪里吗? - Android Developer World

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接