1、什么是SignalR

  ASP.NET SignalR 是一个面向 ASP.NET 开发人员的库,可简化向应用程序添加实时 Web 功能的过程。 实时 Web 功能是让服务器代码在可用时立即将内容推送到连接的客户端,而不是让服务器等待客户端请求新数据。

  SignalR使用的三种底层传输技术分别是Web Socket, Server Sent Events 和 Long Polling, 它让你更好的关注业务问题而不是底层传输技术问题。

  WebSocket是最好的最有效的传输方式, 如果浏览器或Web服务器不支持它的话(IE10之前不支持Web Socket), 就会降级使用SSE, 实在不行就用Long Polling。

  (现在也很难找到不支持WebSocket的浏览器了,所以我们一般定义必须使用WebSocket)

2、我们做一个聊天室,实现一下SignalR前后端通讯

  由简入深,先简单实现一下 

  2.1 服务端Net5

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Threading.Tasks; namespace ServerSignalR.Models
{
public class ChatRoomHub:Hub
{
public override Task OnConnectedAsync()//连接成功触发
{
return base.OnConnectedAsync();
} public Task SendPublicMsg(string fromUserName,string msg)//给所有client发送消息
{
string connId = this.Context.ConnectionId;
string str = $"[{DateTime.Now}]{connId}\r\n{fromUserName}:{msg}";
return this.Clients.All.SendAsync("ReceivePublicMsg",str);//发送给ReceivePublicMsg方法,这个方法由SignalR机制自动创建
}
}
}

  Startup添加

        static string _myAllowSpecificOrigins = "MyAllowSpecificOrigins";
public void ConfigureServices(IServiceCollection services)
{ services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ServerSignalR", Version = "v1" });
});
services.AddSignalR();
services.AddCors(options =>
{
options.AddPolicy(_myAllowSpecificOrigins, policy =>
{
policy.WithOrigins("http://localhost:4200")
.AllowAnyHeader().AllowAnyMethod().AllowCredentials();
});
});
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ServerSignalR v1"));
}
app.UseCors(_myAllowSpecificOrigins);
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub");
});
}

  2.2 前端Angular

    引入包

npm i --save @microsoft/signalr

    ts:

import { Component, OnInit } from '@angular/core';
import * as signalR from '@microsoft/signalr';
import { CookieService } from 'ngx-cookie-service'; @Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
msg = '';
userName='kxy'
public messages: string[] = [];
public hubConnection: signalR.HubConnection; constructor(
private cookie: CookieService
) {this.hubConnection=new signalR.HubConnectionBuilder()
.withUrl('https://localhost:44313/Hubs/ChatRoomHub',
{
skipNegotiation:true,//跳过三个协议协商
transport:signalR.HttpTransportType.WebSockets,//定义使用WebSocket协议通讯
}
)
.withAutomaticReconnect()
.build();
this.hubConnection.on('ReceivePublicMsg',msg=>{
this.messages.push(msg);
console.log(msg);
});
}
ngOnInit(): void {
}
JoinChatRoom(){
this.hubConnection.start()
.catch(res=>{
this.messages.push('连接失败');
throw res;
}).then(x=>{
this.messages.push('连接成功');
});
}
SendMsg(){
if(!this.msg){
return;
}
this.hubConnection.invoke('SendPublicMsg', this.userName,this.msg);
}
}

  这样就简单实现了SignalR通讯!!!

  有一点值得记录一下

    问题:强制启用WebSocket协议,有时候发生错误会被屏蔽,只是提示找不到/连接不成功

    解决:可以先不跳过协商,调试完成后再跳过

3、引入Jwt进行权限验证

安装Nuget包:Microsoft.AspNetCore.Authentication.JwtBearer

  Net5的,注意包版本选择5.x,有对应关系

  Startup定义如下

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.OpenApi.Models;
using ServerSignalR.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using JwtHelperCore; namespace ServerSignalR
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
} public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container.
static string _myAllowSpecificOrigins = "MyAllowSpecificOrigins";
public void ConfigureServices(IServiceCollection services)
{ services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ServerSignalR", Version = "v1" });
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;//是否需要https
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,//是否验证Issuer
ValidateAudience = false,//是否验证Audience
ValidateLifetime = true,//是否验证失效时间
ValidateIssuerSigningKey = true,//是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("VertivSecurityKey001")),//拿到SecurityKey
};
options.Events = new JwtBearerEvents()//从url获取token
{
OnMessageReceived = context =>
{
if (context.HttpContext.Request.Path.StartsWithSegments("/Hubs/ChatRoomHub"))//判断访问路径
{
var accessToken = context.Request.Query["access_token"];//从请求路径获取token
if (!string.IsNullOrEmpty(accessToken))
context.Token = accessToken;//将token写入上下文给Jwt中间件验证
}
return Task.CompletedTask;
}
};
}
); services.AddSignalR(); services.AddCors(options =>
{
options.AddPolicy(_myAllowSpecificOrigins, policy =>
{
policy.WithOrigins("http://localhost:4200")
.AllowAnyHeader().AllowAnyMethod().AllowCredentials();
});
});
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ServerSignalR v1"));
} app.UseCors(_myAllowSpecificOrigins);
app.UseHttpsRedirection(); app.UseRouting(); //Token 授权、认证
app.UseErrorHandling();//自定义的处理错误信息中间件
app.UseAuthentication();//判断是否登录成功
app.UseAuthorization();//判断是否有访问目标资源的权限 app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub");
endpoints.MapControllers();
});
}
}
}

  红色部分为主要关注代码!!!

  因为WebSocket无法自定义header,token信息只能通过url传输,由后端获取并写入到上下文

  认证特性使用方式和http请求一致:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Linq;
using System.Threading.Tasks; namespace ServerSignalR.Models
{
[Authorize]//jwt认证
public class ChatRoomHub:Hub
{ public override Task OnConnectedAsync()//连接成功触发
{
return base.OnConnectedAsync();
} public Task SendPublicMsg(string msg)//给所有client发送消息
{
var roles = this.Context.User.Claims.Where(x => x.Type.Contains("identity/claims/role")).Select(x => x.Value).ToList();//获取角色
var fromUserName = this.Context.User.Identity.Name;//从token获取登录人,而不是传入(前端ts方法的传入参数也需要去掉)
string connId = this.Context.ConnectionId;
string str = $"[{DateTime.Now}]{connId}\r\n{fromUserName}:{msg}";
return this.Clients.All.SendAsync("ReceivePublicMsg",str);//发送给ReceivePublicMsg方法,这个方法由SignalR机制自动创建
}
}
}

  然后ts添加

  constructor(
private cookie: CookieService
) {
var token = this.cookie.get('spm_token');
this.hubConnection=new signalR.HubConnectionBuilder()
.withUrl('https://localhost:44313/Hubs/ChatRoomHub',
{
skipNegotiation:true,//跳过三个协议协商
transport:signalR.HttpTransportType.WebSockets,//定义使用WebSocket协议通讯
accessTokenFactory:()=> token.slice(7,token.length)//会自动添加Bearer头部,我这里已经有Bearer了,所以需要截掉
}
)
.withAutomaticReconnect()
.build();
this.hubConnection.on('ReceivePublicMsg',msg=>{
this.messages.push(msg);
console.log(msg);
});
}

4、私聊

  Hub

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Threading.Tasks; namespace ServerSignalR.Models
{
[Authorize]//jwt认证
public class ChatRoomHub:Hub
{
private static List<UserModel> _users = new List<UserModel>();
public override Task OnConnectedAsync()//连接成功触发
{
var userName = this.Context.User.Identity.Name;//从token获取登录人
_users.Add(new UserModel(userName, this.Context.ConnectionId));
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
var userName = this.Context.User.Identity.Name;//从token获取登录人
_users.RemoveRange(_users.FindIndex(x => x.UserName == userName), 1);
return base.OnDisconnectedAsync(exception);
} public Task SendPublicMsg(string msg)//给所有client发送消息
{
var fromUserName = this.Context.User.Identity.Name;
//var ss = this.Context.User!.FindFirst(ClaimTypes.Name)!.Value;
string str = $"[{DateTime.Now}]\r\n{fromUserName}:{msg}";
return this.Clients.All.SendAsync("ReceivePublicMsg",str);//发送给ReceivePublicMsg方法,这个方法由SignalR机制自动创建
} public Task SendPrivateMsg(string destUserName, string msg)
{
var fromUser = _users.Find(x=>x.UserName== this.Context.User.Identity.Name);
var toUser = _users.Find(x=>x.UserName==destUserName);
string str = $"";
if (toUser == null)
{
msg = $"用户{destUserName}不在线";
str = $"[{DateTime.Now}]\r\n系统提示:{msg}";
return this.Clients.Clients(fromUser.WebScoketConnId).SendAsync("ReceivePrivateMsg", str);
}
str = $"[{DateTime.Now}]\r\n{fromUser.UserName}-{destUserName}:{msg}";
return this.Clients.Clients(fromUser.WebScoketConnId,toUser.WebScoketConnId).SendAsync("ReceivePrivateMsg", str);
}
}
}

  TS:

//加一个监听
this.hubConnection.on('ReceivePublicMsg', msg => {
this.messages.push('公屏'+msg);
console.log(msg);
});
this.hubConnection.on('ReceivePrivateMsg',msg=>{
this.messages.push('私聊'+msg);
console.log(msg);
}); //加一个发送
if (this.talkType == 1)
this.hubConnection.invoke('SendPublicMsg', this.msg);
if (this.talkType == 3){
console.log('11111111111111');
this.hubConnection.invoke('SendPrivateMsg',this.toUserName, this.msg);
}

5、在控制器中使用Hub上下文

  Hub链接默认30s超时,正常情况下Hub只会进行通讯,而不再Hub里进行复杂业务运算

  如果涉及复杂业务计算后发送通讯,可以将Hub上下文注入外部控制器,如

namespace ServerSignalR.Controllers
{
//[Authorize]
public class HomeController : Controller
{
private IHubContext<ChatRoomHub> _hubContext;
public HomeController(IHubContext<ChatRoomHub> hubContext)
{
_hubContext = hubContext;
}
[HttpGet("Welcome")]
public async Task<ResultDataModel<bool>> Welcome()
{
await _hubContext.Clients.All.SendAsync("ReceivePublicMsg", "欢迎");
return new ResultDataModel<bool>(true);
}
}
}

  

  至此,感谢关注!!

SignalR WebSocket通讯机制的更多相关文章

  1. 深入浅出ghostbuster剖析NodeJS与PhantomJS的通讯机制

    深入浅出ghostbuster剖析NodeJS与PhantomJS的通讯机制 蔡建良 2013-11-14 一. 让我们开始吧 通过命令行来执行 1) 进行命令窗口: cmd 2) 进入resourc ...

  2. 浅谈HTML5 WebSocket的机制

    回想上一章 在上一章<为什么我们须要HTML5 WebSocket>中,我简单的介绍了下WebSocket的前世今生.相信大家已对WebSocket有了初步的了解.那么今天我们继续深入学习 ...

  3. 【工业串口和网络软件通讯平台(SuperIO)教程】八.SuperIO通讯机制与设备驱动对接的说明

    SuperIO相关资料下载:http://pan.baidu.com/s/1pJ7lZWf 1.1    通讯机制说明 通讯的总体机制采用呼叫应答方式,就是上位机软件主动发送请求数据命令,下位机终端接 ...

  4. 【工业串口和网络软件通讯平台(SuperIO)教程】一.通讯机制

    1.1    应用场景 通讯平台的交互对象包括两方面:第一.与硬件产品交互.第二.与软件产品交互.基本这两方面考虑,通讯平台一般会应用在两个场景: 1)通讯平台应用在PC机上 主要应用在自动站的工控机 ...

  5. ActiveMQ之 TCP通讯机制

    ActiveMQ支持多种通讯协议TCP/UDP等,我们选取最常用的TCP来分析ActiveMQ的通讯机制.首先我们来明确一个概念:  客户(Client):消息的生产者.消费者对ActiveMQ来说都 ...

  6. webSocket通讯

    1.使用facebook第三方SRWebSocket进行websocket通讯. pod 'SocketRocket' 2.通讯地址: ws://192.168.1.128:18882/ws 注意:s ...

  7. 【node+小程序+web端】简单的websocket通讯

    [node+小程序+web端]简单的websocket通讯 websoket是用来做什么的? 聊天室 消息列表 拼多多 即时通讯,推送, 实时交互 websoket是什么 websocket是一个全新 ...

  8. Flask 实现 WebSocket 通讯---群聊和私聊

    一.WebSocket介绍 WebSocket是一种在单个TCP连接实现了服务端和客户端进行双向文本或二进制数据通信的一种通信的协议. WebSocket使得客户端和服务器之间的数据交换变得更加简单, ...

  9. 前端使用express+node实现接口模拟及websocket通讯

    简述如何使用node+express实现接口连接及入门websocket通讯.使用技术栈:node + express + typescript + websocket. 1.接口实现 这里描述前端如 ...

  10. websocket通讯协议(10版本)简介

    前言: 工作中用到了websocket 协议10版本的,英文的协议请看这里: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotoc ...

随机推荐

  1. IDEA配置JDK版本的地方, 适用于Compilation failed: internal java compiler error

    错误原因: 1. 编译版本不匹配 2.当前项目jdk版本不支持 解决方法 查看项目的jdk 查看工程的jdk 查看java编译器版本 讲这些改成自己需要的版本, 一般就可以解决编译版本出现的错误

  2. dart基础---->函数传值

    1. string type main(List<String> args) { String name = "huhx"; changIt(name); print( ...

  3. 【JavaScript快速排序算法】不同版本原理分析

    说明 快速排序(QuickSort),又称分区交换排序(partition-exchange sort),简称快排.快排是一种通过基准划分区块,再不断交换左右项的排序方式,其采用了分治法,减少了交换的 ...

  4. SpringBoot的EnableCaching简述

    Spring Boot中的EnableCaching简述 spring boot中自带有数据缓存机制,主要通过其org.springframework.cache包下的各种类来实现. EnableCa ...

  5. 全网最详细中英文ChatGPT-GPT-4示例文档-智能AI写作从0到1快速入门——官网推荐的48种最佳应用场景(附python/node.js/curl命令源代码,小白也能学)

    目录 Introduce 简介 setting 设置 Prompt 提示 Sample response 回复样本 API request 接口请求 python接口请求示例 node.js接口请求示 ...

  6. 安装部署keepalived的HA环境

    每一台配置下keepalived #master01 配置: cat >/etc/keepalived/keepalived.conf<<"EOF" ! Conf ...

  7. 生产事故-记一次特殊的OOM排查

    入职多年,面对生产环境,尽管都是小心翼翼,慎之又慎,还是难免捅出篓子.轻则满头大汗,面红耳赤.重则系统停摆,损失资金.每一个生产事故的背后,都是宝贵的经验和教训,都是项目成员的血泪史.为了更好地防范和 ...

  8. Android开发_记事本(1)

    一些知识 Textview TextView中有下述几个属性: id:为TextView设置一个组件id,根据id,我们可以在Java代码中通过findViewById()的方法获取到该对象,然后进行 ...

  9. day72:drf:反序列化功能&模型类序列化器Modelserializer&drf视图APIView

    目录 1.续:反序列化功能(5-8) 1.用户post类型提交数据,反序列化功能的步骤 2.反序列化功能的局部钩子和全局钩子 局部钩子和全局钩子在序列化器中的使用 反序列化相关校验的执行顺序 3.反序 ...

  10. 分布式缓存--Redis

    目录 一.单点Redis的问题 二.Redis持久化 2.1 RDB持久化 2.1.1 单机安装Redis 2.1.2 RDB内部机制 2.1.3 RDB异步持久化 2.1.14 RDB的缺点 2.2 ...