使用异步时,TcpClient与Socket哪个更好?

27
这不是又一篇TcpClient vs Socket的文章。
TcpClient是Socket类的一个包装器,旨在简化开发,同时也暴露了底层的Socket。
但是......
在TcpClient类的MSDN库页面上,可以看到以下备注:
“TcpClient类提供了简单的方法来在网络上以同步阻塞模式连接、发送和接收流数据。”
而对于Socket类:
“Socket类允许您使用枚举类型ProtocolType中列出的任何通信协议执行同步和异步数据传输。”
使用TcpCient异步发送/接收某些数据时,需要调用GetStream方法从中检索底层的NetworkStream,然后在其上调用ReadAsync和WriteAsync方法以异步读取/写入数据,遵循TAP模式(可能使用async/await构造)。
使用Socket异步发送/接收某些数据时(我不是专家,但我认为我理解正确),我们可以直接从/到套接字实例本身读取/写入,通过调用BeginRead / EndRead BeginWrite / EndWrite(或仅调用ReadAsync或WriteAsync..不暴露TAP模式-即不返回任务..令人困惑)。
首先,有没有想过为什么.NET 4.5中的Socket类在任何情况下都未以任何方式实现TAP模式,即ReadAsync和WriteAsync不返回任务(即使以不同的方式调用以保留向后兼容性)?
无论如何,从APM模型方法对生成任务方法很容易,所以假设我调用此异步方法(用于读取),即ReadAsyncTAP(返回一个Task)。
好了?那么现在假设我想编写一个客户端方法async Task<Byte[]> ReadNbBytes(int nbBytes),我将从我的代码中调用它来异步读取特定数量的字节。
基于TcpClient的这种方法实现将通过调用GetStream来获取NetworkStream,并包含一个异步循环,等待ReadAsync调用,直到缓冲区满。

基于Socket的该方法实现将包含一个异步循环,等待ReadAsyncTAP直到缓冲区满。

从客户端代码的角度来看,在这两种情况下,调用await ReadNbBytes都会立即“返回”。但是,在幕后,它们可能存在一些区别...对于使用NetworkStream进行依赖的TcpClient,与直接使用Socket相比,读取是否在任何时候被阻塞,如果没有,则在同步阻塞模式下讨论TcpClient时的备注是否有误?

如果有人能够澄清,将不胜感激!

谢谢。

1个回答

28
TcpClient流上进行异步I/O不会阻塞。看起来MSDN文档是错误的(可以通过跟踪NetworkStream的异步I/O调用在Reflector中验证这一点)。 Stream类型是“有趣”的:默认情况下,Stream基类将通过在同步I/O上阻塞线程池线程来实现异步I/O。因此,您永远不希望在诸如MemoryStream之类仅提供同步方法的东西上执行异步I/O。 NetworkStream确实提供了异步I/O,因此对NetworkStream实例执行异步I/O实际上是异步的。但这并不总是正确的:FileStream特别是通常不是异步的,但如果你恰好以正确的方式构造实例,则它是异步的
关于为什么Socket没有TAP方法:这是一个非常好的问题!我原以为这是一个疏忽,但现在.NET 4.5已经发布了,看起来这是有意留下的。可能是因为他们不想使API过于复杂 - Socket已经有同步和两个异步API,涵盖了相同的操作(SendSendToReceiveReceiveFromConnectAcceptDisconnect)。 TAP反过来将需要两个额外的异步API来完成整个集合。这至少会导致有趣的命名情况(*Async名称已经被占用,他们将为每个操作添加另外两个*Async名称)。

附注: "additional" API 用于高性能异步 Socket 通信。它们使用 SocketAsyncEventArgs,这不太容易使用,但会产生较少的内存垃圾。如果将 TAP API 添加到 Socket 中,则需要提供易于使用的版本(包装 Begin/End)和更高性能的版本(包装 Async)。

如果您有兴趣为 Socket 制作 TAP 方法,则一个很好的起点是 Stephen Toub 的 Awaiting Socket Operations(他仅为高性能 API 提供包装器)。我为我的启用 async 的套接字使用类似的东西。


我喜欢Stepgen Toub的解决方案,但我认为你会写出类似的东西。 :) - Şafak Gür
在命名方面,WebClient也有类似的情况,其中*Async名称用于EAP(基于事件的异步模式)方法。他们解决这个问题的方式是将TAP方法命名为*TaskAsync - svick
@d4wn:正在努力中。:) 由于我白天工作特别忙,可能要等到2013年才能完成。:( - Stephen Cleary
@svick: Socket 中的命名情况比较棘手。在整个 BCL 中,只有这个类使用 *Async 来表示“使用 SocketAsyncEventArgs”,而不是 EAP。因此,您可以对 APM 风格的包装器使用 *TaskAsync,然后如果遵循现有的 TAP 和 Socket 约定,就会得到 *AsyncAsync 用于 SocketAsyncEventArgs 风格的包装器。对于一个已经过度负担的类型来说,这些方法看起来很丑陋,肯定会让人感到困惑。 - Stephen Cleary
再一次感谢Stephen!;) - darkey

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