SignalR的基本概念

前言

最近在自己的项目中实践了SignalR的使用,asp.net core 2.1版本的时候建立了对SignalR的支持,SignalR的可使用Web Socket, Server Sent Events 和 Long Polling作为底层传输方式.SignalR基于这三种技术构建, 抽象于它们之上, 它让你更好的关注业务问题而不是底层传输技术问题.它分为了客户端和服务端,服务端支持到asp.net core和asp.net,客户端分语言支持java、javascript、C#等。

SignalR会优先使用websocket连接,浏览器因为版本问题没办法支持的话会使用回落机制,转用SSE或者长轮询的方式来进行。SignalR将上述机制进行封装,采用了一致的API进行编程,使得开发人员不必纠结与技术细节的选型,提高了编程效率。

SignalR采用RPC(remote procedure call)的编程范式来进行客户端和服务端之间的调用。

SignalR利用底层传输来让服务器可以调用客户端的方法, 反之亦然, 这些方法可以带参数, 参数也可以是复杂对象, SignalR负责序列化和反序列化.

HUB

Hub是SignalR的一个组件, 它运行在ASP.NET Core应用里. 所以它是服务器端的一个类.

Hub使用RPC接受从客户端发来的消息, 也能把消息发送给客户端. 所以它就是一个通信用的Hub.

在ASP.NET Core里, 自己创建的Hub类需要继承于基类Hub.

在Hub类里面, 我们就可以调用所有客户端上的方法了. 同样客户端也可以调用Hub类里的方法.

之前说过方法调用的时候可以传递复杂参数, SignalR可以将参数序列化和反序列化. 这些参数被序列化的格式叫做Hub 协议, 所以Hub协议就是一种用来序列化和反序列化的格式.

Hub协议的默认协议是JSON, 还支持另外一个协议是MessagePack. MessagePack是二进制格式的, 它比JSON更紧凑, 而且处理起来更简单快速, 因为它是二进制的.

此外, SignalR也可以扩展使用其它协议..

横向扩展

随着系统的运行, 有时您可能需要进行横向扩展. 就是应用运行在多个服务器上.

这时负载均衡器会保证每个进来的请求按照一定的逻辑分配到可能是不同的服务器上.

在使用Web Socket的时候, 没什么问题, 因为一旦Web Socket的连接建立, 就像在浏览器和那个服务器之间打开了隧道一样, 服务器是不会切换的.

但是如果使用Long Polling, 就可能有问题了, 因为使用Long Polling的情况下, 每次发送消息都是不同的请求, 而每次请求可能会到达不同的服务器. 不同的服务器可能不知道前一个服务器通信的内容, 这就会造成问题.

针对这个问题, 我们需要使用Sticky Sessions (粘性会话).

Sticky Sessions 貌似有很多中实现方式, 但是主要是下面要介绍的这种方式.

作为第一次请求的响应的一部分, 负载均衡器会在浏览器里面设置一个Cookie, 来表示使用过这个服务器. 在后续的请求里, 负载均衡器读取Cookie, 然后把请求分配给同一个服务器.

在ASP.NET Core 中使用SignalR

我在项目中使用SignalR主要是用来通知用户事件创建的结果。

首先你要创建一个HUB:

public class EventMessageHub : Hub<IEventNotification>
{ }

我就建立了一个空的Hub,它继承自Hub<T>,带泛型的Hub基类表示他是一个强类型的Hub,那么T就是一个接口,这个接口定了了一些发送消息的方法,我的IEventNotification的定义如下:

public interface IEventNotification
{
Task Notify(string principal,string time, string message);
}

我在里面定义了一个方法,用于发送消息到客户端

定义好Hub后你要做的是为这个Hub创建一个URL,使得客户端可以访问得到这个Hub,只有访问到这个Hub才能执行远程的从客户端调用服务端的代码。配置如下:

①首先在StartUp方法中的ConfigureServices方法中配置SignalR的服务:

//signalR
services.AddSignalR();

②然后在Configure方法中配置SignalR的中间件:

app.UseSignalR(route =>
{
route.MapHub<EventMessageHub>("/eventMessage");
});

这样,就把这个Hub配置好,你就可以从服务端发送消息给客户端了。

但是SignalR还需要有认证的过程,SignalR在asp.net core中使用的认证是分开的,也就是说,asp.net core本身做好认证后,SignalR是不会识别这个认证的,需要单独为SignalR配置认证。

SignalR中有一个接口,是IUserIdProvider,这个接口用于标志当前的用户ID,他的定义如下:

 public interface IUserIdProvider
{
//
// 摘要:
// Gets the user ID for the specified connection.
//
// 参数:
// connection:
// The connection to get the user ID for.
//
// 返回结果:
// The user ID for the specified connection.
string GetUserId(HubConnectionContext connection);
}

可见该接口包含一个方法GetUserId,该方法用于标志当前用户。

我给了一个该接口的实现:

public class SignalRUserIdProvider : IUserIdProvider
{
public string GetUserId(HubConnectionContext connection)
{
//tokenvalidationparameter中配置的RoleClaimType和NameClaimType在这里不起作用,要用原始的claim
var orgIdentifier = connection.User.FindFirst("orgIdentifier")?.Value;
if (string.IsNullOrEmpty(orgIdentifier))
{
return null;
}
return orgIdentifier;
}
}

connection实参上有一个User属性,这个属性的类型是ClaimsPrincipal,这个类型属于asp.net core认证框架下的基本概念,你可以上网查,不管你用什么类型的认证方式,最后识别用户都要转换成这个ClaimsPrincipal.

那想要获取这个User属性的值的话就要进行一些配置。我的项目是前后端分离,采用的是jwt token bearer的认证方式,所以,在这里我只讲这种类型的,等到用mvc开发使用cookie的时候再回来补充。

有一个http的请求头是Authorization(授权的意思,我很奇怪这个头的名字为什么不是Authentication),该请求头标志了认证的类型,比如Basic,或Bearer,格式就是Authorization Bearer XXXXjhhhhhX........这样子。那我们进行认证授权的时候就要配置这个头,请求到达了服务端后服务端就会解析这个头,然后识别发送该请求的客户,下面是我服务端配置的认证的一些代码:

 private static void AddJwt(IServiceCollection services, IConfiguration config)
{
//添加认证类型
services.AddAuthentication(option =>
{
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(option =>
{
option.TokenValidationParameters = new TokenValidationParameters
{
//RoleClaimType和NameClaimType的作用是将自定义的claim转化成标准的claim
RoleClaimType = "orgRole",
NameClaimType = "orgIdentifier",
//ensure the token was issued by a trusted authorization server (default value true)
ValidateIssuer = true,
ValidIssuer = configSection.GetSection("Issuer").Value,
//ensure the token audience matches our audience value(default value true)
ValidateAudience = true,
ValidAudience = configSection.GetSection("Audience").Value,
ValidateIssuerSigningKey = true,
//specify the key used to sign the token
IssuerSigningKey = signingKey,
RequireSignedTokens = true,
//ensure the token hasn't expired
ValidateLifetime = true,
RequireExpirationTime = true,
//clock skew compensates fro server time drift. we recommend 5 minutes or less
ClockSkew = TimeSpan.FromMinutes(), };
//signalr需要这个配置
option.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (path.StartsWithSegments("/eventMessage"))
{
var accessToken = context.Request.Query["access_token"];
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
}

上面展示了我项目中配置的认证方式,前面的TokenValidationParameters主要用于配置asp.net core服务端认证用户的方式,后面的option.Events = new JwtBearerEvents()。。。。这个就是用来配置SignalR发送请求的时候是不会携带http header头的,所以你不能使用Authorization Bearer xxxooooxxx....这种方式来对服务端进行认证,SignalR采用的都是这样的:

WebSocket connected to ws://localhost:5003/eventMessage?id=RydCi063Wh0kZLzivQLG-w&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcmdJZCI6ImU1MjY0ODRiLWNhYjUtNGU1Yy04MTBiLWEwYzQ3MDljYmU3ZCIsImp0aSI6IjE3ZmM2YzY3LTM0NTMtNDA0Yy05MWQ1LWI0MDZjZTZmYzc0MyIsImlhdCI6MTU1NjYxMzg1Mywib3JnUm9sZSI6InNlY29uZGFyeWFkbWluIiwib3JnTmFtZSI6IuS4reWbvemTtuihjOWMheWktOWIhuihjOS_oeaBr-enkeaKgOmDqCIsIm9yZ0lkZW50aWZpZXIiOiJBNDY0MCIsIm9yZzIiOiJCMzQyNiIsIm1hbmFnZW1lbnRMaW5lSWQiOiI3NWIzZjNjMS0zMTU3LTRjMjMtYjc0Ni0yMjhiNDY3OWUwNjQiLCJuYmYiOjE1NTY2MTM4NTMsImV4cCI6MTU1NjYxNTY1MywiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDo0MjAwIn0.pmRqvqNGXATprIhmDTCpMGJ-tutgdv4V6GZ3GzMwo3s.

从上面可以看到token是被放到了url的查询字段中的,option.TokenValidationParameters = new TokenValidationParameters{.........}这种方式只适用于http的Authorization头部携带token的方式,所以你就要为你的SignalR设置一套程序来让他能够识别当前用户,也就是认证的过程。上面的代码中的OnMessageReceived 就是要干这个事儿。

也可以看出,jwt token本身就相当于是明文保存,只不过是用base64编码的,你随便把一个jwt token粘贴到一个支持base64 decode的网站上面都可以获取这里面的详细信息。

当我们做好这一步后,我们就可以识别当前用户,在我的项目中我要向特定的用户发送消息:

 await _hubContext.Clients.User(notification.TargetOrgIdentifier).Notify(notification.RequestOrgNam,
DateTime.Now.ToLocalTime().ToString(),
$"发起了{notification.ToString()}");

稍微说明一下这个代码的意图:

_hubContext.Clients.User(string userId)这个方法接受一个UserId,用于标志是哪一个客户,这个UserId我们在前面的IUserIdProvider就已经配置好,只要这个UserId对应的客户端连接上Hub,那么我们就能给这个客户端发送消息。Notify是我们使用强类型的Hub<T>中T接口定义的方法,使用强类型的Hub的好处就是你不用硬编码一个方法,这样就不会因为书写硬编码产生错误。

下面在说一下不使用强类型Hub来编程Hub

public class ChatHub : Hub
{
public async Task SendMessageAsync(string userId, string message)
{
await this.Clients.User(userId).SendAsync("ReciveMessage", message);
}
}

我定义了一个ChatHub的hub,这个hub里面的SendMessageAsync是一种硬编码的写法,SendMessageAsync可以被客户端调用,而方法中的Clients.User(userId).SendAsync(.....方法本身可以作为一个事件在客户端进行监听。比如说JavaScript客户端可以写这样的代码来监听从ChatHub发送过来的消息:

async connect() {
this.connection = new HubConnectionBuilder()
.withUrl(`${environment.api_url}/eventMessage`, { accessTokenFactory: this.jwtHelper.tokenGetter })
.build();//①首先创建一个远程服务器的连接,使用的是建造者模式来创建
this.connection.on('ReciveMessage', (principal: string, time: string, message: string) => {
this.messageList.push({ principal, time, message });
});//②然后让这个连接来监听服务端传回来的事件
await this.connection.start();//③配置好以后就启动这个连接
}

上面的代码使用TypeScript书写,connection是类中的一个私有字段,类型就是HubConnection。

这就是我使用SignalR的过程。后续使用过程中再补充吧。

asp.net core实时库:SignalR(1)的更多相关文章

  1. ASP.NET Core的实时库: SignalR简介及使用

    大纲 本系列会分为2-3篇文章. 第一篇介绍了SignalR的预备知识和原理 本文介绍SignalR以及ASP.NET Core里使用SignalR. 本文的内容: 介绍SignalR 在ASP.NE ...

  2. ASP.NET Core的实时库: SignalR -- 预备知识

    大纲 本系列会分为2-3篇文章. 第一篇介绍SignalR的预备知识和原理 然后会介绍SignalR和如何在ASP.NET Core里使用SignalR. 本文的目录如下: 实时Web简述 Long ...

  3. 在ASP.NET Core下使用SignalR技术

    一.前言 上次我们讲到过如何在ASP.NET Core中使用WebSocket,没有阅读过的朋友请参考 WebSocket in ASP.NET Core 文章 .这次的主角是SignalR它为我们提 ...

  4. ASP.NET Core 2.0 SignalR 示例

    # 一.前言 上次讲SignalR还是在<[在ASP.NET Core下使用SignalR技术](http://dotnet.ren/2017/02/21/%E5%9C%A8ASP-NET-Co ...

  5. ServiceStack.Redis 的 ASP.NET Core 扩展库

    给大家安利一款 ServiceStack.Redis 的 ASP.NET Core 扩展库,它是基于 ServiceStack.Redis.Core 开发的. 简单易用,开源免费,使用ASP.NET ...

  6. Log4net 的 ASP.NET Core 扩展库

    给大家安利一款 log4net 的 ASP.NET Core 扩展库,它是基于 log4net 开发的. 简单易用,开源免费,使用ASP.NET Core自身提供的DI容器来实现服务的注册和消费.直接 ...

  7. ASP.NET Core扩展库

    亲爱的.Neter们,在我们日复一日的编码过程中是不是会遇到一些让人烦恼的事情: 日志配置太过复杂,各种模板.参数也搞不清楚,每次都要去查看日志库的文档,还需要复制粘贴一些重复代码,好无赖 当需要类型 ...

  8. ASP.NET Core扩展库之日志

        上一篇我们对Xfrogcn.AspNetCore.Extensions扩展库功能进行了简单的介绍,从这一篇文章开始,我将逐步介绍扩展库中的核心功能.     日志作为非业务的通用领域基础功能, ...

  9. 在 ASP.NET Core 中使用 SignalR

    https://weblogs.asp.net/ricardoperes/signalr-in-asp-net-core 作者:Ricardo Peres 译者:oopsguy.com 介绍 Sign ...

随机推荐

  1. 此实现不是Windows平台FIPS验证的加密算法的一部分

    运行wpf程序,出现错误“此实现不是Windows平台FIPS验证的加密算法的一部分”. 解决方法: 1.在window中打开功能里输入regedit,回车打开注册器: 2.进入如下路径中 HKEY_ ...

  2. Java 设计模式系列(十五)观察者模式(Observer)

    Java 设计模式系列(十五)观察者模式(Observer) Java 设计模式系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Java ...

  3. 12个优秀的国外Material Design网站案例

    眼看2017年就快完了,你是不是还没完全搞懂Material Design呢?是嫌说明文档太长,还是觉得自己英文不好?都没关系,小编今天给大家整理了一份干货满满的学习笔记,并列举了一些国外的Mater ...

  4. ACTIVITI 5.14事件监听器的BUG

    在ACTIVITI 5.14中,测试内部子流程时发现事件定义的事件监听器不能触发. <activiti:executionListener event="start" del ...

  5. mysql 开通远程连接

    使用localhost好用,但是改成ip地址后不好用,执行sql语句做如下修改: update user set host = '%' where user = 'root'; flush privi ...

  6. 为iOS项目添加Daily Build

    很多人在说到Daily Build的时候总是喜欢背书.背书就背书吧,总比混迹软件行业连书都没看过的强.很久以前遇到一个奇葩.每次到代码提交测的通知就着急忙慌的催促组员赶紧干活,开始严重加班,晚饭都不吃 ...

  7. Hdu1051 Wooden Sticks 2017-03-11 23:30 62人阅读 评论(0) 收藏

    Wooden Sticks Problem Description There is a pile of n wooden sticks. The length and weight of each ...

  8. Android-HttpUtil工具类

    Http(Java 版 HttpURLConnection)请求的相关工具类 public class HttpUtil { private static final int TIMEOUT_IN_M ...

  9. 自我介绍及注册github和上传文件

    自我介绍: 周侃 年龄20 喜好:玩游戏,赚钱,交际 理想:想要改变中国手游界颓靡的时代,让它进入新次元. 注册github,以及上传文件: 今天给大家来讲解下如何注册githup 当我们打开gith ...

  10. MySQL问题排查工具介绍

    本总结来自美团内部分享,屏蔽了内部数据与工具 知识准备 索引 索引是存储引擎用于快速找到记录的一种数据结构 B-Tree,适用于全键值,键值范围或键最左前缀:(A,B,C): A, AB, ABC,B ...