在 .NET Core 2.1 中,System.Net.Sockets 的性能有了很大的提升,最好的证明是 Kestrel 与 HttpClient 都改为使用 System.Net.Sockets ,stackoverflow 上也有人提到了,详见 libuv vs sockets in asp.net core 2.1 。

这两天阅读了 corefx 中 HttpClient 的 SocketsHttpHandler 部分实现代码,学习了一下它如何在异步方法中连接 Socket 。

连接 Socket 是在 ConnectHelper 的 ConnectAsync 异步方法中实现的:

public static async ValueTask<(Socket, Stream)> ConnectAsync(string host, int port, CancellationToken cancellationToken)
{
ConnectEventArgs saea;
//..
saea.Initialize(cancellationToken);
saea.RemoteEndPoint = new DnsEndPoint(host, port); if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, saea))
{
//...
}
else if (saea.SocketError != SocketError.Success)
{
throw new SocketException((int)saea.SocketError);
} Socket socket = saea.ConnectSocket;
socket.NoDelay = true;
return (socket, new NetworkStream(socket, ownsSocket: true));
//...
}

用到了 SocketAsyncEventArgs ,但没有直接使用,而是继承它实现了 ConnectEventArgs :

private sealed class ConnectEventArgs : SocketAsyncEventArgs
{
public AsyncTaskMethodBuilder Builder { get; private set; }
public CancellationToken CancellationToken { get; private set; } public void Initialize(CancellationToken cancellationToken)
{
CancellationToken = cancellationToken;
var b = new AsyncTaskMethodBuilder();
var ignored = b.Task; // force initialization
Builder = b;
} public void Clear() => CancellationToken = default; protected override void OnCompleted(SocketAsyncEventArgs _)
{ /* ... */ }
}

ConnectEventArgs 中出现了一个之前从未见过的身影 —— AsyncTaskMethodBuilder ,而且它存在的目的让人费解 —— 为了让无法进行 await 的 SocketAsyncEventArgs 可以 await(见下面代码中的 await 部分),为什么要这样?带着这个疑问继续看代码。

// saea = new ConnectEventArgs();
if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, saea))
{
// Connect completing asynchronously. Enable it to be canceled and wait for it.
using (cancellationToken.Register(s => Socket.CancelConnectAsync((SocketAsyncEventArgs)s), saea))
{
await saea.Builder.Task.ConfigureAwait(false);
}
}

上面就是连接 Socket 的代码,Socket.ConnectAsync 虽然方法名以 Async 结尾,但与通常的异步方法不同,它的返回类型不是 Task ,而是 bool 。

// Returns true if the I/O operation is pending. The System.Net.Sockets.SocketAsyncEventArgs.Completed
// event on the e parameter will be raised upon completion of the operation.
// Returns false if the I/O operation completed synchronously.

如果返回 true ,则表示对应的网络 IO 操作是异步的;返回 false ,则是同步的。

如果是异步 IO 操作,完成后会通过 Completed 事件通知 SocketAsyncEventArgs ,但这里是在 async 异步方法中调用的, SocketAsyncEventArgs 没有提供可以 await 的异步方法,那如何 await ?

答案就是之前让人疑惑的在 ConnectEventArgs 中引入的 AsyncTaskMethodBuilder ,用它的 Task 进行 await 。

await saea.Builder.Task.ConfigureAwait(false);

但仅仅 await 这个什么也不干的 Task ,即使等到天荒地老,也等不到。而我们希望在连接 Socket 的异步 IO 操作完成后,就立即唤醒继续执行。

ConnectEventArgs 通过在 OnCompleted 事件处理方法中将所等待的 Task 的状态设置为 completed ,巧妙地解决了这个问题。

protected override void OnCompleted(SocketAsyncEventArgs _)
{
switch (SocketError)
{
case SocketError.Success:
Builder.SetResult();
break;
//....
}
}

当看明白这个巧妙之处后,不得不发出赞叹:高!实在是高!

连接 Socket 除了异步等待的问题,还有一个连接超时的问题,这里是通过 CancellationToken 解决的,根据超时时间设置,创建一个 CancellationToken :

cancellationWithConnectTimeout.CancelAfter(Settings._connectTimeout);
cancellationToken = cancellationWithConnectTimeout.Token;

然后在 await 时用它来处理连接超时

using (cancellationToken.Register(s => Socket.CancelConnectAsync((SocketAsyncEventArgs)s), saea))
{
await saea.Builder.Task.ConfigureAwait(false);
}

这次 .NET Core 源码学习就到这里,最大收获就是见识了高手是怎么玩转 C# 异步编程的。

.NET Core 开源给 .NET 开发者带来的福音之一就是可以从世界顶尖高手写的 C# 代码中学习。

.NET Core 2.1 源码学习:看 SocketsHttpHandler 如何在异步方法中连接 Socket的更多相关文章

  1. ASP.NET Core 2.1 源码学习之 Options[1]:Configure

    配置的本质就是字符串的键值对,但是对于面向对象语言来说,能使用强类型的配置是何等的爽哉! 目录 ASP.NET Core 配置系统 强类型的 Options Configure 方法 Configur ...

  2. ASP.NET Core 2.1 源码学习之 Options[2]:IOptions

    在 上一章 中,介绍了Options的注册,而在使用时只需要注入 IOption<T> 即可: public ValuesController(IOptions<MyOptions& ...

  3. ASP.NET Core 2.1 源码学习之 Options[3]:IOptionsMonitor

    前面我们讲到 IOptions 和 IOptionsSnapshot,他们两个最大的区别便是前者注册的是单例模式,后者注册的是 Scope 模式.而 IOptionsMonitor 则要求配置源必须是 ...

  4. ASP.NET Core 2.1 源码学习之 Options[3]:IOptionsMonitor 【转】

    原文链接:https://www.cnblogs.com/RainingNight/p/strongly-typed-options-ioptions-monitor-in-asp-net-core. ...

  5. ASP.NET Core 2.1 源码学习之 Options[2]:IOptions 【转】

    原文链接:https://www.cnblogs.com/RainingNight/p/strongly-typed-options-ioptions-in-asp-net-core.html 在 上 ...

  6. ASP.NET Core 2.1 源码学习之 Options[1]:Configure 【转】

    原文链接:https://www.cnblogs.com/RainingNight/p/strongly-typed-options-configure-in-asp-net-core.html 配置 ...

  7. ASP.NET Core 选项模式源码学习Options Configure(一)

    前言 ASP.NET Core 后我们的配置变得更加轻量级了,在ASP.NET Core中,配置模型得到了显著的扩展和增强,应用程序配置可以存储在多环境变量配置中,appsettings.json用户 ...

  8. TCP/IP协议栈源码图解分析系列10:linux内核协议栈中对于socket相关API的实现

    题记:本系列文章的目的是抛开书本从Linux内核源代码的角度详细分析TCP/IP协议栈内核相关技术 轻松搞定TCP/IP协议栈,原创文章欢迎交流, byhankswang@gmail.com linu ...

  9. ASP.NET Core 选项模式源码学习Options IOptions(二)

    前言 上一篇文章介绍IOptions的注册,本章我们继续往下看 IOptions IOptions是一个接口里面只有一个Values属性,该接口通过OptionsManager实现 public in ...

随机推荐

  1. 为ExecutorService增加shutdown hook

    public class ShutdownHook { private static final ShutdownHook INSTANCE = new ShutdownHook(); private ...

  2. 安装rcssmin方法

    #安装rcssmin方法'''pip install wheelpip install rcssmin --install-option="--without-c-extensions&qu ...

  3. ZMQ示例:使用 curve 进行加密通信

    1. ZMQ 官方文档 ZMQ 的官方文档中关于 curve 的介绍如下: Client and server roles A socket using CURVE can be either cli ...

  4. window下zookeeper的下载启动和报错等问题

    在使用dubbo等需要用到zookeeper,之前window下本地部署,启动一直有问题,后面折腾了下才部署成功,此次记录下来. 将zookeeper下载之后,解压到指定目录即可,无需安装.例如:解压 ...

  5. mysql存储过程详解 mysql存储过程和函数

    第20章:存储程序和函数 目录 20.1. 存储程序和授权表 20.2. 存储程序的语法 20.2.1. CREATE PROCEDURE和CREATE FUNCTION 20.2.2. ALTER ...

  6. maven project 报错解决方法

    1 maven 在添加包后出错,project 处有红线的解决办法 Maven默认会使用本地缓存的库来编译工程,对于上次下载失败的库,maven会在~/.m2/repository/<group ...

  7. james2.3 配置收件 之 MariaDB数据库配置

    james我们公司一直都是使用的2.3这个稳定版本,现在已经有3.0了,不过无所谓,能用就行 基于2.3,来进行一些配置,主要是接受邮件,之前的博文如何安装的,这里不多做介绍了,链接参考:https: ...

  8. virt-viewer 连kvm 虚机

    # yum install virt-viewer [root@ cfplace]# virsh list --all Id Name State -------------------------- ...

  9. CentOS 7 安装GitLab

    CentOS 安装GitLab CentOS 安装GitLab GitLab是一个利用Ruby on Rails开发的开源应用程序,实现一个自托管的Git项目仓库,可通过Web界面进行访问公开的或者私 ...

  10. 【iCore4 双核心板_uC/OS-II】例程三:任务的挂起与恢复

    一.实验说明: 当一个任务正在运行的时候,由于某种情况可能需要终止运行一段时间,终止的这段时间就是 所谓的任务的挂起,可以由该任务自身或者其他任务来挂起一个任务.当被挂起的任务需要再次运 行的时候,可 ...