如果我们不从TcpClient流读取,接收的数据会发生什么

时间:2015-11-19 15:05:20

标签: c# sockets tcp

我对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会立即从目标套接字接收响应。

以下是我的问题:

  1. 存储的传入字节在哪里? (NIC有存储空间,还是存储在RAM中?)
  2. 如果我们不从应用程序中读取它们,我们的NIC会如何处理这些字节。
  3. 读:

    if (stream.CanRead)
    {
       bufferInt = stream.Read(buffer, 0, client.ReceiveBufferSize);
    }
    

3 个答案:

答案 0 :(得分:3)

与许多其他事情一样,答案是分层。

所以,让我们从硬件开始:

所有NIC都有一些内部缓冲区。这是第一个汇总任何响应的地方 - 但它也是像TCP这样的东西并不具有多大意义的水平;所有NIC关心的都是它自己的网络协议,例如以太网或PPP。在这个级别,IP只是一个非差异化的有效载荷,而IP又将TCP作为有效载荷(尽管应该注意到分层远非完美:)在TCP和IP之间有很多耦合,对于例子)。

必须先解释此传入数据,然后才能执行任何操作;让我们跳过细节,并假设NIC缓冲区现在包含一个漂亮的小TCP / IP数据包。现在,NIC驱动程序开始运行 - 机器上的每个开放端口都有一个用于接收数据的相关内存。基本上,这是您在设置ReceiveBufferSizeSendBufferSize时控制的内容。驱动程序将指示NIC如何处理传入数据 - 通常,NIC将使用DMA将数据直接发送到RAM。这是非常快的 - 现代NIC并不需要拥有大型板载内存芯片;即使对于服务器NIC,数量通常也在32 MiB左右。

这两个RAM缓冲区对您的问题最重要 - 当它们已满时,NIC将简单地丢弃任何进一步的数据包。对于具有流量控制的TCP,它会告诉对方停止传输,谢谢。实际上,这模拟了缓冲流的通常行为 - 发送方将被阻止,直到它可以再次发送另一条数据。发生这种情况时,发件人将重新传输最后一次没有成功的数据。在UDP等协议的情况下,没有流量控制,也没有重新传输,因此您只是不可撤销地丢失数据(甚至没有告诉您存在问题)。

如果您有待处理的发送/接收操作(例如NetworkStream.Read),您还将使用自己的缓冲区 - 这是另一层,但它确实是最不重要的。这里发生的所有事情是当操作系统从NIC驱动程序获取信息时,它将使用内部缓冲区中的数据填充缓冲区并向您发出信号。在同步方案中(如您的情况),这只会导致您的Read调用返回。异步方案在.NET和操作系统交互以产生回调方面有点棘手,但其余方面几乎相同。例如,这与从本地硬盘驱动器读取文件根本不同。

在.NET中需要注意的一件重要事情是,在这些发送/接收中使用的缓冲区在操作期间固定。这意味着禁止缓冲区在内存中移动,这会降低垃圾收集器的有效性(防止正确的堆压缩);如果你有很多长时间运行的操作,你真的想尽可能多地重用缓冲区 - 如果你总是为每个操作创建一个新的缓冲区,你可能会遇到堆碎片问题。

答案 1 :(得分:1)

  1. 它存储在RAM中,它使用的ram数量为client.ReceiveBufferSize。网卡上还有少量缓冲存储器,这取决于卡。

  2. 一旦RAM缓冲区已满,操作系统将停止从网卡读取,这将导致网卡的缓冲区已满,并停止确认进入的数据包。这将导致TCP window到收缩到0并且发送方将停止发送,直到它从网卡收到ACK,这只会在数据离开网卡的缓冲区时发生。

答案 2 :(得分:1)

不是答案,但这是一个代码片段,让您可以使用ReceiveBufferSize和SendBufferSize属性。

那里发生了什么?

在您按Enter键之前,服务器不会读取传入缓冲区,当您执行此操作时,您可以看到客户端发送了其他缓冲区。

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);
        }
    }
}