C# 在C中使用套接字api时,TCP数据包是如何到达的#
我一直在阅读有关TCP数据包以及如何在航行中多次拆分它们的文章。我认为,为了存储每个C# 在C中使用套接字api时,TCP数据包是如何到达的#,c#,sockets,networking,tcp,C#,Sockets,Networking,Tcp,我一直在阅读有关TCP数据包以及如何在航行中多次拆分它们的文章。我认为,为了存储每个ReceiveAsync(),直到有足够的数据可用于解析消息,我必须在用于实际网络流量的缓冲区之上实现某种缓冲区。顺便说一句,我正在通过TCP发送带有长度前缀的protobuf序列化消息 然后我读到下层(以太网?、IP?)实际上会透明地重新组装数据包 我的问题是,在C#中,是否保证通过TCP接收完整的“消息”?换句话说,如果我发送32个字节,我是否需要“一次”接收这32个字节(一次调用ReceiveAsync()
ReceiveAsync()
,直到有足够的数据可用于解析消息,我必须在用于实际网络流量的缓冲区之上实现某种缓冲区。顺便说一句,我正在通过TCP发送带有长度前缀的protobuf序列化消息
然后我读到下层(以太网?、IP?)实际上会透明地重新组装数据包
我的问题是,在C#中,是否保证通过TCP接收完整的“消息”?换句话说,如果我发送32个字节,我是否需要“一次”接收这32个字节(一次调用ReceiveAsync()
)?或者我必须“存储”每个接收,直到接收的字节数等于长度前缀
另外,我是否可以在对ReceiveAsync()
的一次调用中收到多条消息?假设一条“protobuf消息”是32字节。我送了两个。我是否可以在“一次”中接收48个字节,然后在另一次中接收16个字节
我知道这个问题很容易在google上出现,但我永远无法判断它是否在正确的上下文中(谈论实际的TCP协议,或者C#将如何向程序员公开网络流量)
谢谢。TCP是一种流协议-它传输字节流。这就是全部。绝对不隐含任何消息框架/分组。事实上,您应该忘记,在使用TCP套接字编写代码时,甚至存在以太网数据包或IP数据报
您可能会发现自己有1个字节可用,或10000个字节可供读取。(同步)Berkeley sockets API的优点在于,作为应用程序程序员,您不需要担心这一点。由于您使用的是长度前缀消息格式(干得好!),所以只需recv()
您所期望的字节数即可。如果有比应用程序请求更多的可用字节,内核将在下一次调用之前保持对其余字节的缓冲。如果可用字节数少于所需字节数,线程将阻塞或调用将指示接收的字节数少于所需字节数。在这种情况下,您只需再次休眠,直到数据可用
异步API的问题在于它需要应用程序跟踪更多的状态本身。即使这样,情况也比实际需要复杂得多。使用异步API,您仍然可以控制从内核请求的数据量,但是当启动异步回调时,您需要知道下一个要请求的数据量
请注意,4.5中的C#异步
/等待
使异步处理变得更容易,因为您可以以同步方式进行。看看作者的评论:
Socket.ReceiveAsync是一个奇怪的例子。它与.net4.5中的异步/等待功能无关。它被设计为一种替代的套接字API,不会像BeginReceive/EndReceive那样对内存进行重击,只需要在最核心的服务器应用程序中使用
我的问题是,在C#中,是否保证通过TCP接收完整的“消息”
不会。您将不会收到完整的消息。一次发送不会导致一次接收。你必须继续阅读,直到你收到你所需要的一切
请参见此处的示例,它将读取的数据保存在缓冲区中,并不断检查是否有更多数据要读取:
private static void ReceiveCallback(IAsyncResult ar)
{
try
{
// Retrieve the state object and the client socket
// from the asynchronous state object.
StateObject state = (StateObject)ar.AsyncState;
Socket client = state.workSocket;
// Read data from the remote device.
int bytesRead = client.EndReceive(ar);
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
// Get the rest of the data.
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReceiveCallback), state);
}
else
{
// All the data has arrived; put it in response.
if (state.sb.Length > 1)
{
response = state.sb.ToString();
}
// Signal that all bytes have been received.
receiveDone.Set();
}
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
有关更多详细信息,请参阅MSDN文章和文章。第二个链接介绍了更多细节,它还有示例代码。TCP是一种基于流的八位字节协议。因此,从应用程序的角度来看,您只能向流中读取或写入字节 我一直在阅读有关TCP数据包以及如何在航行中多次拆分它们的文章 TCP数据包是一个网络实现细节。它们用于提高效率(一次发送一个字节是非常低效的)。数据包分段是在设备驱动程序/硬件级别完成的,并且从不向应用程序公开。应用程序永远不知道“数据包”是什么或其边界在哪里 我认为我必须在用于实际网络流量的缓冲区之上实现某种缓冲区,以便存储每个ReceiveAsync(),直到有足够的数据可用于解析消息 对。因为“消息”不是TCP概念。这纯粹是一个应用程序概念。大多数应用程序协议确实定义了一种“消息”,因为它更容易推理 然而,一些应用程序协议没有定义“消息”的概念;他们将TCP流视为实际流,而不是消息序列 为了支持这两种应用协议,TCP/IP API必须是基于流的 顺便说一句,我正在通过TCP发送带有长度前缀的protobuf序列化消息 那很好。IMO说,长度前缀比其他选项更容易处理 我的问题是,在C#中,是否保证通过TCP接收完整的“消息” 没有 或者我必须“存储”每个接收,直到接收的字节数等于长度前缀?另外,我是否可以在对ReceiveAsync()的一次调用中收到多条消息 对,对 更有趣的是:
- 您只能获得部分长度前缀(假定为多字节长度前缀)
- 您可以一次收到任意数量的消息
- 缓冲区可以包含消息的一部分,也可以包含消息长度前缀的一部分
- 下次读取可能无法完成当前邮件,甚至无法完成当前邮件的长度前缀