如果我们不从TcpClient流中读取,接收到的数据会发生什么
本文关键字:数据 什么 我们 TcpClient 读取 如果 | 更新日期: 2023-09-27 18:22:34
我对tcp流中的一个案例感到好奇。
假设我们创建了一个TcpClient,并向传出流写入一些有意义的请求字符串。例如Http请求。
try
{
string requestString = "GET /Api/Test HTTP/1.1 'r'n" +
"Host: 192.168.2.45 'r'n" +
"Connection:close 'r'n'r'n";
TcpClient client = new TcpClient();
client.Connect(new IPEndPoint(IPAddress.Parse("192.168.2.45"), 80));
NetworkStream stream = client.GetStream();
byte[] reqBuffer = System.Text.Encoding.Default.GetBytes(requestString);
stream.Write(reqBuffer, 0, reqBuffer.Length);
因此,我们的NIC会立即从目标套接字接收到响应。
以下是我的问题:
- 传入的字节存储在哪里?(NIC是否有存储器,或者存储在RAM中?)
- 如果我们不从应用程序中读取这些字节,NIC会对它们做什么
读取:
if (stream.CanRead)
{
bufferInt = stream.Read(buffer, 0, client.ReceiveBufferSize);
}
与许多其他事情一样,答案在于分层。
所以,让我们从硬件开始:
所有NIC都有一些内部缓冲区。这是组装任何响应的第一个地方,但这也是像TCP这样的东西实际上意义不大的地方;NIC关心的只是它自己的网络协议,例如以太网或PPP。在这个级别上,IP只是一个不区分的有效负载,而IP又以TCP作为有效负载(尽管应该注意的是,分层还远远不够完美:)例如,TCP和IP之间有很多耦合)。
必须先对传入的数据进行解释,然后才能执行任何操作;让我们跳过细节,假设NIC缓冲区现在包含一个漂亮的小TCP/IP数据包。现在,NIC驱动程序开始发挥作用——机器上的每个打开的端口都有一块相关的内存,用于接收数据。基本上,这是您在设置ReceiveBufferSize
和SendBufferSize
时控制的内容。驱动程序会指示NIC如何处理传入的数据——通常,NIC会使用DMA将数据直接发送到RAM。这是非常快的-现代NIC实际上不需要有大的板载存储芯片;即使对于服务器NIC,其数量通常也在32 MiB左右。
对于您的问题,这两个RAM缓冲区是最重要的——当它们满了时,NIC只会丢弃任何到达的数据包。在具有流量控制的TCP的情况下,它会告诉对方停止传输,谢谢。实际上,这模仿了缓冲流的通常行为——发送者将被阻止,直到可以再次发送另一段数据为止。当这种情况发生时,发送方将重新发送上次未发送的数据。在像UDP这样的协议的情况下,没有流量控制,也没有重新传输,所以你只是不可逆转地丢失了数据(甚至没有告诉你有问题)。
如果您有一个挂起的发送/接收操作(例如NetworkStream.Read
),您也将使用自己的缓冲区——这是另一层,但它实际上是最不重要的。这里发生的一切是,当操作系统从NIC驱动程序获得信息时,它会用内部缓冲区的数据填充缓冲区,并向您发出信号。在同步场景中(如您的情况),这只会导致Read
调用返回。异步场景有些棘手,因为.NET和操作系统交互以生成回调,但其余部分基本相同。例如,这与从本地硬盘读取文件没有本质区别。
在.NET中需要注意的一件重要事情是,在这些发送/接收中使用的缓冲区在操作期间被固定。这意味着缓冲区被禁止在内存中移动,这会降低垃圾收集器的有效性(阻止正确的堆压缩);如果你有很多长时间运行的操作,你真的希望尽可能多地重用缓冲区——如果你总是为每个操作创建一个新的缓冲区,你可能会面临堆碎片的问题。
-
它存储在RAM中,使用的RAM数量为
client.ReceiveBufferSize
。网卡上还有少量缓冲存储器,这将取决于网卡。 -
一旦RAM缓冲区已满,操作系统将停止从网卡中读取,这将导致网卡的缓冲区已装满,并停止确认传入的数据包。这将导致TCP窗口缩小到0,发送器将停止发送,直到收到来自网卡的确认,只有当数据离开网卡的缓冲区时才会发生这种情况。
不是答案,但这里有一个代码片段,让您可以使用ReceiveBufferSize和SendBufferSize属性。
下面发生了什么?
在您按下回车键之前,服务器不会读取传入的缓冲区,当您这样做时,您可以看到客户端发送了额外的缓冲区。
public class Program
{
public static void Main(string[] args)
{
TcpListener server = new TcpListener(IPAddress.Any, 8989);
server.Start();
server.BeginAcceptSocket(AcceptSocket, server);
TcpClient client = new TcpClient();
client.Connect("127.0.0.1", 8989);
FileStream dataToSend = File.OpenRead("c:''temp''videoUpload.rar");
byte[] tempSendBuffer = new byte[4096];
int numberOfBytesRead = 0;
while ((numberOfBytesRead = dataToSend.Read(tempSendBuffer, 0, tempSendBuffer.Length)) > 0)
{
client.GetStream().Write(tempSendBuffer, 0, numberOfBytesRead);
Console.WriteLine("{0} bytes sent", numberOfBytesRead);
}
Console.ReadLine();
}
private static void AcceptSocket(IAsyncResult asyncResult)
{
Socket localSocket = (asyncResult.AsyncState as TcpListener).EndAcceptSocket(asyncResult);
while (true)
{
if (localSocket.Available > 0)
{
Console.WriteLine("Local Socket has {0} bytes pending to be received. Enter to receive", localSocket.Available);
Console.ReadLine();
byte[] tempReadBuffer = new byte[4096];
int numberOfReceivedBytes = localSocket.Receive(tempReadBuffer);
Console.WriteLine("{0} bytes RECEIVED", numberOfReceivedBytes);
}
Thread.Sleep(500);
}
}
}