【问题标题】:Socket send requires delay for localhost target套接字发送需要本地主机目标的延迟
【发布时间】:2019-08-14 14:22:48
【问题描述】:

我目前正在开发一个 C# 服务器/客户端项目,遇到了一个奇怪的问题,我花了一段时间才解决,但对解决方案并不完全满意,或者更确切地说,我为什么需要它。基本上我发现向 localhost 目标发送超过 10kbytes 的 tcp 套接字消息需要 1ms 的时间延迟。

作为背景,我在一台机器上有一台服务器,许多客户端连接到该服务器并来回传递信息,这一切似乎都运行良好。我也有连接到服务器的本地客户端。我面临的问题是,当消息大小超过约 10kbytes 时,消息将无法通过。我只是注意到这一点,因为发送的大多数消息大小约为 1-2kbytes,但与服务器(本地主机)连接在同一台机器上的客户端更像是一个管理客户端,因此发送/接收更多数据。

真正的问题是服务器(发送方)从 C# 发送数据的命令返回 true / 成功,但是,接收方表示只有第一个块(如果我缓冲)会通过或根本没有.所以我最终打开了wireshark,发现即使发送调用成功完成,实际上也不会通过线路发送任何数据(记住这是在本地环回接口上)。

我尝试了如何发送数据并尝试所有不同的调用 (NetworkStream.Write / NetworkStream.WriteAsync / Socket.Send / Socket.SendAsync / Socket.BeginSend) 以及缓冲数据或一次性发送所有数据。直到我在循环中的发送调用之间设置一个时间延迟,然后一切都正常运行(我测试了高达 1.5GB 的数据流,没有任何问题),似乎没有任何区别。

我还发现任何低于 1 毫秒/10,000 个滴答声的延迟都会再次导致问题,我会通过这么多发送调用来工作,然后它们会再次停止。将TcpClient.NoDelay 设置为true 似乎也没有太大影响。

以下是我发送代码的一个示例,其中包含我尝试过的不同发送命令,它们都具有完全相同的行为。

// _client is an abstracted/based off a TcpClient object (the target)
byte[] dataBytes = Serializer.SerializeMessage(message); 
int    bytesSent = 0;

while (bytesSent < dataBytes.Length) {
   int bytesToSend = ((dataBytes.Length - bytesSent) < 8192) ? (dataBytes.Length - bytesSent) : 8192;
   //_client.Socket.Send(dataBytes, bytesSent, bytesToSend, SocketFlags.Partial);
   //_client.NetworkStream.Write(dataBytes, bytesSent, bytesToSend);
   //_client.Socket.BeginSend(dataBytes, bytesSent,bytesToSend,SocketFlags.None, ar => {int bytes = ((Socket)ar.AsyncState).EndSend(ar);}, _client.Socket);
   _ = _client.NetworkStream.WriteAsync(dataBytes, bytesSent, bytesToSend);

   bytesSent += bytesToSend;
   Thread.Sleep(TimeSpan.FromTicks(10000));
}

因此,虽然现在这工作正常,但我的问题是为什么甚至需要它,根据以前的经验,我总是发现如果您需要“延迟”还有其他问题。此外,没有延迟,没有任何错误,所有调用都报告他们成功发送了字节,但wireshark显示没有数据正在传输。我唯一的想法是库中的某些东西检测到它是本地目标并使用从我那里抽象出来的一些内部命名管道并且缓冲区在某处填满?

我已经尝试搜索任何类似的问题一个多星期了,但找不到任何明显的东西,所以任何见解都将不胜感激。

【问题讨论】:

    标签: c# sockets asynchronous networkstream


    【解决方案1】:

    根据以前的经验,我总是发现如果您需要“延迟”还有其他问题。

    没错。在这里,我怀疑您假设套接字上的 Read 将返回完整的消息。

    但 TCP/IP 是一种流式协议,不包含任何消息的概念。要成功地直接使用 TCP/IP,您必须定义一个“消息帧协议”,以便与远程主机通信需要多少字节。我没有看到你在消息之前写任何东西,所以我怀疑这是缺少的。

    许多人使用的简单解决方案是始终在消息的开头发送一个 4 字节整数,指示将要发送的字节数。然后阅读器可以执行一个 4 字节的读取,然后是一个读取循环,直到读取到预期的字节数。

    【讨论】:

    • 因此问题似乎与读取/接收端没有任何关系,因为根据wireshark,发送调用根本不会通过线路发送任何内容。但只是为了确认我的Serializer.SerializeMessage(message) 方法实际上将我的消息对象序列化为一个位流,该位流的头部包含一个 ID 和有效负载长度字节。 1000 个连接的客户端同时发送消息的压力测试工作正常。
    • 你能制作一个最小的复制品吗?我不希望 WireShark 可以看到 localhost 流量,因为它从不访问网络适配器。
    【解决方案2】:

    所以问题原来是计算机本身(或者更确切地说是我们的一些开发盒)上的问题,它影响了在 localhost 绑定上托管/连接的任何服务。如果主机是本地的,这包括许多网络服务,甚至包括 IISExpress 和 IIS。这导致任何超过约 10kbyte 大小的数据永远不会被发送/接收。

    经过几天尝试找出影响本地主机连接的原因,我们最终重建了我们的开发框,问题现在已经消失(尽管我们仍然不知道为什么/是什么导致了问题)。

    【讨论】:

      猜你喜欢
      • 2018-10-12
      • 2015-03-01
      • 2014-09-27
      • 2017-07-20
      • 1970-01-01
      • 1970-01-01
      • 2021-10-14
      • 1970-01-01
      • 2015-09-05
      相关资源
      最近更新 更多