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) 来维护客户端与服务器的连接。心跳机制的主要作用是:

  1. 检测连接状态:通过定期发送心跳消息,检测连接是否仍然有效。

  2. 防止连接超时:某些网络设备(如代理、防火墙)可能会关闭长时间没有数据交换的连接。

  3. 及时释放资源:如果连接已断开,可以及时释放相关资源

心跳机制的原理

客户端和服务器定期向对方发送一个简单的消息(如 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

引用:使用 C# 在 .NET 6.0 中实现 WebSocket 服务器和客户端 - 实时互动网

posted @   chenxizhaolu  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示