前言

前一篇文章主要介绍了.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. 创建hexo风格的markdown页面

    最近在用 nodejs 搭建一个个人博客,博客当然要有编辑文章的功能啦.个人比较偏爱 hexo 风格的 markdown 格式,所以想自己的博客也是这样的风格.尝试了几个库,发现 marked 的转换 ...

  2. C#数字图像处理时注意图像的未用区域

    原文:C#数字图像处理时注意图像的未用区域 图1. 被锁定图像像素数组基本布局         如图1所示,数组的宽度并不一定等于图像像素数组的宽度,还有一部分未用区域.这是为了提高效率,系统要确定每 ...

  3. Win10版《芒果TV》获评2016年度Windows Store最佳官方/休闲娱乐应用(LiveSino和微软信仰中心联合评选)

    微软信仰中心于2016年12月9日联合了 LiveSino 进行了最佳 Windows Store 应用特辑的投票评选,通过为期20天的海量用户投票,Win10版<芒果TV>荣获最佳官方应 ...

  4. 零元学Expression Blend 4 - Chapter 24 以实作了解Cover Flow功能

    原文:零元学Expression Blend 4 - Chapter 24 以实作了解Cover Flow功能 今天要介绍一个Silverlight Toolkit内好用且在图片展示操作上很常见的元件 ...

  5. ORACLE 11.2.0.4 RAC安装在rhel6上

    . 关闭ipv4防火墙(两个节点): [root@RAC01 ~]# service iptables stop [root@RAC01 ~]# service iptables status ipt ...

  6. 中国2017 Google 开发者大会第一天简单回顾

    昨天有幸参加了中国2017 Google 开发者大会,在这第一天就收获满满,昨天太忙了,今天早晨来一起简单回顾一下,可以让没有参加的童鞋们感受一下现场的温度. 早早就来到了会议现场,外面看不出什么特别 ...

  7. .NET DataTable转换为JSON格式的字符串

    在进行数据传递的时候,有时我们需要通过Ajax的方式或者其他的方式传递一个数据列表,可以将DataTable或者其他形式的数据列表转换为JSON的格式,通过Ajax实体的形式进行传递. 比如说: // ...

  8. Linux下的帮助命令

    内建命令,外部命令,命令别名:使用:type 命令名,查看 内建命令是shell一部分,一开始就加载进内存,程序一般简单:外部命令反之 2.  帮助命令的使用 内建命令:help 命令名:外部命令:命 ...

  9. HTTPS上线过程说明(阿里云提供免费证书)

    一.上马HTTPS的原因: ①.苹果App Store强制其平台上的app均要使用HTTPS ②.网站经常被劫持,用户和领导希望使用HTTPS ③.跟随HTTPS的大趋势 二.应用上马HTTPS之部门 ...

  10. 配置我的Ubuntu Server记(包括桌面及VNC,SSH,NTP,NFS服务) good

    跟老板申请买了一台配置相对较好的计算机回来做GPU计算,当然,不能独享,所以做成服务器让大家都来用. 这篇日志用来记录配置过程中遇到的一些问题,以方便下次不需要到处谷歌度娘. 安装Server版系统 ...