公交车上荫蒂添的好舒服的电影-公用玩物(np双xing总受)-公用小荡货芊芊-公与妇仑乱hd-攻把受做哭边走边肉楼梯play-古装一级淫片a免费播放口

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

基于HTTP2/3的流模式消息交換如何實(shí)現(xiàn)?

freeflydom
2024年2月24日 15:32 本文熱度 1109

我想很多人已經(jīng)體驗(yàn)過GRPC提供的三種流式消息交換(Client Stream、Server Stream和Duplex Stream)模式,在.NET Core上構(gòu)建的GRPC應(yīng)用本質(zhì)上是采用HTTP2/HTTP3協(xié)議的ASP.NET Core應(yīng)用,我們當(dāng)然也可以在一個普通的ASP.NET Core應(yīng)用實(shí)現(xiàn)這些流模式。不僅如此,HttpClient也提供了響應(yīng)的支持,這篇文章通過一個簡單的實(shí)例提供了相應(yīng)的實(shí)現(xiàn),源代碼從這里下載。

一、雙向流的效果
二、[服務(wù)端]流式請求/響應(yīng)的讀寫
三、[客戶端]流式響應(yīng)/請求的讀寫

一、雙向流的效果

在提供具體實(shí)現(xiàn)之前,我們不妨先來演示一下最終的效果。我們通過下面這段代碼構(gòu)建了一個簡單的ASP.NET Core應(yīng)用,如代碼片段所示,在調(diào)用WebApplication的靜態(tài)方法CreateBuilder將WebApplicationBuilder創(chuàng)建出來后,我們調(diào)用其擴(kuò)展方法UseKestrel將默認(rèn)終結(jié)點(diǎn)的監(jiān)聽協(xié)議設(shè)置為Http1AndHttp2AndHttp3,這樣我們的應(yīng)用將提供針對不同HTTP協(xié)議的全面支持。

var url = "http://localhost:9999";
var builder = WebApplication.CreateBuilder(args);
builder.WebHost
    .UseKestrel(kestrel=> kestrel.ConfigureEndpointDefaults(listen=>listen.Protocols = HttpProtocols.Http1AndHttp2AndHttp3))
    .UseUrls(url);
var app = builder.Build();
app.MapPost("/", httpContext=> HandleRequestAsync(httpContext, async (request, writer) => {
    Console.WriteLine($"[Server]Receive request message: {request}");
    await writer.WriteStringAsync(request);
}));
await app.StartAsync();
await SendStreamRequestAsync(url, ["foo", "bar", "baz", "qux"], reply => {
    Console.WriteLine($"[Client]Receive reply message: {reply}\n");
    return Task.CompletedTask;
});

我們針對根路徑(/)注冊了一個HTTP方法為POST的路由終結(jié)點(diǎn),終結(jié)點(diǎn)處理器調(diào)用HanleRequestAsync來處理請求。這個方法提供一個Func<string, PipeWriter, Task>類型的參數(shù)作為處理器,該委托的第一個參數(shù)表示接收到的單條請求消息,PipeWriter用來寫入響應(yīng)內(nèi)容。在這里我們將接收到的消息進(jìn)行簡單格式化后將其輸出到控制臺上,隨之將其作為響應(yīng)內(nèi)容進(jìn)行回寫。

在應(yīng)用啟動之后,我們調(diào)用SendStreamRequestAsync方法以流的方式發(fā)送請求,并處理接收到的響應(yīng)內(nèi)容。該方法的第一個參數(shù)為請求發(fā)送的目標(biāo)URL,第二個參數(shù)是一個字符串?dāng)?shù)組,我們將以流的方式逐個發(fā)送每個字符串。最后的參數(shù)是一個Func<string,Task>類型的委托,用來處理接收到的響應(yīng)內(nèi)容(字符串),在這里我們依然是將格式化的響應(yīng)內(nèi)容直接打印在控制臺上。

程序啟動后控制臺上將出現(xiàn)如上圖所示的輸出,客戶端/服務(wù)端接收內(nèi)容的交錯輸出體現(xiàn)了我們希望的“雙向流式”消息交換模式。我們將在后續(xù)介紹HanleRequestAsync和SendStreamRequestAsync方法的實(shí)現(xiàn)邏輯。

二、[服務(wù)端]流式請求/響應(yīng)的讀寫

HanleRequestAsync方法定義如下。如代碼片段所示,我們利用請求的BodyReader和響應(yīng)的BodyWriter來對請求和響應(yīng)內(nèi)容進(jìn)行讀寫,它們的類型分別是PipeReader和PipeWriter。在一個循環(huán)中,在利用BodyReader將請求緩沖區(qū)內(nèi)容讀取出來后,我們將得到的ReadOnlySequence<byte>對象作為參數(shù)調(diào)用輔助方法TryReadMessage讀取單條請求消息,并調(diào)用handler參數(shù)表示的處理器進(jìn)行處理。當(dāng)請求內(nèi)容接收完畢后,循環(huán)終止。

static async Task HandleRequestAsync(HttpContext httpContext, Func<string, PipeWriter, Task> handler)
{
    var reader = httpContext.Request.BodyReader;
    var writer = httpContext.Response.BodyWriter;
    while (true)
    {
        var result = await reader.ReadAsync();
        var buffer = result.Buffer;
        while (TryReadMessage(ref buffer, out var message))
        {
            await handler(message, writer);
        }
        reader.AdvanceTo(buffer.Start, buffer.End);
        if (result.IsCompleted)
        {
            break;
        }
    }
}

由于客戶端發(fā)送的單條字符串消息長度不限,為了精準(zhǔn)地將其讀出來,我們需要在輸出編碼后的消息內(nèi)容前添加4個字節(jié)的整數(shù)來表示消息的長度。所以在如下所示的TryReadMessage方法中,我們會先將字節(jié)長度讀取出來,再據(jù)此將消息自身內(nèi)容讀取出來,最終通過解碼得到消息字符串。

static bool TryReadMessage(ref ReadOnlySequence<byte> buffer, [NotNullWhen(true)]out string? message)
{
    var reader = new SequenceReader<byte>(buffer);
    if (!reader.TryReadLittleEndian(out int length))
    {
        message = default;
        return false;
    }
    message = Encoding.UTF8.GetString(buffer.Slice(4, length));
    buffer = buffer.Slice(length + 4);
    return true;
}

響應(yīng)消息的寫入是通過如下針對PipeWriter的WriteStringAsync擴(kuò)展方法實(shí)現(xiàn)的,這里的PipeWriter就是響應(yīng)的BodyWriter,針對“Length + Payload“的消息寫入也體現(xiàn)在這里。

public static class Extensions
{
    public static ValueTask<FlushResult> WriteStringAsync(this PipeWriter writer, string content)
    {
        var length = Encoding.UTF8.GetByteCount(content);
        var span = writer.GetSpan(4 + length);
        BitConverter.TryWriteBytes(span, length);
        Encoding.UTF8.GetBytes(content, span.Slice(4));
        writer.Advance(4 + length);
        return writer.FlushAsync();
    }
}

三、[客戶端]流式響應(yīng)/請求的讀寫

客戶端利用HttpClient發(fā)送請求。針對HttpClient的請求通過一個HttpRequestMessage對象表示,其主體內(nèi)容體現(xiàn)為一個HttpContent。流式請求的發(fā)送是通過如下這個StreamContent類型實(shí)現(xiàn)的,它派生于HttpContent。我們重寫了SerializeToStreamAsync方法,利用自定義的StreamContentWriter將內(nèi)容寫入請求輸出流。

public class StreamContent(StreamContentWriter writer) : HttpContent
{
    private readonly StreamContentWriter _writer = writer;
    protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context) 
        => _writer.SetOutputStream(stream).WaitAsync();
    protected override bool TryComputeLength(out long length) => (length = -1) != -1;
}
public class StreamContentWriter
{
    private readonly TaskCompletionSource<Stream> _streamSetSource = new();
    private readonly TaskCompletionSource _streamEndSource = new();
    public StreamContentWriter SetOutputStream(Stream outputStream)
    {
        _streamSetSource.SetResult(outputStream);
        return this;
    }
    public async Task WriteAsync(string content)
    {
        var stream = await _streamSetSource.Task;
        await PipeWriter.Create(stream).WriteStringAsync(content);
    }
    public void Complete() => _streamEndSource.SetResult();
    public Task WaitAsync() => _streamEndSource.Task;
}

StreamContentWriter提供了四個方法,SetOutputStream方法用來設(shè)置請求輸出流,上面重寫的SerializeToStreamAsync調(diào)用了此方法。單條字符串消息的寫入實(shí)現(xiàn)在WriteAsync方法中,它最終調(diào)用的依然是上面提供的WriteStringAsync擴(kuò)展方法。整個流式請求的過程通過一個TaskCompletionSource對象提供的Task來表示,當(dāng)客戶端完成所有輸出后,會調(diào)用Complete方法,該方法進(jìn)一步調(diào)用這個TaskCompletionSource對象的SetResult方法。由于WaitAsync方法返回TaskCompletionSource對象提供的Task,SerializeToStreamAsync方法會調(diào)用此方法等待”客戶端輸出流“的終結(jié)。

如下的代碼片段體現(xiàn)了SendStreamRequestAsync方法的實(shí)現(xiàn)。在這里我們創(chuàng)建了一個表示流式請求的HttpRequestMessage對象,我們將協(xié)議版本設(shè)置為HTTP2,作為主體內(nèi)容的HttpContent正式根據(jù)StreamContentWriter對象創(chuàng)建的StreamContent對象。

 static async Task SendStreamRequestAsync(string url,string[] lines, Func<string, Task> handler)
{
    using var httpClient = new HttpClient();
    var writer = new StreamContentWriter();
    var request = new HttpRequestMessage(HttpMethod.Post, url)
    {
        Version = HttpVersion.Version20,
        VersionPolicy = HttpVersionPolicy.RequestVersionExact,
        Content = new StreamingWeb.StreamContent(writer)
    };
    var task = httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
    _ = Task.Run(async () =>
    {
        var response = await task;
        var reader = PipeReader.Create(await response.Content.ReadAsStreamAsync());
        while (true)
        {
            var result = await reader.ReadAsync();
            var buffer = result.Buffer;
            while (TryReadMessage(ref buffer, out var message))
            {
                await handler(message);
            }
            reader.AdvanceTo(buffer.Start, buffer.End);
            if (result.IsCompleted)
            {
                break;
            }
        }
    });
    foreach (string line in lines)
    {
        await writer.WriteAsync($"{line} ({DateTimeOffset.UtcNow})");
        await Task.Delay(1000);
    }
    writer.Complete();
}

我們將這個HttpRequestMessage作為請求利用HttpClient發(fā)送出去,實(shí)際上發(fā)送的內(nèi)容最終是通過調(diào)用StreamContentWriter對象的WriteAsync方法輸出的,我們每隔1秒發(fā)送一條消息。HttpClient將請求發(fā)出去之后會得到一個通過HttpResponseMessage對象表示的響應(yīng),在一個異步執(zhí)行的Task中,我們根據(jù)響應(yīng)流創(chuàng)建一個PipeReader對象,并在一個循環(huán)中調(diào)用上面定義的TryReadMessage方法逐條讀取接收到的單條消息進(jìn)行處理。


本文作者:Artech,轉(zhuǎn)自https://www.cnblogs.com/artech/p/18021662/streaming_messaging


該文章在 2024/2/24 15:32:33 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 国产中文字幕一级片在线观看 | 成人无码www免费视频樱花 | 精品国产亚洲人成在线观看 | 精品日本一区二区三区在线观 | 成在人线av无码高潮喷水 | 成人合成mv福利视频 | 国产精品免费aⅴ片在线观 国产精品免费aⅴ片在线观看 | 97人妻碰碰视频免费上线 | 国产xxxⅹ野性xxxxhd | av男人的天堂在线观看国产 | 91高清色网一二三区 | 国产91调教丝袜在线 | 国产精品白浆在线播放 | 国产欧美日韩综合精品无毒 | 国产交换配乱婬视频偷大叼 | 国产激情视频在线观看免费播放 | 成年人射精的黄色视频网站 | 加勒比久久综合久久 | 超薄肉色丝袜一二三四区 | 国产区在线视频就爱 | 国产精品人成a片一区二区 国产精品人成a片一区二区下载 | 国产一区二区在线 | 韩国精品在线观看 | 99精品国产自产在线观看 | 国产精品亚洲综合久久小说 | av一区二区三区传媒 | 国产在线欧美中文字幕免费观 | 成人午夜福利免费专区无码 | 国产精品一级久久c片 | 国产高潮流白浆喷水在线观看 | 激情视频在线观看一区 | 国产精品直播手机在线 | 成人a毛片久久免费播放国语 | 国产亚洲欧美激情久久电影 | 国产麻无矿码直接进入 | 国产精品一区12p | a级毛片无码免费真人 | 岛国av一区二区无码 | 国产成人欧美综合在线 | 精品熟妇av一区二区三区四区 | 91麻豆精品无码人妻系到 |