我对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会立即从目标套接字接收响应。
以下是我的问题:
读:
if (stream.CanRead)
{
bufferInt = stream.Read(buffer, 0, client.ReceiveBufferSize);
}
答案 0 :(得分:3)
与许多其他事情一样,答案是分层。
所以,让我们从硬件开始:
所有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中需要注意的一件重要事情是,在这些发送/接收中使用的缓冲区在操作期间固定。这意味着禁止缓冲区在内存中移动,这会降低垃圾收集器的有效性(防止正确的堆压缩);如果你有很多长时间运行的操作,你真的想尽可能多地重用缓冲区 - 如果你总是为每个操作创建一个新的缓冲区,你可能会遇到堆碎片问题。
答案 1 :(得分:1)
它存储在RAM中,它使用的ram数量为client.ReceiveBufferSize
。网卡上还有少量缓冲存储器,这取决于卡。
一旦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);
}
}
}