為什麼推薦Kestrel
網路框架千千萬萬,在dotnet平臺,我們可以直接手擼Socket,也可以基於dotnetty來開發,或者選擇某些第三方類似於dotnetty的網路庫,為何我要推薦Kestrel呢?
1 使用框架
網路程式設計是簡單的,簡單到大概就 new Socket(),Send()傳送資料,Receive()接收資料,這大概是初學者的大致感受。
網路程式設計是複雜的,讓Send()和Receive()穩定工作,花了老夫一年時間,每讓伺服器的效能提高10%又各花老夫兩年時間,這大概是手擼過Socket的大哥的感受。
網路程式設計是抽象的傳輸層加高效的緩衝區管理,我們需要把它提升到框架來,而不能停留在原始的Socket工具級別。這大概是我從dotnetty和kestrel裡悟出的道理。
2 框架的支撐者
選擇某個框架,我們首先要看看這個這個框架背後的支撐者的力量。Kestrel是asp.netcore的Server部分,如果asp.netcore說它是dotnet平臺上第二齣名的應用框架,那沒其它框架敢說第一是自己。我們可以透過commits來檢視有哪些大牛在孜孜不倦地維護kestrel,其中@JamesNK、@BrennanConroy、@davidfowl等世界級大牛一直很活躍。反觀其它網路框架,只有少量的社群力量甚至作者單個人的力量在貢獻。
3 Kestrel的影響
三流的框架在自詡,二流的框架在吸取新鮮技術的養分,一流的框架在推動相關領域技術前行。
3.1 推動System.Net.Socket
在dotnet core 2.0或以前,Kestrel使用Libuv取代dotnet的Socket來操作網路,因為彼時dotnet的Socket效能,要比Libuv要差一些,特別在unix上的表現。也正是因為asp.netcore的kestrel對Socket效能有強烈的需求,在2.1時runtime層開始對Socket的效能大力改進,Task和ValueTask的非同步傳送和接收內部實現融入了SocketAsyncEventArgs,Socket甚至為NetworkStream開了路燈,讓Socket與Libuv的效能直接平級。
3.2 推動System.IO.Pipelines
Pipelines誕生於.NET Core團隊為使Kestrel成為業內最快的Web伺服器之一所做的工作。最初是Kestrel內部的一個實現細節,後來發展成為一個可重用的API,它在dotnet coreapp 2.1 中作為一流的 BCL API(System.IO.Pipelines)提供給所有 .NET 開發人員。
正確解析來自Stream或Socket的資料的工作其實非常複雜,沉長和複雜的程式碼讓人難以閱讀和維護。再加上要實現高效能這條要求的話,就讓人更加吐血,而Pipelines旨在解決這種複雜性。
有關Pipelines的好,我就不班門弄斧了,這是@davidfowl寫的Pipelines介紹。
3.3 對普通開發者的影響
曾經一個小小SocketAwaitableEventArgs class,讓多少開發者眼前一亮,驚歎無比。這不,現在已經不是最初實現了ICriticalNotifyCompletion介面了,轉為實現了IValueTaskSource<SocketOperationResult>
,大家慢慢品吧。
4 Kestrel的魅力
4.1 單應用層多傳輸層
支援一個應用監聽多個埠,每個埠走不同傳輸層,最後到達同一個應用協議層。比如下面的配置,傳輸層分別是tcp和tls over tcp,應用層都是http,不管是哪種傳輸最終都是被我們的application層統一處理http,簡稱殊途同歸。
"Kestrel": {
"Endpoints": {
"http": {
"Url": "http://localhost:5000"
},
"https": {
"Url": "https://localhost:5001"
}
},
"Certificates": {
"Default": {
"Path": "",
"Password": ""
}
}
}
4.2 單傳輸層多應用層
我們也可以使用某個監聽埠對應的傳輸層,分支不同的路由來實現多個應用協議application。常見的比如kestrel使用websocket做傳輸層,應用協議層為mqtt或signalr等。
// Mqtt over WebSocket
app.MapConnectionHandler<MqttConnectionHandler>("/mqtt");
// SingalR over Websocket
app.MapHub<SingalRHub>("/signalr");
4.3 自定義應用層
我們這裡說所的應用層協議,往往是我們在這層協議上構建了業務,而不拿它來做傳輸協議,而實際中,一種協議往往即可以做廣義的傳輸協議,也可以直接做構建業務的應用層協議(典型的WebSocket,甚至http也可以做傳輸協議)。在asp.netcore中,SingalR就是典型的一個不太複雜的應用層協議(相對http),我們也可以基於kestrel來開發telnet over tcp的服務,telnet做為應用層,tcp做傳輸層。
public class TelnetConnectionHandler : ConnectionHandler
{
/// <summary>
/// 收到Telnet連線後
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
public override async Task OnConnectedAsync(ConnectionContext connection)
{
var input = connection.Transport.Input;
var output = connection.Transport.Output;
// 從input解析telnet協議
...
}
}
public static class ListenOptionsExtensions
{
/// <summary>
/// 使用TelnetConnectionHandler
/// </summary>
/// <param name="listen"></param>
public static void UseTelnet(this ListenOptions listen)
{
listen.UseConnectionHandler<TelnetConnectionHandler>();
}
}
var section = context.Configuration.GetSection("Kestrel");
kestrel.Configure(section).Endpoint("Telnet", endpoint => endpoint.ListenOptions.UseTelnet());
4.4 增加傳輸層
假設我們需要telnet應用增加支援tls安全傳輸,我們可以再增加一個Telnets
的EndPoint。在telnet協議之前插入https(實際準確是的叫tls)中介軟體。現在不管是未加密的telnet請求還是tls加密的telnet請求,我們的應用層TelnetConnectionHandler
都能收到telnet請求內容。
var section = context.Configuration.GetSection("Kestrel");
kestrel.Configure(section).Endpoint("Telnets", endpoint => endpoint.ListenOptions.UseHttps().UseTelnet());
4.5 自定義傳輸層
在Stream設計模式裡,往往需要開發TransportStream,其包裝原始Stream且在自身的Read/Write方法裡做必要的資料解碼/編碼操作,比如SslStream(Stream inner)
,向SSlStream寫入[1,2,3,4]的資料,實際上是向inner Stream寫入了[1,2,3,4]加密後的資料。
Kestrel的傳輸層是IDuplexPipe
型別的抽象物件,我們可以把IDuplexPipe物件轉換為Stream物件,然後與既有的Stream套娃模式結合,再把最後的Stream轉為IDuplexPipe
型別,替換到kestrel的連線物件的傳輸層。
這是一個高階但不太常用的功能,想了解更多可以檢視KestrelApp.Transforms
這個專案示例。
5 如何學習Kestrel
為了大家能學習Kestrel,我在github上開源了一個kestrel開發綜合示例專案。
喜歡拿程式碼說話的同學,可以直接食用;喜歡理論指導行動的同學,可以先慢慢看專案上的文件連結,然後再一步步慢慢深入。
專案地址: https://github.com/xljiulang/KestrelApp