在传统的HTTP中,只能客户端主动向服务器端发起请求,服务器端无法主动向客户端发送消息。有的业务场景下,我们需要服务器端主动向客户端发送消息,比如Web聊天室、OA系统、站内消息等。
为了实现服务器端向客户端推送消息,在2008年诞生了WebSocket协议,并且该协议在2011年成为国际标准。目前所有的主流浏览器都已经支持WebSocket协议。WebSocket基于TCP(transmission control protocol,传输控制协议),支持二进制通信,因此通信效率非常高,它可以让服务器处理大量的并发WebSocket连接;WebSocket是双工通信,因此服务器可以高效地向客户端推送消息。
#region << 版 本 注 释 >> /*---------------------------------------------------------------- * 堆代码 duidaima.com * 创建时间:2023/8/24 15:48:43 * 版本:V1.0.0 * 描述: * * ---------------------------------------------------------------- * 修改人: * 时间: * 修改说明: * * 版本:V1.0.1 *----------------------------------------------------------------*/ #endregion << 版 本 注 释 >> using Microsoft.AspNetCore.SignalR; using SignalRApi.Jobs; namespace SignalRApi.Hubs { /// <summary> /// 定义集线器 /// </summary> public class MyHub : Hub { /// <summary> /// 用户字典 /// </summary> private static Dictionary<string, string> dictUsers = new Dictionary<string, string>(); /// <summary> /// 建立连接回调 /// </summary> /// <returns></returns> public override Task OnConnectedAsync() { Console.WriteLine($"ID:{Context.ConnectionId} 已连接"); return base.OnConnectedAsync(); } /// <summary> /// 断开连接回调 /// </summary> /// <param name="exception"></param> /// <returns></returns> public override Task OnDisconnectedAsync(Exception? exception) { Console.WriteLine($"ID:{Context.ConnectionId} 已断开"); return base.OnDisconnectedAsync(exception); } /// <summary> /// 登录功能,将用户ID和ConntectionId关联起来 /// </summary> /// <param name="userId"></param> public void Login(string userId) { if (!dictUsers.ContainsKey(userId)) { dictUsers[userId] = Context.ConnectionId; } Console.WriteLine($"{userId}登录成功,ConnectionId={Context.ConnectionId}"); //向所有用户发送当前在线的用户列表 Clients.All.SendAsync("Users", dictUsers.Keys.ToList()); } /// <summary> /// 退出功能,当客户端退出时调用 /// </summary> /// <param name="userId"></param> public void Logout(string userId) { if (dictUsers.ContainsKey(userId)) { dictUsers.Remove(userId); } Console.WriteLine($"{userId}退出成功,ConnectionId={Context.ConnectionId}"); } } }实时推送任务定义:
#region << 版 本 注 释 >> /*---------------------------------------------------------------- * 创建者:王明亮 * 创建时间:2023/8/24 15:48:27 * 版本:V1.0.0 * 描述: * * ---------------------------------------------------------------- * 修改人: * 时间: * 修改说明: * * 版本:V1.0.1 *----------------------------------------------------------------*/ #endregion << 版 本 注 释 >> using Microsoft.AspNetCore.SignalR; using SignalRApi.Hubs; namespace SignalRApi.Jobs { /// <summary> /// 定义定时任务 /// </summary> public class MyWorker { /// <summary> /// 单例 /// </summary> public static MyWorker Instance; /// <summary> /// 锁 /// </summary> private static readonly object locker = new object(); /// <summary> /// 集线器请求上下文 /// </summary> private IHubContext<MyHub> context; /// <summary> /// 定时器 /// </summary> public static System.Timers.Timer timer; /// <summary> /// 构造注入 /// </summary> /// <param name="context"></param> public MyWorker(IHubContext<MyHub> context) { this.context = context; timer = new System.Timers.Timer(1000);//单位毫秒 timer.Enabled = true; timer.AutoReset = true;//自动重新 timer.Elapsed += Timer_Elapsed; timer.Start(); } /// <summary> /// 时钟到达事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Timer_Elapsed(object? sender, System.Timers.ElapsedEventArgs e) { //模拟数据,一般情况下,从数据库获取,然后通知到客户端 Dictionary<string, object> data = new Dictionary<string, object>(); var online = new Random().Next(0, 100); var male = Math.Floor(new Random().NextSingle() * online); var female = online - male; data["online"] = online; data["male"] = male; data["female"] = female; context.Clients.All.SendAsync("Data", data); } /// <summary> /// 单例注册服务 /// </summary> /// <param name="context"></param> public static void Register(IHubContext<MyHub> context) { if (Instance == null) { lock (locker) { if (Instance == null) { Instance = new MyWorker(context); } } } } } }全局注册SignaIR服务:
using Microsoft.AspNetCore.SignalR; using SignalRApi.Hubs; using SignalRApi.Jobs; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //1.添加注册SignalR服务 builder.Services.AddSignalR(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } //使用路由 app.UseRouting(); app.UseHttpsRedirection(); app.UseAuthorization(); //在Use中注册单例实例 app.Use(async (context, next) => { var hubContext = context.RequestServices .GetRequiredService<IHubContext<MyHub>>(); MyWorker.Register(hubContext);//调用静态方法注册 if (next != null) { await next.Invoke(); } }); app.MapControllers(); //2.映射路由 app.UseEndpoints(endpoints => { endpoints.MapHub<MyHub>("/myhub");//启用SignalR中间件,并且设置当客户端通过SignalR请求“/myhub”这个路径的时候,由ChatHub进行处理。 }); app.Run();四、建立SignalR客户端:
dotnet add package Microsoft.AspNetCore.SignalR.Client --version 6.0.8客户端代码—点击事件定义:
#region 点击事件 /// <summary> /// 建立连接 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btConnect_Click(object sender, EventArgs e) { //1.初始化 InitInfo(); //2.监听 Listen(); //3.连接 Link(); //4.登录 Login(); } /// <summary> ///断开连接 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btDistinct_Click(object sender, EventArgs e) { hubConnection.InvokeAsync("Logout", "12345"); _isDistinct = true; } #endregion 客户端代码—SignaIR连接与监听定义: #region 连接和监听 /// <summary> /// 是否断开连接 /// </summary> private bool _isDistinct = false; /// <summary> /// 集线器连接对象 /// </summary> private HubConnection hubConnection; /// <summary> /// 初始化Connection对象 /// </summary> private void InitInfo() { hubConnection = new HubConnectionBuilder().WithUrl("http://localhost:5275/myhub").WithAutomaticReconnect().Build();//必须和在服务器端MapHub注册单例实例设置的路径一致; hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(5); } /// <summary> /// 监听 /// </summary> private void Listen() { hubConnection.On<Dictionary<string, object>>("Data", ReceiveInfos); } /// <summary> /// 连接 /// </summary> private async void Link() { try { await hubConnection.StartAsync();//建立完成一个客户端到集线器的连接。 } catch (Exception ex) { MessageBox.Show(ex.Message); } } /// <summary> /// /监听事件 /// </summary> /// <param name="data"></param> private void ReceiveInfos(Dictionary<string, object> data) { if (data == null || data.Count < 1 || _isDistinct) { return; } this.tbOnline.Text = data["online"]?.ToString(); this.tbMale.Text = data["male"]?.ToString(); this.tbFemale.Text = data["female"]?.ToString(); } /// <summary> /// 登录 /// </summary> private void Login() { hubConnection.InvokeAsync("Login", "12345"); _isDistinct =false; } #endregion五、运行演示: