-
C#网络编程之WebSocket协议(双向通信、心跳机制)
第7章 WebSocket协议实战
7.1 WebSocket协议(双向通信、心跳机制)
一、为什么需要WebSocket?
我刚做实时聊天项目时,用HTTP轮询实现——客户端每隔1秒发一次GET请求,服务器返回最新消息。结果服务器被请求打垮了:100个客户端每秒发100次请求,服务器CPU直接飙到100%。后来换成WebSocket,服务器CPU降到10%以下,延迟从1秒降到10ms以内。这节我把自己踩过的坑、实战经验都揉进去,用大白话讲透WebSocket的核心知识点,结合C#代码逐行vb.net教程C#教程python教程SQL教程access 2010教程
讲解,让你既能写WebSocket客户端,又能写WebSocket服务器,还能解决心跳、重连等生产问题。
二、先搞懂WebSocket的本质:HTTP握手,TCP通信
WebSocket是双向、全双工的应用层协议,核心是“一次握手,永久连接”:
1.客户端用HTTP请求发起握手,请求头包含Upgrade: websocket;
2.服务器返回101状态码,确认升级为WebSocket协议;
3.握手完成后,客户端和服务器用TCP连接双向通信,不需要再发HTTP请求。
类比:WebSocket就像你打电话——拨号(握手)成功后,双方可以随时说话(双向通信),不需要每次说话都拨号(HTTP请求)。
三、WebSocket的核心优势
| 特性 | WebSocket | HTTP轮询 |
|---|---|---|
| 通信方式 | 双向、全双工 | 单向、半双工 |
| 延迟 | 低(毫秒级) | 高(秒级) |
| 服务器负载 | 低(一个连接处理所有消息) | 高(大量HTTP请求) |
| 数据格式 | 二进制/文本,开销小 | HTTP请求/响应,开销大 |
四、C#实战:用WebSocketSharp写WebSocket客户端和服务器
WebSocketSharp是.NET生态中最流行的WebSocket库,支持客户端和服务器,用法简单——就像你用微信聊天,不需要懂WebSocket协议的细节,直接发消息就行。
步骤1:安装WebSocketSharp
在NuGet中搜索WebSocketSharp-netstandard(支持.NET Core/.NET 5+):
bash
dotnet add package WebSocketSharp-netstandard
-
WebSocket服务器:实时推送消息
csharp
using System;
using WebSocketSharp;
using WebSocketSharp.Server;
namespace WebSocketServerDemo;
// 定义WebSocket服务类:处理客户端的连接、消息、断开
public class ChatService : WebSocketBehavior
{
// 客户端连接成功时触发
protected override void OnOpen()
{
string clientId = Context.QueryString["clientId"]; // 从URL参数获取客户端ID
Console.WriteLine($"客户端 {clientId} 已连接");
}
// 收到客户端消息时触发
protected override void OnMessage(MessageEventArgs e)
{
string clientId = Context.QueryString["clientId"];
string message = e.Data;
Console.WriteLine($"收到客户端 {clientId} 的消息:{message}");
// 1. 回复单个客户端
Send($"服务器已收到:{message}");
// 2. 广播消息给所有客户端
Sessions.Broadcast($"客户端 {clientId} 说:{message}");
}
// 客户端断开连接时触发
protected override void OnClose(CloseEventArgs e)
{
string clientId = Context.QueryString["clientId"];
Console.WriteLine($"客户端 {clientId} 已断开,原因:{e.Reason}");
}
// 发生错误时触发
protected override void OnError(ErrorEventArgs e)
{
Console.WriteLine($"服务器错误:{e.Message}");
}
}
class Program
{
static void Main(string[] args)
{
// 1. 创建WebSocket服务器,监听8080端口
var wssv = new WebSocketServer("ws://localhost:8080");
// 2. 注册WebSocket服务:路径为/chat,对应ChatService类
wssv.AddWebSocketService<ChatService>("/chat");
try
{
// 3. 启动服务器
wssv.Start();
Console.WriteLine("WebSocket服务器已启动,监听ws://localhost:8080/chat");
Console.WriteLine("按任意键停止服务器...");
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine($"服务器启动失败:{ex.Message}");
}
finally
{
// 4. 停止服务器
wssv.Stop();
}
}
}
代码逐行讲:
1.public class ChatService : WebSocketBehavior:继承WebSocketBehavior,重写OnOpen、OnMessage、OnClose、OnError方法,处理客户端的连接、消息、断开、错误;
2.Context.QueryString["clientId"]:从URL参数获取客户端ID(比如ws://localhost:8080/chat?clientId=123);
3.Send($"服务器已收到:{message}"):回复单个客户端;
4.Sessions.Broadcast($"客户端 {clientId} 说:{message}"):广播消息给所有连接的客户端;
5.var wssv = new WebSocketServer("ws://localhost:8080"):创建WebSocket服务器,监听8080端口;
6.wssv.AddWebSocketService
7.wssv.Start():启动服务器;wssv.Stop():停止服务器。
2. WebSocket客户端:实时收发消息
csharp
using System;
using WebSocketSharp;
namespace WebSocketClientDemo;
class Program
{
static void Main(string[] args)
{
// 1. 创建WebSocket客户端,连接服务器
string clientId = "client-123";
var ws = new WebSocket($"ws://localhost:8080/chat?clientId={clientId}");
// 2. 注册事件处理程序
// 连接成功时触发
ws.OnOpen += (sender, e) =>
{
Console.WriteLine("已连接到WebSocket服务器");
// 连接成功后发送消息
ws.Send("你好,服务器!");
};
// 收到服务器消息时触发
ws.OnMessage += (sender, e) =>
{
Console.WriteLine($"收到服务器消息:{e.Data}");
};
// 断开连接时触发
ws.OnClose += (sender, e) =>
{
Console.WriteLine($"已断开连接,原因:{e.Reason}");
};
// 发生错误时触发
ws.OnError += (sender, e) =>
{
Console.WriteLine($"客户端错误:{e.Message}");
};
try
{
// 3. 连接服务器
ws.Connect();
// 4. 输入消息发送给服务器
Console.WriteLine("请输入消息(输入exit退出):");
while (true)
{
string input = Console.ReadLine() ?? string.Empty;
if (input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
ws.Send(input);
}
}
catch (Exception ex)
{
Console.WriteLine($"连接失败:{ex.Message}");
}
finally
{
// 5. 关闭连接
ws.Close();
}
}
}
代码关键:
new WebSocket($"ws://localhost:8080/chat?clientId={clientId}"):创建WebSocket客户端,连接服务器的/chat路径,携带clientId参数;
ws.Connect():同步连接服务器;如果需要异步连接,用ws.ConnectAsync();
ws.Send(input):发送文本消息;还可以发送二进制消息:ws.Send(byte[] buffer);
ws.Close():关闭连接;还可以指定关闭代码和原因:ws.Close(CloseStatusCode.Normal, "正常退出")。
五、生产级WebSocket:心跳机制与断线重连
WebSocket连接可能因为网络中断、服务器重启等原因断开,客户端需要自动重连;同时,服务器需要检测客户端是否在线,清理僵尸连接——这就需要心跳机制。
-
心跳机制的核心逻辑
1.客户端每隔N秒发送一个心跳消息(比如ping);
2.服务器收到心跳消息后,回复一个心跳响应(比如pong);
3.如果客户端在M秒内没有收到服务器的响应,或者服务器在M秒内没有收到客户端的心跳,就认为连接断开,客户端自动重连,服务器清理连接。 -
C#实战:客户端实现心跳与断线重连
csharp
using System;
using System.Timers;
using WebSocketSharp;
namespace WebSocketClientWithHeartbeat;
class Program
{
private static WebSocket? _ws;
private static Timer? _heartbeatTimer;
private static int _heartbeatInterval = 5000; // 心跳间隔:5秒
private static int _reconnectInterval = 3000; // 重连间隔:3秒
private static bool _isReconnecting = false; // 是否正在重连
static void Main(string[] args)
{
InitializeWebSocket();
InitializeHeartbeat();
Console.WriteLine("请输入消息(输入exit退出):");
while (true)
{
string input = Console.ReadLine() ?? string.Empty;
if (input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
_ws?.Send(input);
}
Cleanup();
}
// 初始化WebSocket客户端
private static void InitializeWebSocket()
{
string clientId = "client-123";
_ws = new WebSocket($"ws://localhost:8080/chat?clientId={clientId}");
_ws.OnOpen += (sender, e) =>
{
Console.WriteLine("已连接到WebSocket服务器");
_isReconnecting = false;
// 连接成功后启动心跳
_heartbeatTimer?.Start();
};
_ws.OnMessage += (sender, e) =>
{
if (e.Data == "pong")
{
// 收到心跳响应,重置心跳计时器
_heartbeatTimer?.Stop();
_heartbeatTimer?.Start();
}
else
{
Console.WriteLine($"收到服务器消息:{e.Data}");
}
};
_ws.OnClose += (sender, e) =>
{
Console.WriteLine($"已断开连接,原因:{e.Reason}");
_heartbeatTimer?.Stop();
// 自动重连(不是主动关闭的情况)
if (!_isReconnecting && e.Code != CloseStatusCode.Normal)
{
Reconnect();
}
};
_ws.OnError += (sender, e) =>
{
Console.WriteLine($"客户端错误:{e.Message}");
};
_ws.Connect();
}
// 初始化心跳计时器
private static void InitializeHeartbeat()
{
_heartbeatTimer = new Timer(_heartbeatInterval);
_heartbeatTimer.Elapsed += (sender, e) =>
{
// 发送心跳消息
_ws?.Send("ping");
Console.WriteLine("发送心跳:ping");
};
_heartbeatTimer.AutoReset = false; // 只触发一次,收到响应后再重启
}
// 断线重连
private static void Reconnect()
{
if (_isReconnecting)
{
return;
}
_isReconnecting = true;
Console.WriteLine($"尝试重连({_reconnectInterval/1000}秒后重试)...");
// 用计时器实现重连
var reconnectTimer = new Timer(_reconnectInterval);
reconnectTimer.Elapsed += (sender, e) =>
{
reconnectTimer.Stop();
reconnectTimer.Dispose();
try
{
_ws?.Dispose();
InitializeWebSocket();
}
catch (Exception ex)
{
Console.WriteLine($"重连失败:{ex.Message}");
Reconnect(); // 递归重连
}
};
reconnectTimer.AutoReset = false;
reconnectTimer.Start();
}
// 清理资源
private static void Cleanup()
{
_heartbeatTimer?.Stop();
_heartbeatTimer?.Dispose();
_ws?.Close(CloseStatusCode.Normal, "主动退出");
_ws?.Dispose();
}
}
代码逐行讲:
1.InitializeWebSocket():初始化WebSocket客户端,注册事件处理程序;
2.InitializeHeartbeat():初始化心跳计时器,每隔5秒发送一次ping消息;
3._heartbeatTimer.AutoReset = false:心跳计时器只触发一次,收到服务器的pong响应后再重启——这样可以检测服务器是否在线;
4.Reconnect():断线重连逻辑,用计时器每隔3秒尝试重连一次,直到连接成功;
5._isReconnecting:防止重复重连,避免同时发起多个重连请求;
6.Cleanup():清理心跳计时器和WebSocket客户端资源。
3. WebSocket服务器实现心跳检测
csharp
// 在ChatService类中添加心跳检测逻辑
private DateTime _lastHeartbeatTime; // 最后一次收到心跳的时间
private int _heartbeatTimeout = 15000; // 心跳超时时间:15秒
protected override void OnOpen()
{
string clientId = Context.QueryString["clientId"];
Console.WriteLine($"客户端 {clientId} 已连接");
_lastHeartbeatTime = DateTime.Now;
// 启动心跳检测计时器
var timer = new System.Timers.Timer(1000);
timer.Elapsed += (sender, e) => CheckHeartbeat(clientId, timer);
timer.Start();
}
protected override void OnMessage(MessageEventArgs e)
{
if (e.Data == "ping")
{
// 收到心跳消息,回复pong,更新最后心跳时间
Send("pong");
_lastHeartbeatTime = DateTime.Now;
}
else
{
// 处理业务消息
string clientId = Context.QueryString["clientId"];
Console.WriteLine($"收到客户端 {clientId} 的消息:{e.Data}");
Sessions.Broadcast($"客户端 {clientId} 说:{e.Data}");
}
}
// 检测客户端是否在线
private void CheckHeartbeat(string clientId, System.Timers.Timer timer)
{
if ((DateTime.Now - _lastHeartbeatTime).TotalMilliseconds > _heartbeatTimeout)
{
// 心跳超时,断开连接
Console.WriteLine($"客户端 {clientId} 心跳超时,断开连接");
timer.Stop();
timer.Dispose();
Close(CloseStatusCode.Abnormal, "心跳超时");
}
}
六、基础知识拓展
-
WebSocket的URL格式
普通WebSocket:ws://域名:端口/路径(比如ws://localhost:8080/chat);
加密WebSocket:wss://域名:端口/路径(比如wss://api.example.com/chat)——和HTTPS类似,用SSL/TLS加密传输,防止数据被窃听、篡改。 -
WebSocket的关闭代码
WebSocket的关闭代码是16位整数,常用的关闭代码:
1000:正常关闭;
1001:客户端或服务器正在关闭;
1002:协议错误;
1003:收到不支持的数据类型;
1008:消息违反服务器策略;
1011:服务器内部错误。 -
WebSocket vs Socket.IO
Socket.IO是基于WebSocket的实时通信库,支持降级到HTTP轮询——如果客户端不支持WebSocket,自动切换到HTTP轮询。适合需要兼容老浏览器的场景,但比原生WebSocket多一层封装,性能稍低。
C#实战:用Socket.IO客户端
bash
dotnet add package SocketIOClient
csharp
var client = new SocketIO("http://localhost:3000");
client.OnConnected += (sender, e) =>
{
Console.WriteLine("已连接到Socket.IO服务器");
client.Emit("message", "你好,服务器!");
};
client.On("message", (response) =>
{
string message = response.GetValue<string>();
Console.WriteLine($"收到服务器消息:{message}");
});
await client.ConnectAsync();
七、总结:WebSocket是实时通信的首选
WebSocket:双向、全双工,低延迟,低服务器负载,适合实时聊天、游戏、监控等场景;
心跳机制:解决连接断开检测问题,客户端自动重连,服务器清理僵尸连接;
加密WebSocket:用wss://开头,保证数据传输安全;
进阶:需要兼容老浏览器,用Socket.IO;需要高性能,用原生WebSocket。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49520.html










