什么是流式传输?

流式传输是这一种以稳定持续流的形式传输数据的技术。

流式传输的使用场景

有些场景中,服务器返回的数据量较大,等待时间较长,客户端不得不等待服务器返回所有数据后,再进行相应的操作。这时候使用流式传输,可以将服务器数据碎片化,当每个数据碎片读取完成之后,就只传输完成的部分,而不需要等待所有数据都读取完成。

如何在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">&nbsp;</div>
<div class="row">
<div class="col-6">&nbsp;</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">&nbsp;</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

ASP.NET Core SignalR中的流式传输的更多相关文章

  1. ASP.NET Core MVC中Controller的Action如何直接使用Response.Body的Stream流输出数据

    在ASP.NET Core MVC中,我们有时候需要在Controller的Action中直接输出数据到Response.Body这个Stream流中,例如如果我们要输出一个很大的文件到客户端浏览器让 ...

  2. 【译】ASP.NET Core 6 中的性能改进

    原文 | Brennan Conroy 翻译 | 郑子铭 受到 Stephen Toub 关于 .NET 性能的博文的启发,我们正在写一篇类似的文章来强调 6.0 中对 ASP.NET Core 所做 ...

  3. 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?

    在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...

  4. Asp.Net Core SignalR 用泛型Hub优雅的调用前端方法及传参

    继续学习 最近一直在使用Asp.Net Core SignalR(下面成SignalR Core)为小程序提供websocket支持,前端时间也发了一个学习笔记,在使用过程中稍微看了下它的源码,不得不 ...

  5. Asp.Net Core SignalR 与微信小程序交互笔记

    什么是Asp.Net Core SignalR Asp.Net Core SignalR 是微软开发的一套基于Asp.Net Core的与Web进行实时交互的类库,它使我们的应用能够实时的把数据推送给 ...

  6. [asp.net core]SignalR一个例子

    摘要 在一个后台管理的页面想实时监控一些操作的数据,想到用signalR. 一个例子 asp.net core+signalR 使用Nuget安装包:Microsoft.AspNetCore.Sign ...

  7. ASP.NET Core SignalR

    ASP.NET Core SignalR 是微软开发的一套基于ASP.NET Core的与Web进行实时交互的类库,它使我们的应用能够实时的把数据推送给Web客户端. 功能 自动管理连接 允许同时广播 ...

  8. 微服务中的健康监测以及其在ASP.NET Core服务中实现运行状况检查

    1 .什么是健康检查? 健康检查几乎就是名称暗示的.它是一种检查您的应用程序是否健康的方法.随着越来越多的应用程序转向微服务式架构,健康检查变得尤其重要(Health Check).虽然微服务架构有很 ...

  9. ASP.NET Core MVC中的IActionFilter.OnActionExecuted方法执行时,Controller中Action返回的对象是否已经输出到Http Response中

    我们在ASP.NET Core MVC项目中有如下HomeController: using Microsoft.AspNetCore.Mvc; namespace AspNetCoreActionF ...

随机推荐

  1. 分布式缓存技术之Redis_04Redis的应用实战

    目录 1 Redis Java客户端的使用 Jedis 单点连接 Jedis sentinel连接哨兵集群 Jedis sentinel源码分析 Jedis Cluster分片环境连接 Jedis C ...

  2. python入门编程之mysql编程

    python关于mysql方面的连接编程 前提:引入mysql模块MySQLdb,即:MySQL_python-1.2.5-cp27-none-win_amd64.whl 如果要用线程池,则要引用模块 ...

  3. hdu 3478 Catch--二分图判断

    我觉得,给了初始点的话用bfs方便点,没有则dfs ||可能超片面 https://vjudge.net/contest/281085?tdsourcetag=s_pcqq_aiomsg#proble ...

  4. 20162329张旭升 2018-2019-2《网络对抗技术》第1周 Kali的安装

    目录 Kali的安装 设置与主机的文件交互 Kali的安装 1.创建虚拟机 2.选择安装语言 3.设置网络 4.设置分区 5.安装完成 设置与主机的文件交互 首先在vmware workstation ...

  5. Spring Cloud微服务笔记(四)客户端负载均衡:Spring Cloud Ribbon

    客户端负载均衡:Spring Cloud Ribbon 一.负载均衡概念 负载均衡在系统架构中是一个非常重要,并且是不得不去实施的内容.因为负载均衡对系统的高可用性. 网络压力的缓解和处理能力的扩容的 ...

  6. Python科学计算库

    Python科学计算库 一.numpy库和matplotlib库的学习 (1)numpy库介绍:科学计算包,支持N维数组运算.处理大型矩阵.成熟的广播函数库.矢量运算.线性代数.傅里叶变换.随机数生成 ...

  7. SDL中按键对应的值

    想用SDL的按键检测,网上找了半天都没找到SDL中按键的值的定义,索性自己去看头文件,在SDL_keysym.h中. 其实很多键的值和它们的ASCII码是相同的. 其他更多的用法,可以参考这篇博客:h ...

  8. 操作redis数据库 & 操作Excel & 开发接口

    操作redis数据库: string类型 1. 增 set,传俩个参数 key value(只要是字符串就行)2. 删 delete 传一个参数 key3. 修改 set 在目标key重新传参 key ...

  9. 页面的input唤醒软键盘再收起后,页面会出现软键盘高度的空白背景

    微信浏览器在版本6.7.4及以上会有这个bug:页面的input唤醒软键盘再收起后,页面会出现软键盘高度的空白背景,触摸到滚动条会消失恢复! 解决代码 后台框架嵌入iframe的情景,iframe内部 ...

  10. 团队项目:Recycle

    一.团队名字 地球保卫队(EPT) 二.团队阵容 1.项目部分 小组成员思维活跃,仅仅在一节课的时间里提出了n个颠覆软件开发界的思维的idea,最后在层层pk最后留下了八个惊世骇俗的想法.其中包括了要 ...