c# - 连续调用 NetworkStream.Write - 它有什么不同吗?

标签 c# tcp networkstream

考虑以下两种发送数据的方法和一种读取数据的方法:

public static void SendConsecutively(this NetworkStream stream)
{
    byte[] header = {1, 2, 3, 4};
    byte[] message = {5, 6, 7, 8, 9, 10};
    stream.Write(header, 0, header.Length);
    stream.Write(message, 0, message.Length);
}

public static void SendAllInOne(this NetworkStream stream)
{
    byte[] headerAndMessage = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    stream.Write(headerAndMessage, 0, headerAndMessage.Length);
}

public static byte[] ReadMessage(this NetworkStream stream)
{
    byte[] data = new byte[10];
    int bytesRead = stream.Read(data, 0, 10);
    return data;        
}

与不拆分数据(如 SendAllInOne)相比,是否将数据拆分为两个 block (如 SendConsecutively)有区别吗?

问题是,在我的测试中,ReadMessage 总是读取 10 个字节。我向其发送 header 和消息(但不知道它是如何实现的)的第 3 方服务器通常也会收到 10 个字节。但有时 - 在极少数情况下 - 他们告诉我第 3 方服务器只收到 4 个字节。我必须处理这个问题,即该服务器只收到 4 个字节,尽管我确信我已经使用 SendConsecutively 发送了 10 个字节。 (因为我的日志中没有异常,这意味着 stream.Write 调用肯定已经发出。)

因此,一个问题是:两次连续的 stream.Write 调用之间发生了什么?我想:没什么,因为 NetworkStream.Flush 的文档说:“Flush 方法实现了 Stream.Flush 方法;但是,因为 NetworkStream 没有缓冲,所以它对网络流没有影响。” [1]

我收到了 Wireshark 日志,其中可以看到一个 TCP 数据包仅包含四个 header 字节。每个 stream.Write 调用都会产生一个 TCP 数据包吗?但话又说回来,我什至需要关心我的数据是如何拆分成 TCP 数据包的吗?因为大消息无论如何都会被分割成多个 TCP 数据包,不是吗? (是的,也有发送的大消息比我在上面的示例中使用的 6 字节大得多 - 例如:4 字节 header + 3000 字节消息)

我的 SendConsecutively 方法是否有任何缺陷,可能会在接收端导致此类问题(同时考虑到 message 可能是 3000 字节,而不是像示例代码)?

难道是第 3 方服务器的问题?如果该服务器像上面的 ReadMessage 方法一样实现,则可能会出现问题。因为有时(我认为这一定会发生,当消息通过 TCP 分段时),ReadMessage 读取的字节数少于已发送的字节数,并且返回的 bytesRead 数字小于实际的字节数消息的长度。消息的其余部分稍后在流中可用(几毫秒?)。对此有什么想法吗?

[1] MSDN,NetworkStream.Flush,https://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.write(v=vs.110).aspx

最佳答案

网络流是一种抽象——它隐藏了实现细节。通常开发人员可以忽略发送数据的传输方式——多个数据包或单个数据包,使用哪种协议(protocol)等。但是,重要的是(至少对我来说,可能对每个使用网络流的人来说)记住一些关于 I/O 的事实:

  • 通常保证数据的顺序(它按照写入的顺序接收)
  • 可以随时中断(通过抛出异常)
  • 同步读取可能会无限期地阻塞(大多数情况下不是,但有机会)
  • I/O 比其他指令慢得多
  • 数据 block 的大小在流的不同端是不同的
  • 频繁写入小块的效率可能低于写入一个大块(由于底层传输层导致的额外负载)
  • 读取数据比写入数据慢可能会溢出内部缓冲区

这些事实中的大部分并不取决于电线两端使用的技术。它可以在一侧是 Java,在另一侧是 .NET - 通常只要 channel 只是一个字节流就没有关系。

考虑到这一点,您的问题的答案:

Does it make a difference whether or not to split the data into two chunks (like in SendConsecutively) compared to not splitting the data (like in SendAllInOne)? ... What happens between two consecutive stream.Write calls? ... Is my SendConsecutively method flawed in any way which could cause such problems on the receiver-side (Also considering that message could be 3000 bytes, not just 6 like in the example code)?

根据套接字的内部实现可能会有所不同。 At 可能导致两次连续发送或合并为一次发送。 NetworkStream发送调用底层套接字的发送。这个调用被转换为套接字的 Send() 然后它真的取决于 WinSock's send 的实现.

对于接收者来说,数据发送的准确程度并不重要。网络层可以将数据拆分成不同的 block ,而与发送 block 的大小无关。

关于c# - 连续调用 NetworkStream.Write - 它有什么不同吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49635260/

相关文章:

c# - IEnumerable 跳过并获取

tcp - IP数据包和MAC目标地址

c# - 长时间运行的阻塞方法。阻塞、休眠、开始/结束和异步之间的区别

支持异步操作并遵守超时的.NET TcpClient/NetworkStream 实现

c# - BackgroundWorker如何决定在哪个线程上运行RunWorkerCompleted处理程序?

C# 等效于 Swift typealias

c# - 如何检索在 Acrobat 中选择的打印机的名称?

c# - NetworkStream.CanRead 返回 true 但缓冲区不返回任何值

Go中的TCP客户端/服务器文件传输

c# - 发送大量 Protobuf 消息时 NetworkStream 关​​闭