处理异步时的 TcpClient 与 Socket
Posted
技术标签:
【中文标题】处理异步时的 TcpClient 与 Socket【英文标题】:TcpClient vs Socket when dealing with asynchronousy 【发布时间】:2012-08-12 10:40:48 【问题描述】:这又不是另一个 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 模式 - 即不返回 Task .. 令人困惑)。
首先,知道为什么 .NET 4.5 中的 Socket 类没有以任何方式实现 TAP 模式,即 ReadAsync 和 WriteAsync 返回 Task(如果以不同方式调用以保持向后兼容的事件)?
无论如何,从 APM 模型方法对构建一个 Task 方法很容易,所以假设我将此异步方法(用于读取)称为 ReadAsyncTAP(返回一个 Task)。
好的?所以现在假设我想编写一个客户端方法async Task<Byte[]> ReadNbBytes(int nbBytes)
,我将从我的代码中调用它以异步地从网络中读取一定数量的字节。
仅基于 TcpClient 的此方法的实现将通过调用 GetStream 获取 NetworkStream,并将包含一个异步循环等待 ReadAsync 调用,直到缓冲区满。
此方法基于 Socket 的实现将包含一个异步循环,等待 ReadAsyncTAP 直到缓冲区满。
归根结底,从客户端代码的角度来看,我想这没什么区别。在这两种情况下,对await ReadNbBytes
的调用都会立即“返回”。但是,我想这在幕后会有所不同...
对于依赖 NetworkStream 的 TcpClient,与直接使用 socket 相比,读取是否会在任何时候阻塞?如果不是,是不是说同步阻塞模式时对 TcpClient 的说明是错误的?
如果有人能澄清,将不胜感激!
谢谢。
【问题讨论】:
【参考方案1】:TcpClient
流上的异步 I/O 不会阻塞。看起来 MSDN 文档是错误的(您可以在 Reflector 中通过遵循 NetworkStream
的异步 I/O 调用来验证这一点)。
Stream
类型很“有趣”:默认情况下,Stream
基类将通过在同步 I/O 上阻塞线程池线程来实现异步 I/O。因此,您永远都不想在 MemoryStream
之类的东西上执行异步 I/O,它只提供同步方法。
NetworkStream
确实提供异步 I/O,因此 NetworkStream
实例上的异步 I/O 实际上是异步的。但情况并非总是如此:FileStream
尤其是usually not asynchronous but it is if you construct the instance just right。
关于为什么Socket
没有TAP 方法:这是一个非常好的问题!我认为这是一个疏忽,但现在 .NET 4.5 发布了,看起来它是故意遗漏的。可能是他们只是不想让 API 过于复杂 - Socket 已经有同步和 两个 异步 API,涵盖相同的操作集(Send
、SendTo
、Receive
、@ 987654335@、Connect
、Accept
、Disconnect
)。反过来,TAP 将需要两个额外的异步 API 用于该完整集。这至少会导致一个有趣的命名情况(*Async
名称已被使用,并且他们将为每个操作添加另外两个 *Async
名称)。
旁注:“附加”API 用于高性能异步Socket
通信。他们使用SocketAsyncEventArgs
,虽然不太好用,但产生的内存垃圾更少。如果将 TAP API 添加到 Socket
,他们会希望提供易于使用的版本(包装 Begin
/End
)和更高性能的版本(包装 Async
)。
如果您对为Socket
制作 TAP 方法感兴趣,可以从 Stephen Toub 的 Awaiting Socket Operations 开始(他只为高性能 API 提供包装器)。我为启用async
的套接字使用了类似的东西。
【讨论】:
我喜欢 Stepgen Toub 的解决方案,但我认为您会写出类似的东西。 :) 与WebClient
命名的情况类似,其中*Async
名称用于EAP(基于事件的异步模式)方法。他们解决的方法是将 TAP 方法命名为*TaskAsync
。
@d4wn:正在努力。 :) 由于我的日常工作很忙,可能要到 2013 年才能完成。:(
@svick:Socket
中的命名情况比较棘手。有了整个 BCL 中的这个一个类,他们决定使用*Async
来表示“使用SocketAsyncEventArgs
”而不是EAP。因此,您可以将 *TaskAsync
用于 APM 样式的包装器,然后如果您同时遵循现有的 TAP 和 Socket
约定,那么您最终会使用 *AsyncAsync
用于 SocketAsyncEventArgs
样式的包装器。对于已经负担过重的类型,丑陋且肯定令人困惑的方法。
再次...谢谢斯蒂芬! ;)以上是关于处理异步时的 TcpClient 与 Socket的主要内容,如果未能解决你的问题,请参考以下文章
Socket网络编程(C#)----TcpListener 与 TcpClient