在 .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. GIT里的一些名词

    origin:他是一个特定远程仓库的别名,他不是一个仓库的属性. head:通常情况下可以将它与当前的分支等同.

  2. unbuntu系统( PC机 )中安装360wifi步骤

    少说废话,每一步都经过验证: 1.  首先查看一下当前使用的linux版本: gxjun@gxjun:~$ uname -r 4.8.0-59-generic 2. 将360wifi插入PC的USB中 ...

  3. 先从一个 libev 的 demo 入手

    最近想研究下 libev 这个网络库,所以先从官方文档一个最简单的 demo 开始,代码如下: //io.c // a single header file is required #include ...

  4. 从MySQL全库备份中恢复某个库和某张表

    在Mysqldump官方工具中,如何只恢复某个库呢? 全库备份 [root@HE1 ~]# mysqldump -uroot -p --single-transaction -A --master-d ...

  5. 记一次spring boot参数初始化的问题

    背景:接手一个项目,看到一个配置参数的引用: @Value("${webSocket.id}") 再看看配置application.yml: ... webSocket: id: ...

  6. 【原创 Hadoop&Spark 动手实践 13】Spark综合案例:简易电影推荐系统

    [原创 Hadoop&Spark 动手实践 13]Spark综合案例:简易电影推荐系统

  7. (转)java术语(PO/POJO/VO/BO/DAO/DTO)

    转自:http://blog.csdn.net/gaoyunpeng/article/details/2093211 PO(persistant object) 持久对象在o/r 映射的时候出现的概念 ...

  8. vs code 设置问题

    现已取消 .vue 文件与 HTML 的默认关联,需要手动配置.vue 文件里不能使用div + Tab 键快速生成 html 代码   "emmet.syntaxProfiles" ...

  9. Servlet 2.0 && Servlet 3.0 新特性

    概念:透传. Callback 在异步线程中是如何使用的.?? Servlet 2.0 && Servlet 3.0 新特性 Servlet 2.0 && Servle ...

  10. quartz与spring boot-最简模式

    多年前使用过quartz,今天又需要再用,而且是在spring boot框架下.很神奇,spring也是十年前用过的. 这里仅记录下完成的最快速和简单的操作,高级的使用以后有空弄明白了再写: 1.增加 ...