NetworkStream.Write与Socket.Send的区别

21

我有一个使用自定义FTP库的C#应用程序。目前,我使用Socket.Send发送数据,但我想知道是否更好地使用NetworkStream.Write,通过套接字初始化NetworkStream。

使用其中一种方法有什么优势吗?

3个回答

29
NetworkStream的优势主要在于它是一个Stream。而Socket的劣势在于,通用代码从抽象I/O源(如Stream)读取和写入时,无法处理SocketNetworkStream的主要用途是当你有其他某些代码从Stream中读取或写入数据,并且你希望能够将其用于Socket时。如果你处于这种情况下,NetworkStream将会非常有帮助!
例如,假设你有一个通信库,并支持从文件、命名管道和TCP/IP中序列化消息。此时,最理想的I/O类选择应该是Stream。然后,你的序列化方法可以接受FileStreamPipeStreamNetworkStream,甚至可以接受MemoryStream。这就是抽象的好处,因为创建了流之后,方法可以与其交互而不知道它是哪种类型的流。
从这个意义上说,NetworkStream使用了适配器设计模式。它将Socket API转换为Stream API,以便期望使用Stream的客户端可以使用它。
最后,问题是,如果NetworkStreamSocketStream适配器,那么我们应该使用哪个?嗯,如果你需要一个Stream,那么NetworkStream是你唯一的选择。如果你不需要Stream,那么你可以使用你最熟悉的API。如果你已经成功使用Socket,则没有迫切的理由要切换到NetworkStream

我可以补充一下,NetworkStream也具有更好的异步支持。 - undefined
@LukeMcGregor 这不是真的。NetworkStream没有直接实现基于任务的异步支持,而是依赖于Stream自己的APM-to-Task功能,这并不像“本地”的基于任务的异步那样理想(它使用TaskFactory.FromAsync)。此外,BeginRead/BeginWrite(APM)方法只是包装了Socket.BeginReceive/Socket.BeginSend,这些方法不如(名称令人困惑的非基于任务的)Socket.SendAsyncSocket.ReceiveAsync方法高效。 - Dai
进一步说,与“本地”IOCP和重叠I/O相比,TPL和APM异步模型都不算是“高性能”的。但这是一个套接字编程概念,对于大多数开发人员来说已经超出了范畴,对于那些声称自己是“Windows程序员”的人来说,这也是一门失传的艺术。IOCP仍然是平台特定的技术,但它将比BCL深处发明的任何“用户模式”解决方案更快地发送/接收数据。如果您想在Windows下获得“最快的tx/rx性能”,您需要使用原始套接字、静态缓冲区和IOCP w/Overlapped I/O(或非Windows平台上的等效本地解决方案)。 - Shaun Wilson

3
您可以将NetworkStream的创建与其作为抽象Stream进行处理分开 - 这样您就能够更改传输方式,或者仅为测试创建Stream存根。
至于方法本身的问题 - NetworkStream.Write内部只有一个操作(除了状态检查),即streamSocket.Send(buffer, offset, size, SocketFlags.None); - 因此它大多数情况下和在套接字上调用它是相同的。

3

使用.NET Framework实现的NetworkStream::Write的一个缺点是,如果底层网络(OSI层1-4)无法接收整个数据缓冲区,则您不会得到通知(除非通过检查.NET Networking性能计数器)。

不可靠。

类似于这样的内容出现在Microsoft的.NET FrameworkSystem.dll中的NetworkStream::Write中:

try
{
    socket.Send(buffer, offset, size, SocketFlags.None);
}
/* ... catch{throw;} */

注意返回值,它代表Send()写入的字节数,被忽略了。这表明NetworkStream::Write在次优网络(漫游/无线)或可能超出可用带宽的网络中不可靠。

不止一种实现。

您可以找到其他实现,它们会写入所有字节,直到所有buffer都被写入(或发生其他故障)。这是发送数据的标准方法,并且始终会表现出以下行为:

var count = 0;
while (count < size)
{
    count += socket.Send(buffer, offset + count, size - count, ...);
}

这是什么意思?

通过直接调用Socket::Send,您可以编写适当且可靠的send()代码,但是如果调用NetworkStream::Write,则无法编写适当且可靠的send()代码。

另外,其他变体的NetworkStream没有BCL分解所显示的相同问题(请参见monocosi2),而仍有一些类似的问题尽管他们已经尽最大努力(请参见flourinefx)。

参考文献

  1. Github上的单库存储库
  2. FlourineFxSL源代码
  3. BitBucket上的cosi2存储库(通过searchcode.com)

1
根据socket.Send的文档:“在非阻塞模式下,即使发送的字节数少于您请求的字节数,发送操作也可能成功完成。您的应用程序有责任跟踪发送的字节数并重试操作,直到应用程序发送所需的字节数。” - 因此,这个错误不适用于不使用非阻塞套接字的情况,并且非阻塞是默认的,因此大多数用户应该没有问题。 - Dai
此外,如果在NetworkStream的构造函数中传入一个非阻塞的Socket实例(if( !socket.IsBlocking ) {),它将抛出一个IOException异常。 - Dai
我点赞你的评论是因为它准确无误,而不是因为我同意你对问题的解释。尽管有构造函数检查,但仍然可以使用非阻塞套接字。我个人过去曾处理过这个问题,让人们了解到这一点是值得的。这个问题可以通过在 Socket::Send 调用周围添加循环来在 netfx 中修复,就像其他实现(corefx、mono、其他)中所做的那样,但“文档说”仍然是允许这个 bug 存在的辩护理由。 - Shaun Wilson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接