前言

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

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

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

相关使用如下:

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

头部压缩算法

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

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

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

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

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

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

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

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

HTTP帧处理

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

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

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

  • HeaderLength = 9:Header长度
  • TypeOffset = 3:类型偏移量
  • FlagsOffset = 4:标记偏移量
  • StreamIdOffset = 5:StreamId偏移量
  • SettingSize = 6:Id占用2 bytes, 值占用了4 bytes
其内部方法除了有不同帧类型的处理外,还包括获取有效负荷长度、读取配置信息,这里的配置信息主要指的是协议默认值,而不是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. 在 Laravel 中通过 Artisan View 扩展包创建及删除应用视图文件

    1.简介 本扩展包添加了两个视图相关的Artisan命令到Laravel应用,以便我们通过Artisan命令即可创建和管理视图文件,可谓是进一步解放了生产力. 2.安装 还是通过Composer安装: ...

  2. mingw64 构建 Geos

    简述 在做某个小程序时候用到了QT,而用的Qt是mingw版本的,所以使用mingw构建了一下geos库. 1.准备工作 首先需要先安装好mingw,这里直接使用http://www.mingw-w6 ...

  3. mage J和Graphpad如何对Western Blot条带灰度分析

    原文 mage J和Graphpad如何对Western Blot条带灰度分析 WB是研究蛋白表达的一个经典方法.对于一些时间点或者是不同组织蛋白表达量的分析就涉及到量的变化.一些凝胶成像软件带有此分 ...

  4. Visual studio 创建通用项目失败vstemplate

    Visual studio 创建项目失败 提示 the vstemplate file references the wizard class 'Microsoft.VisualStudio.WinR ...

  5. .NET Core2.1项目在Linux上使用验证码报Unable to load shared library 'gdiplus' or one of its dependencies

    -- ::, 线程ID:[] 日志级别:ERROR 出错类:WebApp.HttpGlobalExceptionFilter property:[(null)] - 错误描述:System.TypeI ...

  6. SqlServer 动态SQL(存储过程)中Like 传入参数无正确返回值的问题

    最近在做项目时,以动态Sql进行Like语句查询时发现应该返回的结果却一直返回空,后来发现是写法错误: 错误SQL: DECLARE @0 varchar(20) SET @0 = 'XA-LZ' S ...

  7. 15个非常棒的jQuery无限滚动插件【瀑布流效果】

    现在,最热门的网站分页趋势之一是jQuery的无限滚动(也即瀑布流).如果你碰巧观察Pinterest的网站,如Facebook,Twitter和deviantART的,你会发现无限滚动的动作,当旧的 ...

  8. 初次比较正式的IT职场面试后几点对自己web开发的思考

    昨天晚上参加一个web开发面试,对于还没有真正毕业的自己来说,web开发的面试不是第一次,暑假就面试几家公司,前几次的面试并没有发现自己对自己学习的专业知识有什么学习态度的问题,因为前几次的面试官都是 ...

  9. 3017C语言_文件

    第七章  文件 7.1  C语言文件的概念 7.1.1 文件的概念 在此之前,所有的输入和输出只涉及到键盘和显示器.在运行C程序时,我们通过键盘输入数据并借助显示器把程序的运算结果显示出来.但是,计算 ...

  10. 细谈unity资源加载和卸载

    转载请标明出处:http://www.cnblogs.com/zblade/ 一.概要 在了解unity的资源管理方式之后,接下来细谈一下Unity的资源是如何从磁盘中加载到运行时的内存中,以及又是如 ...