前言

前一篇文章主要介绍了.NET Core继承Kestrel的目的、运行方式以及相关的使用,接下来将进一步从源码角度探讨.NET Core 3.0中关于Kestrel的其他内容,该部分内容,我们无需掌握,依然可以用好Kestrel,本文只是将一些内部的技术点揭露出来,供自己及大家有一个较深的认识。

Kestrel提供了HTTP 1.X及HTTP 2.0的支持,内容比较多,从趋势上看,Http2.0针对HTTP 1.X的众多缺陷进行了改进,所以这篇文章主要关注Kestrel对HTTP 2.0的支持。

HTTP 2.0

流控制

在讨论流控制之前,我们先看一下流控制的整体结构图:

接下来,我们详细讨论一下流控制,其中内部有一个结构体的实现:FlowControl,FlowControl在初始化的时候设置了所能接收或者输出的数据量大小,并会根据输入出入进行动态控制,毕竟资源是有限的,在有限资源的限制下,需要灵活处理数据包对资源的占用。FlowControl.Advance方法的调用会腾出空间,FlowControl.TryUpdateWindow会占用空间,以下是FlowControl的源码:

  1. 1: internal struct FlowControl
  1. 2: {
  1. 3: public FlowControl(uint initialWindowSize)
  1. 4: {
  1. 5: Debug.Assert(initialWindowSize <= Http2PeerSettings.MaxWindowSize, $"{nameof(initialWindowSize)} too large.");
  1. 6:  
  1. 7: Available = (int)initialWindowSize;
  1. 8: IsAborted = false;
  1. 9: }
  1. 10:  
  1. 11: public int Available { get; private set; }
  1. 12: public bool IsAborted { get; private set; }
  1. 13:  
  1. 14: public void Advance(int bytes)
  1. 15: {
  1. 16: Debug.Assert(!IsAborted, $"({nameof(Advance)} called after abort.");
  1. 17: Debug.Assert(bytes == 0 || (bytes > 0 && bytes <= Available), $"{nameof(Advance)}({bytes}) called with {Available} bytes available.");
  1. 18:  
  1. 19: Available -= bytes;
  1. 20: }
  1. 21:
  1. 22: public bool TryUpdateWindow(int bytes)
  1. 23: {
  1. 24: var maxUpdate = Http2PeerSettings.MaxWindowSize - Available;
  1. 25:  
  1. 26: if (bytes > maxUpdate)
  1. 27: {
  1. 28: return false;
  1. 29: }
  1. 30:  
  1. 31: Available += bytes;
  1. 32:  
  1. 33: return true;
  1. 34: }
  1. 35:  
  1. 36: public void Abort()
  1. 37: {
  1. 38: IsAborted = true;
  1. 39: }
  1. 40: }

在控制流中,主要包括FlowControl和StreamFlowControl,StreamFlowControl依赖于FlowControl(Http2Stream引用了StreamFlowControl的读写实现)。我们知道,在计算机网络中,Flow和Stream都是指流的概念,Flow侧重于主机或者网络之间的双向传输的数据包,Stream侧重于成对的IP之间的会话。

在FlowControl的输入输出控制中,OutFlowControl增加了对OutputFlowControlAwaitable的引用,并采用了队列的方式。

相关使用如下:

  1. 1: public OutputFlowControlAwaitable AvailabilityAwaitable
  1. 2: {
  1. 3: get
  1. 4: {
  1. 5: Debug.Assert(!_flow.IsAborted, $"({nameof(AvailabilityAwaitable)} accessed after abort.");
  1. 6: Debug.Assert(_flow.Available <= 0, $"({nameof(AvailabilityAwaitable)} accessed with {Available} bytes available.");
  1. 7:  
  1. 8: if (_awaitableQueue == null)
  1. 9: {
  1. 10: _awaitableQueue = new Queue<OutputFlowControlAwaitable>();
  1. 11: }
  1. 12:  
  1. 13: var awaitable = new OutputFlowControlAwaitable();
  1. 14: _awaitableQueue.Enqueue(awaitable);
  1. 15: return awaitable;
  1. 16: }
  1. 17: }

头部压缩算法

头部压缩算法这块涉及到动/静态表、哈夫曼编/解码、整型编/解码等。

头部字段维护在HeaderField中,源码如下:

  1. 1: internal readonly struct HeaderField
  1. 2: {
  1. 3: public const int RfcOverhead = 32;
  1. 4:  
  1. 5: public HeaderField(Span<byte> name, Span<byte> value)
  1. 6: {
  1. 7: Name = new byte[name.Length];
  1. 8: name.CopyTo(Name);
  1. 9:  
  1. 10: Value = new byte[value.Length];
  1. 11: value.CopyTo(Value);
  1. 12: }
  1. 13:  
  1. 14: public byte[] Name { get; }
  1. 15:  
  1. 16: public byte[] Value { get; }
  1. 17:  
  1. 18: public int Length => GetLength(Name.Length, Value.Length);
  1. 19:  
  1. 20: public static int GetLength(int nameLength, int valueLength) => nameLength + valueLength + 32;
  1. 21: }

静态表由StaticTable实现,内部维护了一个只读的HeaderField数组,动态表由DynamicTable实现,可以视为是HeaderField的一个动态数组的实现,其初始大小在实例化的时候输入,并除以32(HeaderField.RfcOverhead)。

哈夫曼编/解码和整型编/解码会被HPackDecoder和HPackEncoder引用。

HPackDecoder提供了三个公共方法,这三个方法最终都会调用EncodeString进行最终的编码,目前可以看到其内部只有整形编码,我相信在未来会增加哈夫曼编码,以下是EncodeString源码(有兴趣的朋友可以关注下Span<>的使用):

  1. 1: private bool EncodeString(string s, Span<byte> buffer, out int length, bool lowercase)
  1. 2: {
  1. 3: const int toLowerMask = 0x20;
  1. 4:  
  1. 5: var i = 0;
  1. 6: length = 0;
  1. 7:  
  1. 8: if (buffer.Length == 0)
  1. 9: {
  1. 10: return false;
  1. 11: }
  1. 12:  
  1. 13: buffer[0] = 0;
  1. 14:  
  1. 15: if (!IntegerEncoder.Encode(s.Length, 7, buffer, out var nameLength))
  1. 16: {
  1. 17: return false;
  1. 18: }
  1. 19:  
  1. 20: i += nameLength;
  1. 21:  
  1. 22: for (var j = 0; j < s.Length; j++)
  1. 23: {
  1. 24: if (i >= buffer.Length)
  1. 25: {
  1. 26: return false;
  1. 27: }
  1. 28:  
  1. 29: buffer[i++] = (byte)(s[j] | (lowercase && s[j] >= (byte)'A' && s[j] <= (byte)'Z' ? toLowerMask : 0));
  1. 30: }
  1. 31:  
  1. 32: length = i;
  1. 33: return true;
  1. 34: }

HPackEncoder只有一个公共方法Decode,不过其内部实现非常复杂,它实现了流的不同帧的处理、大小的控制以及多路复用。

HTTP帧处理

  1. 我们知道,在建立HTTP2.X连接后,EndPoints就可以交换帧了。.NET Core中,主要有十种帧的处理,代码实现上,将这十种帧放到了一个大的类中,也就是Http2Frame,.NET Core在具体的使用场景中会对其进行一次预处理,主要是为了确定流大小、StreamId、帧的类型以及特定场景下的特殊属性的赋值。(关于HTTP帧的知识点,大家可以点击链接查看详细的信息。)
  1. Http2Frame源码如下:
  1. 1: internal enum Http2FrameType : byte
  1. 2: {
  1. 3: DATA = 0x0,
  1. 4: HEADERS = 0x1,
  1. 5: PRIORITY = 0x2,
  1. 6: RST_STREAM = 0x3,
  1. 7: SETTINGS = 0x4,
  1. 8: PUSH_PROMISE = 0x5,
  1. 9: PING = 0x6,
  1. 10: GOAWAY = 0x7,
  1. 11: WINDOW_UPDATE = 0x8,
  1. 12: CONTINUATION = 0x9
  1. 13: }

  1. 帧类型的区分,可以使得.NET Core更好的处理不同的帧,比如读取和写入。
  1. 写入功能主要在Http2FrameWriter中实现,内部除了对特定帧的处理外,还包括更新数据包大小、完成、挂起以及刷新操作,内部都用到了lock以实现线程安全。部分源码如下:
  1. 1: public void UpdateMaxFrameSize(uint maxFrameSize)
  1. 2: {
  1. 3: lock (_writeLock)
  1. 4: {
  1. 5: if (_maxFrameSize != maxFrameSize)
  1. 6: {
  1. 7: _maxFrameSize = maxFrameSize;
  1. 8: _headerEncodingBuffer = new byte[_maxFrameSize];
  1. 9: }
  1. 10: }
  1. 11: }
  1. 12:  
  1. 13: public ValueTask<FlushResult> FlushAsync(IHttpOutputAborter outputAborter, CancellationToken cancellationToken)
  1. 14: {
  1. 15: lock (_writeLock)
  1. 16: {
  1. 17: if (_completed)
  1. 18: {
  1. 19: return default;
  1. 20: }
  1. 21:
  1. 22: var bytesWritten = _unflushedBytes;
  1. 23: _unflushedBytes = 0;
  1. 24:  
  1. 25: return _flusher.FlushAsync(_minResponseDataRate, bytesWritten, outputAborter, cancellationToken);
  1. 26: }
  1. 27: }

读取功能主要由Http2FrameReader实现,内部有四个常数,如下所示:

  • HeaderLength = 9:Header长度
  • TypeOffset = 3:类型偏移量
  • FlagsOffset = 4:标记偏移量
  • StreamIdOffset = 5:StreamId偏移量
  • SettingSize = 6:Id占用2 bytes, 值占用了4 bytes
  1. 其内部方法除了有不同帧类型的处理外,还包括获取有效负荷长度、读取配置信息,这里的配置信息主要指的是协议默认值,而不是Kestrel默认值,该功能由

Http2PeerSettings实现,内部提供了一个Update方法用于更新配置信息。

除此以外还包括Stream生命周期处理、错误编码、连接控制等,限于篇幅此处不做其他说明,有兴趣的朋友可以自己查看源代码。

.NET Core 3.0之深入源码理解Kestrel的集成与应用(二)的更多相关文章

  1. .NET Core 3.0之深入源码理解Kestrel的集成与应用(一)

      写在前面 ASP.NET Core 的 Web 服务器默认采用Kestrel,这是一个基于libuv(一个跨平台的基于Node.js异步I/O库)的跨平台.轻量级的Web服务器. 在开始之前,先回 ...

  2. .NET Core 3.0之深入源码理解Startup的注册及运行

    原文:.NET Core 3.0之深入源码理解Startup的注册及运行   写在前面 开发.NET Core应用,直接映入眼帘的就是Startup类和Program类,它们是.NET Core应用程 ...

  3. .NET Core 3.0之深入源码理解Configuration(一)

    Configuration总体介绍 微软在.NET Core里设计出了全新的配置体系,并以非常灵活.可扩展的方式实现.从其源码来看,其运行机制大致是,根据其Source,创建一个Builder实例,并 ...

  4. .NET Core 3.0之深入源码理解HttpClientFactory(二)

      写在前面 上一篇文章讨论了通过在ConfigureServices中调用services.AddHttpClient()方法,并基于此进一步探讨了DefaultHttpClientFactory是 ...

  5. .NET Core 3.0之深入源码理解Host(二)

      写在前面 停了近一个月的技术博客,随着正式脱离996的魔窟,接下来也正式恢复了.本文从源码角度进一步讨论.NET Core 3.0 中关于Host扩展的一些技术点,主要讨论Long Run Pro ...

  6. .NET Core 3.0之深入源码理解ObjectPool(一)

    写在前面 对象池是一种比较常用的提高系统性能的软件设计模式,它维护了一系列相关对象列表的容器对象,这些对象可以随时重复使用,对象池节省了频繁创建对象的开销. 它使用取用/归还的操作模式,并重复执行这些 ...

  7. .NET Core 3.0之深入源码理解HealthCheck(一)

    写在前面 我们的系统可能因为正在部署.服务异常终止或者其他问题导致系统处于非健康状态,这个时候我们需要知道系统的健康状况,而健康检查可以帮助我们快速确定系统是否处于正常状态.一般情况下,我们会提供公开 ...

  8. .NET Core 3.0之深入源码理解Host(一)

    写在前面 ASP .NET Core中的通用主机构建器是在v2.1中引入的,应用在启动时构建主机,主机作为一个对象用于封装应用资源以及应用程序启动和生存期管理.其主要功能包括配置初始化(包括加载配置以 ...

  9. .NET Core 3.0之深入源码理解Configuration(三)

      写在前面 上一篇文章讨论了文件型配置的基本内容,本篇内容讨论JSON型配置的实现方式,理解了这一种配置类型的实现方式,那么其他类型的配置实现方式基本可以触类旁通.看过了上一篇文章的朋友,应该看得出 ...

随机推荐

  1. IdentityServer学习目录

    IdentityServer IdentityServer的基本概念与特性 IdentityServer流程图与相关术语 最简单的IdentityServer实现 最简单的IdentityServer ...

  2. spring boot的默认配置

    # BANNER banner.charset=UTF- # Banner file encoding. banner.location=classpath:banner.txt # Banner f ...

  3. IOS开发之把 Array 和 Dictionaries 序列化成 JSON 对象

    1 前言通过 NSJSONSerialization 这个类的 dataWithJSONObject:options:error:方法来实现,Array 和 dictionary 序列化成 JSON ...

  4. WPF,通过修改dataGrid的cell的style,改变选中行失去焦点时的颜色 4.0可用

    <Style TargetType="{x:Type DataGridCell}"> <Style.Triggers> <Trigger Proper ...

  5. UBUTUN 通过蓝牙连接Hoary和诺基亚手机

    通过蓝牙连接Hoary和诺基亚手机 这个how to已经用Hoary.诺基亚6630和一个道尔芯片(Dongle)蓝牙(Usb蓝牙)测试过了.通过这个How to,你可以:-通过蓝牙,从你的电脑发送文 ...

  6. Win8Metro(C#)数字图像处理--2.19图像水平镜像

    原文:Win8Metro(C#)数字图像处理--2.19图像水平镜像  [函数名称] 图像水平镜像函数MirrorXProcess(WriteableBitmap src) [函数代码]      ...

  7. 微信小程序把玩(三十三)Record API

    原文:微信小程序把玩(三十三)Record API 其实这个API也挺奇葩的,录音结束后success不走,complete不走,fail也不走, 不知道是不是因为电脑测试的原因,只能等公测或者等他们 ...

  8. 阿里Android开发手册正式版一览

    新年伊始,春意盎然之际,阿里巴巴在2月28日再度为工程师们送上了一份重磅开春好礼:<阿里巴巴Android开发手册>. 该手册长达66页,是阿里巴巴集团各大 Android 开发团队的集体 ...

  9. Android零基础入门第68节:完善RecyclerView,添加首尾视图

    在之前学习ListView的时候,有学习过如何给ListView添加列表头和列表尾.但是通过近几期的学习,发现RecyclerView是一个比ListView更加强大和灵活的组件,今天一起来学习如何给 ...

  10. Delphi-网络编程-第一个网络方面作品(UDP聊天程序)

    其实这不算是一个聊天程序,因为还不能实现双方互发信息,只有一方能发信息,呵呵 我以后再改进吧.... 服务端代码: unit Unit1;   interface   uses   Windows, ...