前一篇文章主要介紹了.NET Core繼承Kestrel的目的、運行方式以及相關的使用,接下來將進一步從源碼角度探討.NET Core 3.0中關於Kestrel的其餘內容,該部份內容,咱們無需掌握,依然能夠用好Kestrel,本文只是將一些內部的技術點揭露出來,供本身及你們有一個較深的認識。html
Kestrel提供了HTTP 1.X及HTTP 2.0的支持,內容比較多,從趨勢上看,Http2.0針對HTTP 1.X的衆多缺陷進行了改進,因此這篇文章主要關注Kestrel對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的引用,並採用了隊列的方式。ui
相關使用以下:編碼
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: }
頭部壓縮算法這塊涉及到動/靜態表、哈夫曼編/解碼、整型編/解碼等。spa
頭部字段維護在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,不過其內部實現很是複雜,它實現了流的不一樣幀的處理、大小的控制以及多路複用。
咱們知道,在創建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實現,內部有四個常數,以下所示:
其內部方法除了有不一樣幀類型的處理外,還包括獲取有效負荷長度、讀取配置信息,這裏的配置信息主要指的是協議默認值,而不是Kestrel默認值,該功能由
Http2PeerSettings實現,內部提供了一個Update方法用於更新配置信息。
除此之外還包括Stream生命週期處理、錯誤編碼、鏈接控制等,限於篇幅此處不作其餘說明,有興趣的朋友能夠本身查看源代碼。