【问题标题】:.Net Calling NetworkStream.Read() twice caused about 100ms overhead, what's the cause?.Net 调用 NetworkStream.Read() 两次导致大约 100ms 的开销,是什么原因?
【发布时间】:2019-01-30 00:50:35
【问题描述】:

我正在使用 TcpListener 和 TcpClient 编写一个简单的 Tcp 通信程序,.Net 4.7.1

我将自己的协议设计为: 对于每个“数据单元”,前4个字节是一个int,表示数据体的长度。一旦接收到一个完整的“数据单元”,数据体(作为一个字节[])被传递到上层。

我的读写函数是:

    public static byte[] ReadBytes(Stream SocketStream)
    {
        int numBytesToRead = 4, numBytesRead = 0, n;

        byte[] Length = new byte[4];
        do
        {
            n = SocketStream.Read(Length, numBytesRead, numBytesToRead);
            numBytesRead += n;
            numBytesToRead -= n;
        } while (numBytesToRead > 0 && n != 0);

        if (n == 0) return null; //network error

        if (!BitConverter.IsLittleEndian) Array.Reverse(Length);

        numBytesToRead = BitConverter.ToInt32(Length, 0); //get the data body length
        numBytesRead = 0;

        byte[] Data = new byte[numBytesToRead];
        do
        {
            n = SocketStream.Read(Data, numBytesRead, numBytesToRead);
            numBytesRead += n;
            numBytesToRead -= n;
        } while (numBytesToRead > 0 && n != 0);

        if (n == 0) return null; //network error

        return Data;
    }

    public static void SendBytes(Stream SocketStream, byte[] Data)
    {
        byte[] Length = BitConverter.GetBytes(Data.Length);
        if (!BitConverter.IsLittleEndian) Array.Reverse(Length);
        SocketStream.Write(Length, 0, Length.Length);
        SocketStream.Write(Data, 0, Data.Length);
        SocketStream.Flush();
    }

我还做了一个简单的 echo 程序来测试 RTT:

    private void EchoServer()
    {
        var Listener = new TcpListener(System.Net.IPAddress.Any, 23456);
        Listener.Start();
        var ClientSocket = Listener.AcceptTcpClient();
        var SW = new System.Diagnostics.Stopwatch();
        var S = ClientSocket.GetStream();
        var Data = new byte[1];
        Data[0] = 0x01;
        Thread.Sleep(2000);
        SW.Restart();
        SendBytes(S, Data); //send the PING signal
        ReadBytes(S); //this method blocks until signal received from client
        //System.Diagnostics.Debug.WriteLine("Ping: " + SW.ElapsedMilliseconds);
        Text = "Ping: " + SW.ElapsedMilliseconds;
        SW.Stop();
    }

    private void EchoClient()
    {
        var ClientSocket = new TcpClient();
        ClientSocket.Connect("serverIP.com", 23456);
        var S = ClientSocket.GetStream();
        var R = ReadBytes(S); //wait for PING signal from server
        SendBytes(S, R); //response immediately
    }

在“ReadBytes”中,我必须先从 NetworkStream 中读取 4 个字节,才能知道接下来要读取多少字节。所以总共我必须调用 NetworkStream.Read 两次,如上面的代码所示。

问题是:我发现调用它两次会导致大约 110 毫秒的 RTT。虽然调用一次(无论数据完整性如何)只有大约 2~10 毫秒(在第一个 do-while 循环之后立即放置一个“return Length;”,或者注释掉第一个 do-while 循环并硬编码数据长度,或在一次调用“Read”时尽可能多地阅读)。

如果我采用“一次调用尽可能多地读取”方法,它可能会导致数据“过度读取”,我必须编写更多行来处理过度读取的数据以组装下一个“数据单元”正确。

任何人都知道几乎 50 倍开销的原因是什么?

我从 Micrisoft 那里读到的

Microsoft 通过包含一个内置缓冲区提高了 .NET Framework 中所有流的性能。

所以即使我调用 .Read 两次,它也只是从内存中读取,对吗?

(如果您想测试代码,请在真实服务器上进行,并从您的家庭 PC 连接,在 localhost 中进行总是返回 0ms)

【问题讨论】:

  • Write-write-read -- 这可能是 Nagle 问题吗?
  • 我尝试先合并字节数组并使用单个 Stream.Write/Stream.Flush 并没有什么不同。
  • 一些问题。问题指的是哪一行?当您说“读取”时-您真的是指“消耗”-例如,一旦从流中读取字节,它们就会脱离流?在我看来,这就是你在这里所做的。
  • 感谢提醒,我会在.Net的NetworkStream中尝试google Nagle的算法
  • @theMayer "ReadBytes" 中的 2 个 do-while 循环,虽然它是一个循环,但它在我的实验中只执行了一次,所以 2 个 do-while 循环称为 SocketStream.Read 总共两次。

标签: c# .net tcpclient tcplistener networkstream


【解决方案1】:

感谢 Jeroen Mostert 的评论提醒。我补充说:

                ClientSocket.NoDelay = true;
                ClientSocket.Client.NoDelay = true;

对于客户端和服务器端,烦人的延迟消失了,RTT 回到了预期。

TcpClient.NoDelay Property

进一步的测试表明,双方(客户端和服务器)在不修改“NoDelay”选项的情况下贡献了大约 50 毫秒的延迟,因此总共大约 100 毫秒的 RTT“开销”。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-05
    • 2020-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多