本节内容,部分为补充内容。主要NuGet包:
- Microsoft.AspNetCore.SignalR.Client(BlazorWASM的SignalR客户端)
- Microsoft.AspNetCore.SignalR.StackExchangeRedis(使用Redis部署分布式SignalR)
一、SignalR基本使用
SignalR使用集线器Hub类,连接服务端和客户端,实现服务端与客户端,以及客户端之间的双向通讯,关系如下,服务端<->集线器<->客户端。首先,在服务端配置SignalR服务,同时创建集线器类,包括具体响应逻辑和发送消息命令;然后,在客户端连接集线器,监听消息,并发送请求。
1、服务端操作Asp.net Core
//第一步:在Hubs文件夹中,创建集线器类 //ChatRoomHub.cs,继承自Hub类 public class ChatRoomHub : Hub { //集线器方法,客户端可以请求调用 public Task SendPublicMessage(string user, string message) { string connId = Context.ConnectionId; //获取连接客户端ID string msg = $"{connId}/{user}: {message}"; //向所有连接的客户端发送消息,消息名“ReceivePublicMessage”,消息内容msg return Clients.All.SendAsync("ReceivePublicMessage", msg); } } //第二步:配置SignalR服务 //program.cs var builder = WebApplication.CreateBuilder(args); ...... //注册SignalR服务 builder.Services.AddSignalR(); //注册跨域服务,并将Blazor项目的URL设置为可信任 string[] corsUrls = new[] { "https://localhost:7042", "http://localhost:5042" }; builder.Services.AddCors(opts => { opts.AddDefaultPolicy(builder => { builder.WithOrigins(corsUrls).AllowAnyMethod().AllowAnyHeader().AllowCredentials(); }); }); var app = builder.Build(); ...... //启用跨域中间件 app.UseCors(); app.UseHttpsRedirection(); ...... //启用和配置SignalR中间件,当客户端访问/Hubs/ChatRoomHub时,使用ChatRoomHub进行处理 app.MapHub<ChatRoomHub>("/Hubs/ChatRoomHub"); app.MapControllers(); app.Run();
补充说明:
①Signal是对WebSocket的封装,基于TCP。相对于HTTP,①采用二进制通信,通讯效率高;②使用双工通信,所以服务器可以向客户端推送消息
②虽然Signal独立于HTTP,但一般将WebSocket服务端部署到HTTP服务器上,一是借助HTTP完成初次“握手”协商,二是共享HTTP服务器的端口
③本案例,使用前后端分离模式,客户端使用BlazorWASM,由于SignalR连接的初次握手需要借助HTTP,所以要启动跨域CORS支持
④Hub类的生命周期为瞬态,在集线器类中不要保持状态。Hub类还提供了两个虚方法OnConnectedAsync和OnDisconnectedAsync,可以在客户端连接或断开时执行逻辑
⑤SignalR提供了各种客户端SDK,用于客户端连接集线器,包括BalzorWASM、JavaScript、Java等
2、客户端操作Blazor Webassembly
//第一步:安装Nuget包,Microsoft.AspNetCore.SignalR.Client //第二步:创建前端UI,模拟简单的聊天室,在同一页面,发送消息和接收消息 //Index.razor <h3>User:</h3><input @bind = "userInput" /> <h3>Message:</h3><input @bind = "messageInput" /> <button @onclick = "Send" disabled="@(!IsConnected)">发送</button> @foreach (var message in messages) { <li>@message</li> } @code{ private HubConnection? hubConnection; //集线器连接对象 private string? userInput; //输入用户名 private string? messageInput; //输入聊天信息 private List<string> messages = new List<string>(); //聊天记录 protected override async Task OnInitializedAsync() { //创建集线器连接 hubConnection = new HubConnectionBuilder() .WithUrl("https://localhost:7110/Hubs/ChatRoomHub") .WithAutomaticReconnect() .Build(); //监听集线器消息,将消息内容添加到messages集合中,并刷新 hubConnection.On<string>("ReceivePublicMessage", msg => { messages.Add(msg); StateHasChanged(); }); //开始连接集线器 await hubConnection.StartAsync(); } //如果已连接集线器,则调用集线器方法,发送信息 private async Task Send() { if (hubConnection is not null) { await hubConnection.SendAsync("SendPublicMessage", userInput, messageInput); } } //判断客户端是否连接集线器 public bool IsConnected => hubConnection?.State == HubConnectionState.Connected; //组件销毁时,断开集线器连接(销毁集线器连接对象) public async ValueTask DisposeAsync() { if (hubConnection is not null) { await hubConnection.DisposeAsync(); } } }
二、除了可以在集线器中向客户端推送消息,还可以在控制器或托管服务中,调用集线器,向客户端推送信息
//第一步:服务端,在控制器中注入IHubContext<THub>服务,并发送消息 [Route("api/[controller]/[action]")] [ApiController] public class MyController : ControllerBase { private readonly IHubContext<ChatRoomHub> hubContext; public MyController(IHubContext<ChatRoomHub> hubContext) { this.hubContext = hubContext; } [HttpGet] public async Task<ActionResult> SendControllerMessage(string msg) { await hubContext.Clients.All.SendAsync("ControllerMessage", msg); return Ok(); } } //客户端,在前端中增加对消息“ControllerMessage”的监听 hubConnection.On<string>("ControllerMessage", msg => { messages.Add(msg); StateHasChanged(); });
三、SignalR连接过程,禁用协商
1、SignalR封装了Websocket、服务器发送事件、长轮询三种实现方式,优先顺序依次降低。连接时,客户端首先发出一个negotiate请求,协商使用哪种协议,这次协商过程称之为“握手”。确定好协议后,客户端按协商协议再次连接。
2、在协商过程中,服务器就会创建上下文信息,建立连接后,会继续使用这些上下文信息。如果有多台服务器,可能发生创建上下文信息的服务器和实际连接的服务器不一致情况。这种情况,一般使用“禁止协商”来解决,即不用协商过程,直接按Websocket连接,毕竟现代客户端绝大多数都支持Websocket协议
//客户端连接时,禁止协商设置 hubConnection = new HubConnectionBuilder() .WithUrl("https://localhost:7110/Hubs/ChatRoomHub",options => { options.SkipNegotiation = true; options.Transports = HttpTransportType.WebSockets; }) .WithAutomaticReconnect() .Build();
四、SignalR的分布式部署
如果SignalR的服务端,部署在多个服务器上,连接到不同服务器上的客户端,由于连接的是不同的集线器,所以没办法通讯。可以通过使用Redis作为中间层,解决数据同步的问题。首先安装Nuget包:Microsoft.AspNetCore.SignalR.StackExchangeRedis。然后在服务端的program.cs中设置SignalR
//AddStackExchangeRedis方法的第一个参数为Redis服务器地址,第一个参数配置当前应用程序的前缀 builder.Services.AddSignalR().AddStackExchangeRedis("127.0.0.1", opts => { opts.Configuraton.ChannelPrefix = "Test1_" });
五、SignalR的身份认证及发送范围控制(略,详见认证和授权章节)
六、补充:几个常见通讯协议的区别和联系
1、TCP、HTTP和Websocket
- TCP:在OSI模型有7层结构中,属于传输层协议,HTTP和Websocket的底层连接都基于TCP
- HTTP:在OSI模型有7层结构中,属于应用层协议,基于TCP
- Websocket:和HTTP一样,也属于应用层协议,也基于TCP
2、HTTP和Websocket的特点及区别:
1)HTTP
- 基于请求响应模式,由客户端发起请求,服务端做出响应。
- 无连接、无状态,即每次请求响应后,连接都会断开,且服务端不会记录
- 以文本方式传输数据
- 指定了80(HTTP)和443(HTTPS)端口,一般计算机不会限制,可以顺利通过防火墙
- 有同源限制,即跨域问题
2)Websocket
- 基于双工模式,客户端和服务端可以相互推送信息
- 可以保持长连接,可以保存状态信息
- 即可以传输文本,也可以传输二进制,且传输报文更轻量,所以性能更高效
- 与HTTP协议有着良好的兼容性,一般将Websocket布置到HTTP服务器上,共享HTTP的端口,服务端和客户端初次握手阶段也使用HTTP协议
- 没有同源限制,不存在跨域问题
- 协议标识符为ws或wss(加密),网址也使用URL
3、HTTP1、HTTP2、HTTP3的区别
1)HTTP1的问题
- 每个TCP连接只能处理一个请求,且浏览器限制连接数,所以会造成请求阻塞
- 只能通过文本方式传输数据,性能开销较大
2)HTTP2的提升
- 引用多路复用技术,一个TCP连接可以处理所有请求数据
- 新的编码机制,传输数据被分割,且采用二进制,性能更高效
- 服务端在客户端请求后,可以PUSH其它资源
3)HTTP3的提成
- 使用QUIC,解决HTTP2可能存在的丢包重连的情况,而且可以缓存上下文,在HTTP2基础上,大大提升了性能和纠错能力
4、RPC:不是协议,而是一种远程调用方法(API)
- 可以基于TCP协议,也可以基于HTTP协议,还可以自定义TCP协议
- 基于二进制传输,且传输报文更轻量,性能高
- 自带负载均衡,而HTTP需要借助Nginxt等
- RPC因为传输效率高、性能消耗低等特点,常用于服务端之间相互调用
- gRPC是由google开发的RPC框架,基于HTTP2协议,与语言无关,应用比较广泛
特别说明:
1、本系列内容主要基于杨中科老师的书籍《ASP.NET Core技术内幕与项目实战》及配套的B站视频视频教程,同时会增加极少部分的小知识点
2、本系列教程主要目的是提炼知识点,追求快准狠,以求快速复习,如果说书籍学习的效率是视频的2倍,那么“简读系列”应该做到再快3-5倍
标签:Core,ASP,HTTP,集线器,SignalR,连接,服务端,客户端 From: https://www.cnblogs.com/functionMC/p/16867881.html