VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • 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

  1. 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("/chat"):注册WebSocket服务,路径为/chat——客户端需要连接ws://localhost:8080/chat才能访问这个服务;
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. 心跳机制的核心逻辑
    1.客户端每隔N秒发送一个心跳消息(比如ping);
    2.服务器收到心跳消息后,回复一个心跳响应(比如pong);
    3.如果客户端在M秒内没有收到服务器的响应,或者服务器在M秒内没有收到客户端的心跳,就认为连接断开,客户端自动重连,服务器清理连接。
  2. 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, "心跳超时");
	}
	}

六、基础知识拓展

  1. WebSocket的URL格式
    普通WebSocket:ws://域名:端口/路径(比如ws://localhost:8080/chat);
    加密WebSocket:wss://域名:端口/路径(比如wss://api.example.com/chat)——和HTTPS类似,用SSL/TLS加密传输,防止数据被窃听、篡改。
  2. WebSocket的关闭代码
    WebSocket的关闭代码是16位整数,常用的关闭代码:
    1000:正常关闭;
    1001:客户端或服务器正在关闭;
    1002:协议错误;
    1003:收到不支持的数据类型;
    1008:消息违反服务器策略;
    1011:服务器内部错误。
  3. 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


相关教程