处理异步时的 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,涵盖相同的操作集(SendSendToReceive、@ 987654335@、ConnectAcceptDisconnect)。反过来,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的主要内容,如果未能解决你的问题,请参考以下文章

TCP调试助手与TCPSocket区别

Socket网络编程(C#)----TcpListener 与 TcpClient

求助tcpclient 报“由于目标计算机积极拒绝,无法连接”

tcpclient和socket的区别?

socket接收到数据增加no

esp32tcpclient最大传输的数据长度