什么是流式传输?
流式传输是这一种以稳定持续流的形式传输数据的技术.
流式传输的使用场景
有些场景中, 服务器返回的数据量较大, 等待时间较长, 客户端不得不等待服务器返回所有数据后, 再进行相应的操作. 这时候使用流式传输, 可以将服务器数据碎片化, 当每个数据碎片读取完成之后, 就只传输完成的部分, 而不需要等待所有数据都读取完成.
如何在 ASP.NET Core SignalR 中启用流式传输
在 ASP.NET Core SignalR 中当一个 Hub 方法的返回值是 ChannelReader 或者 Task<ChannelReader>, 这个 Hub 方法自动就会变成一个流式传输 Hub 方法.
下面我们来做了一个简单的例子
创建一个 ASP.NET Core web 应用
首先我们使用 Visual Studio 2017 创建一个 ASP.NET Core Web 应用程序.
选择创建 ASP.NET Core 2.1 的 Web Application
创建 Hub
下面我们添加一个 StreamHub 类, 代码如下
- public class StreamHub : Hub
- {
- public ChannelReader<int> DelayCounter(int delay)
- {
- var channel = Channel.CreateUnbounded<int>();
- _ = WriteItems(channel.Writer, 20, delay);
- return channel.Reader;
- }
- private async Task WriteItems(ChannelWriter<int> writer, int count, int delay)
- {
- for (var i = 0; i <count; i++)
- {
- await writer.WriteAsync(i);
- await Task.Delay(delay);
- }
- writer.TryComplete();
- }
- }
DelayCounter 是一个流式传输方法, 它定义了一个延迟参数 delay, 定义了推送数据碎片的间隔时间
WriteItems 是一个私有方法, 它返回了一个 Task 对象
WriteItems 方法的最后一行
writer.TryComplete()
表明了流式传输完成
配置 SignalR
首先我们在 Startup 类的 ConfigureService 方法中添加 SignalR 服务
services.AddSignalR();
然后我们还需要为 SignalR 流添加路由, 我们需要在 Startup 类的 Configure 方法中添加如下代码:
- App.UseSignalR(routes =>
- {
- routes.MapHub<StreamHub>("/streamHub");
- });
添加 SignalR 客户端脚本库
这一步中我们需要在客户端中添加 SignalR JS 库.
这里我们需要借助 NPM 来下载 SignalR JS 库.
NPM install @aspnet/signalr
安装完成后, 我们手动将 signalr.JS 从 < projectfolder>\node_modules@aspnet\signalr\dist\browser 目录中拷贝到 wwwroot\lib\signalr 目录下
编写页面
拷贝以下代码到 Index.cshtml
- @page
- @model IndexModel
- @{
- ViewData["Title"] = "Home page";
- }
- <div class="container">
- <div class="row"> </div>
- <div class="row">
- <div class="col-6"> </div>
- <div class="col-6">
- <input type="button" id="streamButton" value="Start Streaming" />
- </div>
- </div>
- <div class="row">
- <div class="col-12">
- <hr />
- </div>
- </div>
- <div class="row">
- <div class="col-6"> </div>
- <div class="col-6">
- <ul id="messagesList"></ul>
- </div>
- </div>
- </div>
- <script src="~/lib/signalr/signalr.js"></script>
- <script src="~/js/signalrstream.js"></script>
JavaScript 中启用流式传输
在 wwwroot\JS 目录中创建一个新文件 signalrstream.JS, 代码如下
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
- };
- var connection = new signalR.HubConnectionBuilder()
- .withUrl("/streamHub")
- .build();
- document.getElementById("streamButton").addEventListener("click", (event) => __awaiter(this, void 0, void 0, function* () {
- try {
- connection.stream("DelayCounter", 500)
- .subscribe({
- next: (item) => {
- var li = document.createElement("li");
- li.textContent = item;
- document.getElementById("messagesList").appendChild(li);
- },
- complete: () => {
- var li = document.createElement("li");
- li.textContent = "Stream completed";
- document.getElementById("messagesList").appendChild(li);
- },
- error: (err) => {
- var li = document.createElement("li");
- li.textContent = err;
- document.getElementById("messagesList").appendChild(li);
- },
- });
- }
- catch (e) {
- console.error(e.toString());
- }
- event.preventDefault();
- }));
- (() => __awaiter(this, void 0, void 0, function* () {
- try {
- yield connection.start();
- }
- catch (e) {
- console.error(e.toString());
- }
- }))();
代码解释
与传统 SignalR 不同, 这里我们使用了不同的语法创建一个 SignalR 连接
- var connection = new signalR.HubConnectionBuilder()
- .withUrl("/streamHub")
- .build();
对于一般的 SignalR 连接, 我们会使用 connection.on 方法来添加监听器, 但是在使用流式传输的时候, 我们需要改用 connection.stream 方法, 这个方法有 2 个参数
Hub 方法名称, 本例中是 DelayCounter
Hub 方法的参数, 本例中是
- 500
- connection.stream("DelayCounter", 500)
- .subscribe({
- next: (item) => {
- var li = document.createElement("li");
- li.textContent = item;
- document.getElementById("messagesList").appendChild(li);
- },
- complete: () => {
- var li = document.createElement("li");
- li.textContent = "Stream completed";
- document.getElementById("messagesList").appendChild(li);
- },
- error: (err) => {
- var li = document.createElement("li");
- li.textContent = err;
- document.getElementById("messagesList").appendChild(li);
- },
- });
connection.stream 方法的返回对象中有一个 subscribe 方法, 这个方法中可以注册 3 个事件
next - 获得到一个数据碎片时执行
complete - 流式传输完成时执行
error - 流式传输异常时执行
最终效果
总结
流式传输不是一个新概念, 但是对 ASP.NET Core SignalR 来说, 这是一个非常棒的特性. 流式传输保证的用户体验的流畅, 也降低了服务器压力.
大部分程序员都知道 SignalR 不能传输过大的数据, 但是使用流式传输之后, 客户端不需要一次性等待服务器端返回所有数据, 所以如果你的项目单次请求的数据量很大, 可以考虑使用 SignalR 的流式传输改善用户体验, 减轻服务器压力.
本篇源代码地址 https://github.com/lamondlu/StreamingInSignalR
来源: https://www.cnblogs.com/lwqlun/p/9839305.html