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. 使用RPA有什么好处

    通过本章学习,您将了解到: 什么是RPA RPA能为我们做些什么 使用RPA有什么好处 RPA的体系结构是什么 应用RPA是否需要深入了解这项技术 RPA的未来趋势怎么样 什么是RPA? RPA即Ro ...

  2. MyBatisPlus映射匹配兼容性

    字段映射与表名映射 1.当数据库表名tbl_user与实体类名User不一致时:在实体类上添加 :@TableName("tbl_user") package com.itheim ...

  3. LinkedBlockingQueue出入队实现原理

    类图概述 由类图可以看出,L是单向链表实现的,有两个ReentrantLock实例用来控制元素入队和出队的原子性,takeLock用来控制只有一个线程可以从队头获取元素,putLock控制只有一个线程 ...

  4. CZHA0黑客游戏

    一个自己开发的黑客游戏,里面用到了自研AC库 A0阶段: from ac import* from time import * from sys import * def printf(text): ...

  5. VUEX面试题

    1.你有写过vuex中store的插件吗? 答:没有 2.你有使用过vuex的module吗?主要是在什么场景下使用? 答:把状态全部集中在状态树上,非常难以维护.按模块分成多个module,状态树延 ...

  6. LeetCode刷题笔记 - 2022

    这篇博客集中整理在LeetCode的刷题记录,方便查阅 258. 各位相加 - 力扣(LeetCode) (leetcode-cn.com) 代码 class Solution { public: i ...

  7. python文字转语音库及使用方法

    作者:陈哲链接:https://www.zhihu.com/question/473797102/answer/2019063801来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  8. Sphinx 配置文件说明

    一.    创建sphinx.conf文件 其结构组成主要如下: Source 源名称1{                //指定数据源 一些配置 } Index 索引名称1{ Source=源名称1 ...

  9. 百度松果菁英班--oj赛(第二次)

    目录 一.小码哥剪绳子 二.咖啡品鉴师小码哥 三.均分糖果 四.持盾 五.活动安排 六.甜品供应 七.斐波那契数列的组合 八.小码哥的布阵指挥 九.活动分组 十.外卖递送 一.小码哥剪绳子 题目:马上 ...

  10. OpenFec介绍

    官网: http://openfec.org/accueil.html   1.提供的编解码器 Reed-Solomon stable codec over GF(28)                ...