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

  1. using Microsoft.AspNetCore.Authorization;
  2. using Microsoft.AspNetCore.SignalR;
  3. using System;
  4. using System.Threading.Tasks;
  5.  
  6. namespace ServerSignalR.Models
  7. {
  8. public class ChatRoomHub:Hub
  9. {
  10. public override Task OnConnectedAsync()//连接成功触发
  11. {
  12. return base.OnConnectedAsync();
  13. }
  14.  
  15. public Task SendPublicMsg(string fromUserName,string msg)//给所有client发送消息
  16. {
  17. string connId = this.Context.ConnectionId;
  18. string str = $"[{DateTime.Now}]{connId}\r\n{fromUserName}:{msg}";
  19. return this.Clients.All.SendAsync("ReceivePublicMsg",str);//发送给ReceivePublicMsg方法,这个方法由SignalR机制自动创建
  20. }
  21. }
  22. }

  Startup添加

  1. static string _myAllowSpecificOrigins = "MyAllowSpecificOrigins";
  2. public void ConfigureServices(IServiceCollection services)
  3. {
  4.  
  5. services.AddControllers();
  6. services.AddSwaggerGen(c =>
  7. {
  8. c.SwaggerDoc("v1", new OpenApiInfo { Title = "ServerSignalR", Version = "v1" });
  9. });
  10. services.AddSignalR();
  11. services.AddCors(options =>
  12. {
  13. options.AddPolicy(_myAllowSpecificOrigins, policy =>
  14. {
  15. policy.WithOrigins("http://localhost:4200")
  16. .AllowAnyHeader().AllowAnyMethod().AllowCredentials();
  17. });
  18. });
  19. }
  20.  
  21. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  22. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  23. {
  24. if (env.IsDevelopment())
  25. {
  26. app.UseDeveloperExceptionPage();
  27. app.UseSwagger();
  28. app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ServerSignalR v1"));
  29. }
  30. app.UseCors(_myAllowSpecificOrigins);
  31. app.UseHttpsRedirection();
  32. app.UseRouting();
  33. app.UseAuthorization();
  34. app.UseEndpoints(endpoints =>
  35. {
  36. endpoints.MapControllers();
  37. endpoints.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub");
  38. });
  39. }

  2.2 前端Angular

    引入包

  1. npm i --save @microsoft/signalr

    ts:

  1. import { Component, OnInit } from '@angular/core';
  2. import * as signalR from '@microsoft/signalr';
  3. import { CookieService } from 'ngx-cookie-service';
  4.  
  5. @Component({
  6. selector: 'app-home',
  7. templateUrl: './home.component.html',
  8. styleUrls: ['./home.component.scss']
  9. })
  10. export class HomeComponent implements OnInit {
  11. msg = '';
  12. userName='kxy'
  13. public messages: string[] = [];
  14. public hubConnection: signalR.HubConnection;
  15.  
  16. constructor(
  17. private cookie: CookieService
  18. ) {this.hubConnection=new signalR.HubConnectionBuilder()
  19. .withUrl('https://localhost:44313/Hubs/ChatRoomHub',
  20. {
  21. skipNegotiation:true,//跳过三个协议协商
  22. transport:signalR.HttpTransportType.WebSockets,//定义使用WebSocket协议通讯
  23. }
  24. )
  25. .withAutomaticReconnect()
  26. .build();
  27. this.hubConnection.on('ReceivePublicMsg',msg=>{
  28. this.messages.push(msg);
  29. console.log(msg);
  30. });
  31. }
  32. ngOnInit(): void {
  33. }
  34. JoinChatRoom(){
  35. this.hubConnection.start()
  36. .catch(res=>{
  37. this.messages.push('连接失败');
  38. throw res;
  39. }).then(x=>{
  40. this.messages.push('连接成功');
  41. });
  42. }
  43. SendMsg(){
  44. if(!this.msg){
  45. return;
  46. }
  47. this.hubConnection.invoke('SendPublicMsg', this.userName,this.msg);
  48. }
  49. }

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

  有一点值得记录一下

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

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

3、引入Jwt进行权限验证

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

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

  Startup定义如下

  1. using Microsoft.AspNetCore.Builder;
  2. using Microsoft.AspNetCore.Hosting;
  3. using Microsoft.Extensions.Configuration;
  4. using Microsoft.Extensions.DependencyInjection;
  5. using Microsoft.Extensions.Hosting;
  6. using Microsoft.OpenApi.Models;
  7. using ServerSignalR.Models;
  8. using System;
  9. using System.Collections.Generic;
  10. using System.Linq;
  11. using System.Threading.Tasks;
  12. using System.Text;
  13. using Microsoft.AspNetCore.Authentication.JwtBearer;
  14. using Microsoft.IdentityModel.Tokens;
  15. using JwtHelperCore;
  16.  
  17. namespace ServerSignalR
  18. {
  19. public class Startup
  20. {
  21. public Startup(IConfiguration configuration)
  22. {
  23. Configuration = configuration;
  24. }
  25.  
  26. public IConfiguration Configuration { get; }
  27.  
  28. // This method gets called by the runtime. Use this method to add services to the container.
  29. static string _myAllowSpecificOrigins = "MyAllowSpecificOrigins";
  30. public void ConfigureServices(IServiceCollection services)
  31. {
  32.  
  33. services.AddControllers();
  34. services.AddSwaggerGen(c =>
  35. {
  36. c.SwaggerDoc("v1", new OpenApiInfo { Title = "ServerSignalR", Version = "v1" });
  37. });
  38. services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
  39. .AddJwtBearer(options =>
  40. {
  41. options.RequireHttpsMetadata = false;//是否需要https
  42. options.TokenValidationParameters = new TokenValidationParameters
  43. {
  44. ValidateIssuer = false,//是否验证Issuer
  45. ValidateAudience = false,//是否验证Audience
  46. ValidateLifetime = true,//是否验证失效时间
  47. ValidateIssuerSigningKey = true,//是否验证SecurityKey
  48. IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("VertivSecurityKey001")),//拿到SecurityKey
  49. };
  50. options.Events = new JwtBearerEvents()//从url获取token
  51. {
  52. OnMessageReceived = context =>
  53. {
  54. if (context.HttpContext.Request.Path.StartsWithSegments("/Hubs/ChatRoomHub"))//判断访问路径
  55. {
  56. var accessToken = context.Request.Query["access_token"];//从请求路径获取token
  57. if (!string.IsNullOrEmpty(accessToken))
  58. context.Token = accessToken;//将token写入上下文给Jwt中间件验证
  59. }
  60. return Task.CompletedTask;
  61. }
  62. };
  63. }
  64. );
  65.  
  66. services.AddSignalR();
  67.  
  68. services.AddCors(options =>
  69. {
  70. options.AddPolicy(_myAllowSpecificOrigins, policy =>
  71. {
  72. policy.WithOrigins("http://localhost:4200")
  73. .AllowAnyHeader().AllowAnyMethod().AllowCredentials();
  74. });
  75. });
  76. }
  77.  
  78. // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
  79. public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
  80. {
  81. if (env.IsDevelopment())
  82. {
  83. app.UseDeveloperExceptionPage();
  84. app.UseSwagger();
  85. app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "ServerSignalR v1"));
  86. }
  87.  
  88. app.UseCors(_myAllowSpecificOrigins);
  89. app.UseHttpsRedirection();
  90.  
  91. app.UseRouting();
  92.  
  93. //Token 授权、认证
  94. app.UseErrorHandling();//自定义的处理错误信息中间件
  95. app.UseAuthentication();//判断是否登录成功
  96. app.UseAuthorization();//判断是否有访问目标资源的权限
  97.  
  98. app.UseEndpoints(endpoints =>
  99. {
  100. endpoints.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub");
  101. endpoints.MapControllers();
  102. });
  103. }
  104. }
  105. }

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

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

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

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

  然后ts添加

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

4、私聊

  Hub

  1. using Microsoft.AspNetCore.Authorization;
  2. using Microsoft.AspNetCore.SignalR;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Threading.Tasks;
  6.  
  7. namespace ServerSignalR.Models
  8. {
  9. [Authorize]//jwt认证
  10. public class ChatRoomHub:Hub
  11. {
  12. private static List<UserModel> _users = new List<UserModel>();
  13. public override Task OnConnectedAsync()//连接成功触发
  14. {
  15. var userName = this.Context.User.Identity.Name;//从token获取登录人
  16. _users.Add(new UserModel(userName, this.Context.ConnectionId));
  17. return base.OnConnectedAsync();
  18. }
  19. public override Task OnDisconnectedAsync(Exception exception)
  20. {
  21. var userName = this.Context.User.Identity.Name;//从token获取登录人
  22. _users.RemoveRange(_users.FindIndex(x => x.UserName == userName), 1);
  23. return base.OnDisconnectedAsync(exception);
  24. }
  25.  
  26. public Task SendPublicMsg(string msg)//给所有client发送消息
  27. {
  28. var fromUserName = this.Context.User.Identity.Name;
  29. //var ss = this.Context.User!.FindFirst(ClaimTypes.Name)!.Value;
  30. string str = $"[{DateTime.Now}]\r\n{fromUserName}:{msg}";
  31. return this.Clients.All.SendAsync("ReceivePublicMsg",str);//发送给ReceivePublicMsg方法,这个方法由SignalR机制自动创建
  32. }
  33.  
  34. public Task SendPrivateMsg(string destUserName, string msg)
  35. {
  36. var fromUser = _users.Find(x=>x.UserName== this.Context.User.Identity.Name);
  37. var toUser = _users.Find(x=>x.UserName==destUserName);
  38. string str = $"";
  39. if (toUser == null)
  40. {
  41. msg = $"用户{destUserName}不在线";
  42. str = $"[{DateTime.Now}]\r\n系统提示:{msg}";
  43. return this.Clients.Clients(fromUser.WebScoketConnId).SendAsync("ReceivePrivateMsg", str);
  44. }
  45. str = $"[{DateTime.Now}]\r\n{fromUser.UserName}-{destUserName}:{msg}";
  46. return this.Clients.Clients(fromUser.WebScoketConnId,toUser.WebScoketConnId).SendAsync("ReceivePrivateMsg", str);
  47. }
  48. }
  49. }

  TS:

  1. //加一个监听
  2. this.hubConnection.on('ReceivePublicMsg', msg => {
  3. this.messages.push('公屏'+msg);
  4. console.log(msg);
  5. });
  6. this.hubConnection.on('ReceivePrivateMsg',msg=>{
  7. this.messages.push('私聊'+msg);
  8. console.log(msg);
  9. });
  10.  
  11. //加一个发送
  12. if (this.talkType == 1)
  13. this.hubConnection.invoke('SendPublicMsg', this.msg);
  14. if (this.talkType == 3){
  15. console.log('11111111111111');
  16. this.hubConnection.invoke('SendPrivateMsg',this.toUserName, this.msg);
  17. }

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

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

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

  1. namespace ServerSignalR.Controllers
  2. {
  3. //[Authorize]
  4. public class HomeController : Controller
  5. {
  6. private IHubContext<ChatRoomHub> _hubContext;
  7. public HomeController(IHubContext<ChatRoomHub> hubContext)
  8. {
  9. _hubContext = hubContext;
  10. }
  11. [HttpGet("Welcome")]
  12. public async Task<ResultDataModel<bool>> Welcome()
  13. {
  14. await _hubContext.Clients.All.SendAsync("ReceivePublicMsg", "欢迎");
  15. return new ResultDataModel<bool>(true);
  16. }
  17. }
  18. }

  

  至此,感谢关注!!

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. Python学习笔记--数据输出

    数据输出 输出为Python对象 collect算子 具体实现: reduce算子 具体实现: take算子 具体实现: count算子 具体实现: 输出到文件中 saveAsTextFile算子 具 ...

  2. Javaweb学习笔记第十三弹--JSP和Servlet

    JSP = HTML + Java 目的是为了简化开发,其本质是一个Servlet 快速入门 步骤: 1.导包 2.创建文件 3.编写程序 得到结果: JSP脚本(用于在JSP页面里面定义Java代码 ...

  3. 音频和视频流最佳选择?SRT 协议解析及报文识别

    我们所知道 SRT 是由 Haivision 和 Wowza 开发的开源视频流协议.很多人会认为在不久的将来,它被是 RTMP 的替代品.因为 RTMP 协议安全性稍低,延迟相对较高 ,而相对于 SR ...

  4. 痞子衡嵌入式:恩智浦经典LPC系列MCU内部Flash IAP驱动入门

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦经典LPC系列MCU内部Flash IAP驱动. LPC 系列 MCU 是恩智浦公司于 2003 年开始推出的非常具有代表性的产品 ...

  5. 动态开点线段树&线段树合并学习笔记

    动态开点线段树 使用场景 \(4 \times n\) 开不下. 值域需要平移(有负数). 什么时候开点 显然,访问的节点不存在时(只会在修改递归时开点). trick 区间里面有负数时,\(mid ...

  6. docker方式实现redis数据持久化离线安装

    保存镜像 root@hello:~# docker pull redis:latest latest: Pulling from library/redis a2abf6c4d29d: Already ...

  7. 4.测试类mapper报错

    1.总结:前几天还有今天一直在弄测试类报错的原因,想着项目是一个大整体,写一个mappe测试类,测试一个mapper,这样后面不会出错: 但是在测试mapper的时候一直,出现mapper值为空的异常 ...

  8. PMD插件:你必须掌握的代码质量工具!

    当今的软件开发需要使用许多不同的工具和技术来确保代码质量和稳定性.PMD是一个流行的静态代码分析工具,可以帮助开发者在编译代码之前发现潜在的问题.在本文中,我们将讨论如何在Gradle中使用PMD,并 ...

  9. 最新升级优化 shopee|美客多 Mercadolibre|shopfiy|lazada|独立货代贴单系统 可规模化的贴单打单系统 源码下载独立部署

    七想网络 跨境猴 最新优化改进版本的 虾皮代打包-虾皮代贴单 独立部署源码版本货代贴单系统 介绍: 台湾海外仓_shopee货代_虾皮物流–虾皮代贴单 虾皮代打包-虾皮代贴单-虾皮货代平台 shope ...

  10. Git rebase使用小结

    1.分支之间rebase 构造两个分支master和feature,其中feature是在提交点B处从master上拉出的分支 master上有一个新提交M,feature上有两个新提交C和D 此时我 ...