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

Posted

技术标签:

【中文标题】使用 TcpClient 进行通信的更快方式?【英文标题】:Faster way to communicate using TcpClient? 【发布时间】:2011-05-25 16:01:23 【问题描述】:

我正在用 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 时间

那么,我做错了吗?有没有办法让 TcpServer 和 TcpClient 之间的连接保持同步,以便服务器始终准备好接收数据? (反之亦然:有时处理来自客户端的请求需要几毫秒,然后客户端似乎还没有准备好接收来自服务器的响应,直到它在 Read() 阻塞后有片刻时间醒来.)

【问题讨论】:

您不使用WCF的任何特殊原因?该框架包含您尝试做的所有事情,并且内置了更多内容。 只是出于好奇,您是否尝试过使用 StreamReader? 相反,IMO; WCF 非常臃肿,大多数人只使用其中的一小部分。原始套接字通常很好,而且通常效率更高。 我快速浏览了 WCF,虽然我可以看到它在互连的企业应用程序的巨大网格中如何有用,但我真的只是在能够在两个之间发送简单的字节数组之后网络上的计算机。 .NET 有很多很好的特性(序列化、DeflateStream、XML 等)可以扩展它,这对于我的目的来说已经绰绰有余了。 Anyeay 我现在修好了,见下文。 Jay:顺便说一句,我试过 StreamReader,没什么区别。但是谢谢。 【参考方案1】:

事实证明,我的服务器和客户端毕竟不是完全对称的。我注意到了,但我认为这根本不重要。显然这是一笔大买卖。具体来说,服务器是这样做的:

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 ,与我的第一个版本相同),没有提到这样做会导致非常严重的性能损失。

【讨论】:

只是为了解释延迟,我相信您在 Windows 套接字中使用了 Nagles 算法:将小数据包放入缓冲区以提高网络整体性能。每个单个数据包在到达网络对等方之前可能会出现 200 毫秒的延迟:support.microsoft.com/kb/214397/en-usmsdn.microsoft.com/en-us/library/… 嗯,谢谢你,它提供了很多信息。我希望微软能更好地链接到 MSDN 中的优秀文章。或者也许我只需要知道在哪里看。不管怎样,我现在感觉更聪明了。我测量到大约 100 毫秒的延迟仍然有点奇怪,与 Winsock 缓冲区的 200 毫秒超时完全不同,但也许我测量错了。 这并不是微软特有的(除了他们自己选择的算法)。任何生产 TCP 堆栈都将倾向于发送大数据包而不是小数据包以减少开销。因此,当您将少量数据写入 TCP 流时,它将被缓冲,以期发生其他一些写入。一个好的 TCP 堆栈算法力求平衡低延迟(通过立即发送)和高带宽(通过使用可能的最大数据包大小)。大多数堆栈都提供了一种方法,可以根据命令手动刷新缓冲区,或者为了获得更好的性能,在特定套接字上指定不延迟。 如果这对任何人都有帮助:TcpClient 中有一个名为 NoDelay 的属性,您可以使用它来“修复”此问题

以上是关于使用 TcpClient 进行通信的更快方式?的主要内容,如果未能解决你的问题,请参考以下文章

C#使用TcpListener和TcpClient实现简单通信

区分 TcpClient 和 WebSocket?

TcpClient 与服务器通信以保持 C# 中的活动连接?

TCPListener和TCPClient之间的通信代码

TcpClient 的 NetworkStream 啥时候完成一次读操作?

TcpClient/NetworkStream 在同一个实例上同时读写