KestrelServer詳解[2]: 網絡連接是如何創建的?

《KestrelServer詳解[1]:注冊監聽終結點(Endpoint)》已經詳細講述了如何使用KestrelServer,現在我們來簡單聊聊這種服務器的總體設計和實現原理。當KestrelServer啟動的時候,注冊的每個終結點將轉換成對應的“連接監聽器”,后者在監聽到初始請求時會創建“連接”,請求的接收和響應的回復都在這個連接中完成。[本文節選《ASP.NET Core 6框架揭秘》第18章]

一、連接上下文(ConnectionContext )

監聽器創建的連接是一個抽象的概念,我們可以將其視為客戶端和服務端完成消息交換而構建的“上下文”,該上下文通過如下這個ConnectionContext類型表示。ConnectionContext派生于抽象基類BaseConnectionContext,后者實現了IAsyncDisposable接口。每個連接具有一個通過ConnectionId屬性表示的ID,它的LocalEndPoint和RemoteEndPoint屬性返回本地(服務端)和遠程(客戶端)終結點。服務器提供的特性集合體現在它的Features屬性上,另一個Items提供了一個存放任意屬性的字典。ConnectionClosed屬性提供的CancellationToken可以用來接收連接關閉的通知。Abort方法可以中斷當前連接,這兩個方法在ConnectionContext被重寫。ConnectionContext類型的Transport屬性提供的IDuplexPipe對象是用來對請求和響應進行讀寫的雙向管道。

public?abstract?class?ConnectionContext?:?BaseConnectionContext
{public?abstract?IDuplexPipe?Transport?{?get;?set;?}public?override?void?Abort(ConnectionAbortedException?abortReason);public?override?void?Abort();
}public?abstract?class?BaseConnectionContext?:?IAsyncDisposable
{public?virtual?EndPoint??LocalEndPoint?{?get;?set;?}public?virtual?EndPoint??RemoteEndPoint?{?get;?set;?}public?abstract?string?ConnectionId?{?get;?set;?}public?abstract?IFeatureCollection?Features?{?get;?}public?abstract?IDictionary<object,?object?>?Items?{?get;?set;?}public?virtual?CancellationToken?ConnectionClosed?{?get;?set;?}public?abstract?void?Abort();public?abstract?void?Abort(ConnectionAbortedException?abortReason);public?virtual?ValueTask?DisposeAsync();
}

如果采用HTTP 1.X和HTTP 2協議,KestrelServer會采用TCP套接字(Socket)進行通信,對應的連接體現為一個SocketConnection對象。如果采用的是HTTP 3,會采用基于UDP的QUIC協議進行通信,對應的連接體現為一個QuicStreamContext對象。如下面的代碼片段所示,這兩個類型都派生于TransportConnection,后者派生于ConnectionContext。

internal?abstract?class?TransportConnection?:?ConnectionContext{}
internal?sealed?class?SocketConnection?:?TransportConnection{}
internal?sealed?class?QuicStreamContext?:?TransportConnection{}

二、連接監聽器(IConnectionListener )

KestrelServer同時支持三個版本的HTTP協議,HTTP 1.X和HTTP 2建立在TCP協議之上,針對這樣的終結點會轉換成通過如下這個IConnectionListener接口表示的監聽器。它的EndPoint屬性表示監聽器綁定的終結點,當AcceptAsync方法被調用時,監聽器便開始了網絡監聽工作。當來自某個客戶端端的初始請求抵達后,它會將創建代表連接的ConnectionContext上下文創建出來。另一個UnbindAsync方法用來解除終結點綁定,并停止監聽。

public?interface?IConnectionListener?:?IAsyncDisposable
{EndPoint?EndPoint?{?get;?}ValueTask<ConnectionContext?>?AcceptAsync(CancellationToken?cancellationToken?=?default(CancellationToken));ValueTask?UnbindAsync(CancellationToken?cancellationToken?=?default(CancellationToken));
}

QUIC利用傳輸層的UDP協議實現了真正意義上的“多路復用”,所以它將對應的連接監聽器接口命名為IMultiplexedConnectionListener。它的AcceptAsync方法創建的是代表多路復用連接的MultiplexedConnectionContext對象,后者的AcceptAsync會將ConnectionContext上下文創建出來。QuicConnectionContext 類型是對MultiplexedConnectionContext的具體實現,它的AcceptAsync方法創建的就是上述的QuicStreamContext對象,該類型派生于抽象類TransportMultiplexedConnection。

public?interface?IMultiplexedConnectionListener?:?IAsyncDisposable
{EndPoint?EndPoint?{?get;?}ValueTask<MultiplexedConnectionContext?>?AcceptAsync(IFeatureCollection??features?=?null,CancellationToken?cancellationToken?=?default(CancellationToken));ValueTask?UnbindAsync(CancellationToken?cancellationToken?=?default(CancellationToken));
}public?abstract?class?MultiplexedConnectionContext?:?BaseConnectionContext
{public?abstract?ValueTask<ConnectionContext?>?AcceptAsync(CancellationToken?cancellationToken?=?default(CancellationToken));public?abstract?ValueTask<ConnectionContext>?ConnectAsync(IFeatureCollection??features?=?null,CancellationToken?cancellationToken?=?default(CancellationToken));
}internal?abstract?class?TransportMultiplexedConnection?:?MultiplexedConnectionContext
internal?sealed?class?QuicConnectionContext?:?TransportMultiplexedConnection

KestrelServer使用的連接監聽器均由對應的工廠來構建。如下所示的IConnectionListenerFactory接口代表用來構建IConnectionListener監聽器的工廠,IMultiplexedConnectionListenerFactory工廠則用來構建IMultiplexedConnectionListener監聽器。

public?interface?IConnectionListenerFactory
{ValueTask<IConnectionListener>?BindAsync(EndPoint?endpoint,CancellationToken?cancellationToken?=?default(CancellationToken));
}public?interface?IMultiplexedConnectionListenerFactory
{ValueTask<IMultiplexedConnectionListener>?BindAsync(EndPoint?endpoint,?IFeatureCollection??features?=?null,CancellationToken?cancellationToken?=?default(CancellationToken));
}

三、總體設計

上面圍繞著“連接”介紹了一系列接口和類型,它們之間的關系體現在如圖1所示的UML中。KestrelServer啟動時會根據每個終結點支持的HTTP協議利用IConnectionListenerFactory或者IMultiplexedConnectionListenerFactory工廠來創建代表連接監聽器的IConnectionListener或者IMultiplexedConnectionListener對象。IConnectionListener監聽器會直接將代表連接的ConnectionContext上下文創建出來,IMultiplexedConnectionListener監聽器創建的則是一個MultiplexedConnectionContext上下文,代表具體連接的ConnectionContext上下文會進一步由該對象進行創建。

0da6f9cd63a862d8d943bc478385e02b.png
圖1 “連接”相關的接口和類型

四、利用連接接收請求和回復響應

下面演示的實例直接利用IConnectionListenerFactory工廠創建的IConnectionListener監聽器來監聽連接請求,并利用建立的連接來接收請求和回復響應。由于表示連接的ConnectionContext上下文直接面向傳輸層,接受的請求和回復的響應都體現為二進制流,解析二進制數據得到請求信息是一件繁瑣的事情。這里我們借用了“HttpMachine”NuGet包提供的HttpParser組件來完成這個任務,為此我們為它定義了如下這個HttpParserHandler類型。如果將這個HttpParserHandler對象傳遞給HttpParser對象,后者在請求解析過程中會調用前者相應的方法,我們利用這些方法利用讀取的內容將描述請求的HttpRequestFeature特性構建出來。源代碼可以從這里查看。

public?class?HttpParserHandler?:?IHttpParserHandler
{private?string??headerName?=?null;public?HttpRequestFeature?Request?{?get;?}?=?new?HttpRequestFeature();public?void?OnBody(HttpParser?parser,?ArraySegment<byte>?data)?=>?Request.Body?=?new?MemoryStream(data.Array!,?data.Offset,?data.Count);public?void?OnFragment(HttpParser?parser,?string?fragment)?{?}public?void?OnHeaderName(HttpParser?parser,?string?name)?=>?headerName?=?name;public?void?OnHeadersEnd(HttpParser?parser)?{?}public?void?OnHeaderValue(HttpParser?parser,?string?value)?=>?Request.Headers[headerName!]?=?value;public?void?OnMessageBegin(HttpParser?parser)?{?}public?void?OnMessageEnd(HttpParser?parser)?{?}public?void?OnMethod(HttpParser?parser,?string?method)?=>?Request.Method?=?method;public?void?OnQueryString(HttpParser?parser,?string?queryString)?=>?Request.QueryString?=?queryString;public?void?OnRequestUri(HttpParser?parser,?string?requestUri)?=>?Request.Path?=?requestUri;
}

如下所示的演示程序利用WebApplication對象的Services屬性提供的IServicePovider對象來提供IConnectionListenerFactory工廠。我們調用該工廠的BindAsync方法創建了一個連接監聽器并將其綁定到采用5000端口本地終結點。在一個無限循環中,我們調用監聽器的AcceptAsync方法開始監聽連接請求,并最終將代表連接的ConnectionContext上下文創建出來。

using?App;
using?HttpMachine;
using?Microsoft.AspNetCore.Connections;
using?Microsoft.AspNetCore.Http.Features;
using?System.Buffers;
using?System.IO.Pipelines;
using?System.Net;
using?System.Text;var?factory?=?WebApplication.Create().Services.GetRequiredService<IConnectionListenerFactory>();
var?listener?=?await?factory.BindAsync(new?IPEndPoint(IPAddress.Any,?5000));
while?(true)
{var?context?=?await?listener.AcceptAsync();_?=?HandleAsync(context!);static?async?Task?HandleAsync(ConnectionContext?connection){var?reader?=?connection!.Transport.Input;while?(true){var?result?=?await?reader.ReadAsync();var?request?=?ParseRequest(result);reader.AdvanceTo(result.Buffer.End);Console.WriteLine("[{0}]Receive?request:?{1}?{2}?Connection:{3}",connection.ConnectionId,?request.Method,?request.Path,?request.Headers?["Connection"]????"N/A");var?response?=?@"HTTP/1.1?200?OK
Content-Type:?text/plain;?charset=utf-8
Content-Length:?12Hello?World!";await?connection.Transport.Output.WriteAsync(Encoding.UTF8.GetBytes(response));if?(request.Headers.TryGetValue("Connection",?out?var?value)?&&?string.Compare(value,?"close",?true)?==?0){await?connection.DisposeAsync();return;}if?(result.IsCompleted){break;}}}static??HttpRequestFeature?ParseRequest(ReadResult?result){var?handler?=?new?HttpParserHandler();var?parserHandler?=?new?HttpParser(handler);parserHandler.Execute(new?ArraySegment<byte>(result.Buffer.ToArray()));return?handler.Request;}
}

針對連接的處理實現在HandleAsync方法中。HTTP 1.1默認會采用長連接,多個請求會使用同一個連接發送過來,所以針對單個請求的接收和處理會放在一個循環中,直到連接被關閉。請求的接收利用ConnectionContext對象的Transport屬性返回的IDuplexPipe對象來完成。簡單起見,我們假設每個請求的讀取剛好能夠一次完成,所以每次讀取的二進制剛好是一個完整的請求。讀取的二進制內容利用ParseRequest方法借助于HttpParser對象轉換成HttpRequestFeature對象后,我們直接生成一個表示響應報文的字符串并采用UTF-8對其編碼,編碼后的響應利用上述的IDuplexPipe對象發送出去。手工生成的“Hello World!”響應將以圖18-5的形式呈現在瀏覽器上。

059c1bbc216c39efc88dacd0476315cd.png
圖2 面向“連接”編程

按照HTTP 1.1規范的約定,如果客戶端希望關閉默認開啟的長連接,可以在請求中添加“Connection:Close”報頭。HandleAsync方法在處理每個請求時會確定是否攜帶了此報頭,并在需要的時候調用ConnectionContext上下文的 DisposeAsync方法關閉并釋放當前連接。該方法在對請求進行處理時會將此報頭和連接的ID輸出到控制臺上。圖2所示的控制臺輸出是先后接收到三次請求的結果,后面兩次顯式添加了“Connection:Close”,可以看出前兩次復用同一個連接。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/282544.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/282544.shtml
英文地址,請注明出處:http://en.pswp.cn/news/282544.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

PHP操作文件和目錄的相關函數

//判斷文件類型 filetype(a.php);//打開或創建文件 fopen(a.php,w);//讀取文件 fgets(a.php);//按行讀取 fread(a.php,1049);//按塊讀取 file_get_contents(a.php);//讀取文件//復制文件 copy(a.php,./b.php);//將a.php復制到b.php//刪除文件 unlink(b.php);//移動文件 rename(…

linux LyX中文編輯環境安裝配置指南-TeX可視化工具

TeX可以說是國際上排版的標準&#xff0c;尤其是論文、書籍之類&#xff0c;對公式的表現比MS辦公系列強的太多&#xff0c;格式異常優美&#xff0c;但是由于其比較復雜的命令&#xff0c;非可視化編輯&#xff0c;所以使得入門門檻較高&#xff0c;所以出現了LaTeX這樣的命令…

pandas DataFrame 數據處理常用操作

Xgboost調參&#xff1a; https://wuhuhu800.github.io/2018/02/28/XGboost_param_share/ https://blog.csdn.net/hx2017/article/details/78064362 pandas DataFrame中的空值處理&#xff1a; https://blog.csdn.net/yuanxiang01/article/details/78738812 pandas的DataFrame、…

redis集群報Jedis does not support password protected Redis Cluster configurations異常解決辦法...

解決spring-data-redis操作redis集群報“Jedis does not support password protected Redis Cluster configurations”的異常 原因&#xff1a;使用spring-data-redis操作redis集群時由于redis集群設置了密碼。 解決方案&#xff1a;升級spring-data-redis版本即可解決&#xf…

支付寶支付開發流程

支付寶開發流程1、首先我們先談談第三方支付所謂第三方支付就是和一些各大銀行簽約&#xff0c;并具備一定實力和信譽保障的第三方獨立機構提供的交易平臺目前市面上常見的有支付寶&#xff0c;財付通&#xff0c;網銀&#xff0c;易寶支付等&#xff0c;網站需要實現第三方支付…

MQ消息隊列之MSMQ

主要參考文章&#xff1a; 消息隊列&#xff08;Message Queue&#xff09;簡介及其使用 轉載于:https://www.cnblogs.com/mailaidedt/p/6599130.html

css選擇器總結

一.選擇器 1. css1選擇器 2.css2選擇器 3.css3選擇器 4.:nth-of-type(n)和:nth-child(n)區別 (1).在不指定類型時&#xff0c;nth-child(n)選中的是父元素下的第N個子元素。nth-of-type(n)選中的是父元素下的不同類型標簽的第N個。(2).在指定具體元素時,ele:nth-child(n)要求不…

Hypercrx:開源項目不只有Star

01戰隊簡介大家好&#xff0c;我們是Hypercrx戰隊&#xff0c;非常榮幸獲得了首屆Microsoft Edge瀏覽器開拓者大賽的一等獎&#xff01;我是隊長唐燁男&#xff08;中&#xff09;&#xff0c;位于我左側的是寧志成&#xff0c;右側的是林以任&#xff0c;我們都來自華東師范大…

《Python編程快速上手 讓繁瑣工作自動化》pdf

<div id"article_content" class"article_content tracking-ad" data-mod"popu_307" data-dsm"post"> <p><br></p><p>下載地址&#xff1a;<a target"_blank" href"https://page74.c…

PHP上傳圖片到數據庫,并進行顯示

1、創建數據表 CREATE TABLE ccs_image (id int(4) unsigned NOT NULL auto_increment,description varchar(250) default NULL,bin_data longblob,filename varchar(50) default NULL,filesize varchar(50) default NULL,filetype varchar(50) default NULL,PRIMARY KEY (id)…

Keras版Faster-RCNN代碼學習(IOU,RPN)1

最近開始使用Keras來做深度學習&#xff0c;發現模型搭建相較于MXnet, Caffe等確實比較方便&#xff0c;適合于新手練手&#xff0c;于是找來了目標檢測經典的模型Faster-RCNN的keras代碼來練練手&#xff0c;代碼的主題部分轉自知乎專欄Learning Machine&#xff0c;作者張瀟捷…

歐拉函數模板

一、單個歐拉函數計算 可評測鏈接&#xff1a;http://codevs.cn/problem/4939/ 單個歐拉函數計算公式&#xff1a;φ&#xff08;n&#xff09;n*&#xff08;1-1/p1&#xff09;*&#xff08;1-1/p2&#xff09;*……*&#xff08;1-1/pn&#xff09; Step 1&#xff1a; 一邊…

洛谷P1145 約瑟夫

題目描述 n個人站成一圈&#xff0c;從某個人開始數數&#xff0c;每次數到m的人就被殺掉&#xff0c;然后下一個人重新開始數&#xff0c;直到最后只剩一個人。現在有一圈人&#xff0c;k個好人站在一起&#xff0c;k個壞人站在一起。從第一個好人開始數數。你要確定一個最小的…

.NET 反向代理-YARP

什么是 YARPYARP (另一個反向代理) 設計為一個庫&#xff0c;提供核心代理功能&#xff0c;你可以根據應用程序的特定需求進行自定義。YARP 是使用 .NET的基礎架構構建在 .NET上的。YARP 的主要不同之處在于&#xff0c;它被設計成可以通過 .NET 代碼輕松定制和調整&#xff0c…

JavaScript 開發的45個經典技巧

2019獨角獸企業重金招聘Python工程師標準>>> 前言&#xff1a;此篇譯文在各網站均有標注原創的聲明&#xff0c;譯者名字已不可考&#xff0c;暫為佚名 JavaScript是一個絕冠全球的編程語言&#xff0c;可用于Web開發、移動應用開發&#xff08;PhoneGap、Appcelera…

PHP循環輸出二維數組

目的: 將二維數組中的每一個元素輸出 首先定義一個二維數組 //定義數組 $arr array(array(北京,上海,深圳,廣州),array(黑龍江,吉林,遼寧,江蘇) ); 一 for循環輸出 1.1 直接輸出 //for循環遍歷數組 for($i 0; $i < count($arr); $i) {for($j 0; $j < count($arr[…

回歸遠程 - 云原生IDE是IaC從表象觸達本質的必然選擇 | SmartIDE

作者&#xff1a;徐磊&#xff0c;開源云原生SmartIDE創始人、LEANOSFT創始人/首席架構師/CEO&#xff0c;微軟最有價值專家MVP/微軟區域技術總監Regional Director&#xff0c;華為云最有價值專家。從事軟件工程咨詢服務超過15年時間&#xff0c;為超過200家不同類型的企業提供…

android獲取手機機型、廠商、deviceID基本信息

/*** 系統工具類*/ public class SystemUtil {/*** 獲取當前手機系統語言。** return 返回當前系統語言。例如&#xff1a;當前設置的是“中文-中國”&#xff0c;則返回“zh-CN”*/public static String getSystemLanguage() {return Locale.getDefault().getLanguage();}/***…

題目1362:左旋轉字符串(Move!Move!!Move!!!)

題目1362&#xff1a;左旋轉字符串&#xff08;Move!Move!!Move!!!&#xff09; 時間限制&#xff1a;2 秒 內存限制&#xff1a;32 兆 特殊判題&#xff1a;否 提交&#xff1a;2306 解決&#xff1a;961 題目描述&#xff1a;匯編語言中有一種移位指令叫做循環左移&#xff0…

PHP簡單實現遞歸

//遞歸 //斐波那契數列 function digui($n) {if($n > 2) {$arr[$n] digui($n-1) digui($n-2);return $arr[$n];} else {return 1;} }//使用 echo digui(5); 總結 : 首先應該想到出口是什么,將出口放在else條件里 例如,本例斐波那契數列中,出口是前兩個數是1,也就是數組下…