webSocket在.net中的使用案例
前言
前面asp.net实现长连接 - chenxizhaolu - 博客园学习了如何在asp.net中实现http长连接,这里继续学习websocket。
WebSockets 是一种协议,它能让客户端和服务器之间通过单个长期连接进行无缝通信。与 HTTP 等遵循请求-响应模式的传统网络通信方法不同,WebSockets 引入了全双工通信通道。这意味着客户端和服务器可以同时发送和接收数据,从而实现即时更新和实时交互。
WebSockets 常用于需要实时数据更新的场景。例如,多人游戏大厅聊天、实时通知、股票市场数据馈送和实时协作工具。这些应用需要即时通信和数据交换,而 WebSockets 恰好能提供这些功能。
创建服务端程序
var builder = WebApplication.CreateBuilder(args); builder.WebHost.UseUrls("http://localhost:5000"); var app = builder.Build(); app.UseWebSockets(); //收到消息后回应 app.Map("/ws", async context => { if (context.WebSockets.IsWebSocketRequest) { using (var webSocket = await context.WebSockets.AcceptWebSocketAsync()) { _ = Task.Run(async () => { while (true) { var message = "服务器心跳推送当前时间:" + DateTime.Now.ToLongTimeString(); var buffer = Encoding.UTF8.GetBytes(message); var segment = new ArraySegment<byte>(buffer); await webSocket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None); Thread.Sleep(3000); } }); var buffer = new byte[1024 * 4]; WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); var clientMsg = Encoding.UTF8.GetString(buffer, 0, result.Count); while (!result.CloseStatus.HasValue) { var replayMessage = Encoding.UTF8.GetBytes("服务器收到了客户端的信息:" + clientMsg); await webSocket.SendAsync(new ArraySegment<byte>(replayMessage), WebSocketMessageType.Text, result.EndOfMessage, CancellationToken.None); result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None); } await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None); Console.WriteLine("WebSocket 连接已关闭"); } } else { context.Response.StatusCode = (int)HttpStatusCode.BadRequest; } }); await app.RunAsync();
创建html客户端
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.UseStaticFiles(); app.Run();
<body> <h1>WebSocket 示例</h1> <div> <input type="text" id="messageInput" placeholder="输入消息"> <button id="sendButton">发送</button> </div> <ul id="messages"></ul> <script> // 创建 WebSocket 连接 const ws = new WebSocket('ws://localhost:5000/ws'); // 监听连接打开事件 ws.onopen = () => { console.log('已连接到 WebSocket 服务器'); appendMessage('服务器', '连接已建立'); }; // 监听服务器发送的消息 ws.onmessage = (event) => { const message = event.data; console.log('收到服务器消息:', message); appendMessage('服务器', message); }; // 监听连接关闭事件 ws.onclose = () => { console.log('WebSocket 连接已关闭'); appendMessage('服务器', '连接已关闭'); }; // 监听连接错误事件 ws.onerror = (error) => { console.error('WebSocket 错误:', error); appendMessage('服务器', '连接错误'); }; // 发送消息 document.getElementById('sendButton').addEventListener('click', () => { const message = document.getElementById('messageInput').value; if (message) { ws.send(message); // 向服务器发送消息 appendMessage('我', message); document.getElementById('messageInput').value = ''; } }); // 在页面上显示消息 function appendMessage(sender, message) { const li = document.createElement('li'); li.textContent = `${sender}: ${message}`; document.getElementById('messages').appendChild(li); } </script> </body>
效果:
增加心跳机制
在 WebSocket 实战 中,通常需要一个 心跳机制(Heartbeat) 来维护客户端与服务器的连接。心跳机制的主要作用是:
-
检测连接状态:通过定期发送心跳消息,检测连接是否仍然有效。
-
防止连接超时:某些网络设备(如代理、防火墙)可能会关闭长时间没有数据交换的连接。
- 及时释放资源:如果连接已断开,可以及时释放相关资源
心跳机制的原理
客户端和服务器定期向对方发送一个简单的消息(如 ping
或 pong
)。如果一方在指定时间内没有收到心跳消息,则认为连接已断开,并关闭连接。
核心代码展示
服务端
public class HeartbeatManager { // 记录最后一次收到 pong 的时间 public static DateTime LastPongTime = DateTime.UtcNow; public async Task StartHeartbeatAsync(WebSocket webSocket, CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { try { // 检查是否超过 60 秒未收到 pong 回复 if ((DateTime.UtcNow - LastPongTime).TotalSeconds > 60) { Console.WriteLine("客户端未回复 pong,关闭连接"); await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "客户端未回复 pong", cancellationToken); break; } // 发送心跳消息 byte[] heartbeatMessage = Encoding.UTF8.GetBytes("ping"); await webSocket.SendAsync(new ArraySegment<byte>(heartbeatMessage), WebSocketMessageType.Text, true, cancellationToken); Console.WriteLine("发送心跳消息: ping"); // 等待 30 秒 await Task.Delay(30000, cancellationToken); } catch (Exception ex) { Console.WriteLine("心跳任务错误: " + ex.Message); break; } } } }
using (var webSocket = await context.WebSockets.AcceptWebSocketAsync()) { var cts = new CancellationTokenSource(); _ = new HeartbeatManager().StartHeartbeatAsync(webSocket, cts.Token); //定时推送消息 _ = Task.Run(async () => { while (true) { var message = "服务器心跳推送当前时间:" + DateTime.Now.ToLongTimeString(); var buffer = Encoding.UTF8.GetBytes(message); var segment = new ArraySegment<byte>(buffer); await webSocket.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None); Thread.Sleep(10000); } }); var buffer = new byte[1024 * 4]; try { WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cts.Token); while (!result.CloseStatus.HasValue) { var clientMsg = Encoding.UTF8.GetString(buffer, 0, result.Count); if (clientMsg.Equals("ping")) { await webSocket.SendAsync(Encoding.UTF8.GetBytes("pong"), WebSocketMessageType.Text, result.EndOfMessage, cts.Token); } else if (clientMsg.Equals("pong")) { HeartbeatManager.LastPongTime = DateTime.UtcNow; Console.WriteLine("收到了客户端的pong回复" + DateTime.UtcNow.ToLongTimeString()); } var replayMessage = Encoding.UTF8.GetBytes("服务器收到了客户端的信息:" + clientMsg); await webSocket.SendAsync(new ArraySegment<byte>(replayMessage), WebSocketMessageType.Text, result.EndOfMessage, cts.Token); result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cts.Token); clientMsg = string.Empty; } await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, cts.Token); Console.WriteLine("WebSocket 连接已关闭"); } catch (Exception ex) { Console.WriteLine("WebSocket 错误: " + ex.Message); } finally { cts.Cancel(); } }
客户端
let lastPongTime = Date.now(); // 心跳检测逻辑 let heartbeatInterval; function startHeartbeat() { heartbeatInterval = setInterval(() => { appendMessage('客户端',"ping"); ws.send("ping"); // 检查是否超过 60 秒未收到服务器的 ping if (Date.now() - lastPongTime > 60000) { console.log('服务器超时未相应 pong,关闭连接'); ws.close(); // 关闭连接 stopHeartbeat(); } }, 10000); // 每 10 秒检查一次,发一次ping } function stopHeartbeat() { clearInterval(heartbeatInterval); } // 监听连接打开事件 ws.onopen = () => { console.log('已连接到 WebSocket 服务器'); appendMessage('服务器', '连接已建立'); startHeartbeat(); // 启动心跳检测 }; // 监听服务器发送的消息 ws.onmessage = (event) => { const message = event.data; console.log('收到服务器消息:', message); appendMessage('服务器', message); if (message == 'ping') { appendMessage('客户端', 'pong'); ws.send('pong'); } else if (message == 'pong') { lastPongTime = Date.now(); } };
这里做得比较复杂,客户端和服务端都定时发送ping命令并检查pong是否超时,算是双向保活吧。
源码地址:https://gitee.com/xiaoqingyao/keep-alive-http-demo.git
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix