【发布时间】: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