大家好我是码农刚子。本文详解 WebSocket 全双工通信原理、前端 API 及 C# 服务端三种实现ASP.NET Core/Fleck/HttpListener涵盖心跳重连、安全扩展等工程实践附完整源码助你快速上手实时应用开发。1. 引言在传统 Web 开发中HTTP 协议一直是客户端与服务器交互的主流方式。但 HTTP 有一个天生的缺陷它只能由客户端主动发起请求服务器被动响应。这就导致服务器无法主动向客户端推送数据。为了实现实时通信如聊天、股票行情、在线游戏等开发者不得不借助轮询、长轮询等“模拟”方案但这些方案要么浪费带宽要么延迟较高。WebSocket 的出现彻底解决了这个问题。它允许客户端和服务器之间建立一条持久、全双工的通信通道双方可以随时向对方发送数据真正实现了“即时通讯”。本文将带你从零开始掌握 WebSocket 的核心概念、工作原理并手把手实现一个完整的 WebSocket 应用——包含前端 HTML/JavaScript 以及 C# 后端服务端代码。2. WebSocket 是什么WebSocket是一种在单个 TCP 连接上提供全双工通信的协议由 IETF 标准化为 RFC 6455。它设计用于 Web 浏览器和服务器之间的实时数据交换但也适用于任何客户端-服务器场景。简单类比HTTP 就像写信你写好信寄出去对方收到后回信你才能收到回复。每次通信都需要重新“寄信”。WebSocket 就像打电话双方先拨通电话建立连接之后任何一方都可以随时说话另一方随时收听直到挂断电话关闭连接。WebSocket 使用ws://或wss://作为协议前缀wss是加密版本类似 HTTPS。3. 为什么需要 WebSocket传统 HTTP 在实时场景下面临三大痛点痛点说明单向请求只有客户端能发起请求服务器无法主动推送短连接每次请求都需要重新建立 TCP 连接三次握手开销大实时性差轮询Polling需要频繁发起请求浪费带宽且延迟不可控常见的“伪实时”方案短轮询客户端每隔几秒就发一次请求询问是否有新数据。缺点大量无效请求浪费资源。长轮询客户端发起请求后服务器保持连接打开直到有数据或超时才返回。缺点占用连接资源实现复杂。WebSocket 通过一次握手建立持久连接后续所有数据都通过该连接传输帧头仅 2 字节通信效率极高延迟达到毫秒级。因此它已成为实时 Web 应用的事实标准。4. WebSocket 工作原理WebSocket 通信分为两个阶段握手阶段和数据传输阶段。4.1 握手阶段握手使用 HTTP 协议客户端发送一个特殊的 GET 请求包含Upgrade: websocket头表明希望将协议升级为 WebSocket。服务器如果支持则返回状态码101 Switching Protocols协议切换完成。此后该 TCP 连接就不再是 HTTP 协议而是 WebSocket 协议。示例请求头GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ Sec-WebSocket-Version: 13响应头HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbKxOo4.2 数据传输阶段握手成功后连接进入全双工模式。双方可以随时发送数据帧帧格式轻量支持文本UTF-8和二进制数据。通信保持持久直到一方主动关闭连接或网络中断。5. 前端 WebSocket API 及示例浏览器原生提供了WebSocket对象无需第三方库。5.1 核心 API// 创建连接 const socket new WebSocket(ws://localhost:8080); // 事件监听 socket.onopen function() { /* 连接建立 */ }; socket.onmessage function(event) { /* 收到消息event.data 为数据 */ }; socket.onerror function(error) { /* 出错 */ }; socket.onclose function() { /* 连接关闭 */ }; // 发送数据 socket.send(Hello Server!); // 关闭连接 socket.close();5.2 完整前端示例下面是一个简单的聊天界面 HTML与任何 WebSocket 服务端兼容。!DOCTYPE html html head meta charsetUTF-8 titleWebSocket 聊天示例/title style #output { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: 10px; margin-bottom: 10px; } .msg { margin: 5px 0; } .self { color: blue; } .server { color: green; } /style /head body h2WebSocket 聊天/h2 div idoutput/div input idinput typetext placeholder输入消息... / button onclicksendMessage()发送/button button onclickcloseConnection()断开/button script // 1. 创建连接根据实际后端地址修改 const socket new WebSocket(ws://localhost:8080/ws); // 2. 连接成功 socket.onopen function() { appendMessage(系统, ✅ 连接已建立, system); }; // 3. 收到消息 socket.onmessage function(event) { appendMessage(服务器, event.data, server); }; // 4. 错误处理 socket.onerror function(error) { console.error(WebSocket 错误:, error); appendMessage(系统, ❌ 发生错误, system); }; // 5. 连接关闭 socket.onclose function() { appendMessage(系统, 连接已关闭, system); }; // 6. 发送消息 function sendMessage() { const input document.getElementById(input); const text input.value.trim(); if (text ) return; if (socket.readyState WebSocket.OPEN) { socket.send(text); appendMessage(我, text, self); input.value ; } else { alert(连接未打开); } } // 7. 关闭连接 function closeConnection() { socket.close(); } // 辅助在界面中追加消息 function appendMessage(sender, text, type) { const output document.getElementById(output); const div document.createElement(div); div.className msg (type || ); div.textContent [${sender}] ${text}; output.appendChild(div); output.scrollTop output.scrollHeight; } // 回车发送 document.getElementById(input).addEventListener(keyup, function(e) { if (e.key Enter) sendMessage(); }); /script /body /html6. C# 后端 WebSocket 服务端实现在 C# 中我们有多种方式搭建 WebSocket 服务下面分别介绍ASP.NET Core 原生、轻量级 Fleck和HttpListener 自托管三种方案你可以根据项目需求选择。6.1 方案一ASP.NET Core 原生 WebSocket推荐适用于 .NET Core / .NET 5 的 Web 应用程序与 MVC/WebAPI 无缝集成。步骤创建一个空的 ASP.NET Core Web 项目。在Program.cs中启用 WebSocket 中间件并定义/ws路由。using System.Collections.Concurrent; using System.Net.WebSockets; using System.Text; var builder WebApplication.CreateBuilder(args); var app builder.Build(); // 配置 WebSocket app.UseWebSockets(new WebSocketOptions { KeepAliveInterval TimeSpan.FromSeconds(30), ReceiveBufferSize 4 * 1024 }); // 存储所有活跃连接线程安全 var connections new ConcurrentDictionarystring, WebSocket(); app.Map(/ws, async context { if (context.WebSockets.IsWebSocketRequest) { var webSocket await context.WebSockets.AcceptWebSocketAsync(); var clientId Guid.NewGuid().ToString(); connections.TryAdd(clientId, webSocket); Console.WriteLine($✅ 客户端 {clientId} 已连接); await HandleWebSocket(webSocket, clientId, connections); } else { context.Response.StatusCode StatusCodes.Status400BadRequest; } }); app.Run(); // 处理单个 WebSocket 连接 static async Task HandleWebSocket( WebSocket ws, string clientId, ConcurrentDictionarystring, WebSocket connections) { var buffer new byte[1024 * 4]; WebSocketReceiveResult result; while (ws.State WebSocketState.Open) { try { result await ws.ReceiveAsync(new ArraySegmentbyte(buffer), CancellationToken.None); if (result.MessageType WebSocketMessageType.Text) { var receivedText Encoding.UTF8.GetString(buffer, 0, result.Count); Console.WriteLine($ 来自 {clientId}: {receivedText}); // 回显消息 var reply $服务器回显: {receivedText}; var replyBytes Encoding.UTF8.GetBytes(reply); await ws.SendAsync( new ArraySegmentbyte(replyBytes), WebSocketMessageType.Text, true, CancellationToken.None); // 如果需要广播可以调用 Broadcast 方法见下文 // await Broadcast(${clientId}: {receivedText}, connections); } else if (result.MessageType WebSocketMessageType.Close) { await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, 正常关闭, CancellationToken.None); } } catch (WebSocketException) { // 处理异常如连接突然断开 break; } } // 移除断开的连接 connections.TryRemove(clientId, out _); Console.WriteLine($ 客户端 {clientId} 已断开); } // 广播方法可选 static async Task Broadcast(string message, ConcurrentDictionarystring, WebSocket connections) { var bytes Encoding.UTF8.GetBytes(message); var tasks connections.Values .Where(ws ws.State WebSocketState.Open) .Select(ws ws.SendAsync( new ArraySegmentbyte(bytes), WebSocketMessageType.Text, true, CancellationToken.None)); await Task.WhenAll(tasks); }启动运行项目服务端监听在ws://localhost:5000/ws默认端口。6.2 方案二轻量级 Fleck 库Fleck 是一个纯 C# 实现的 WebSocket 服务器不依赖 ASP.NET CoreAPI 极其简洁适合控制台应用、桌面程序或微服务。安装NuGet 包Fleckdotnet add package Fleck基础回声服务器using Fleck; var server new WebSocketServer(ws://0.0.0.0:8181); server.Start(socket { socket.OnOpen () Console.WriteLine(✅ 客户端已连接); socket.OnClose () Console.WriteLine( 客户端已断开); socket.OnMessage message { Console.WriteLine($ 收到: {message}); socket.Send($Echo: {message}); }; }); Console.WriteLine( Fleck 服务器运行在 ws://localhost:8181); Console.ReadLine();广播聊天室版本var allSockets new ListIWebSocketConnection(); var server new WebSocketServer(ws://0.0.0.0:8181); server.Start(socket { socket.OnOpen () { Console.WriteLine(✅ 新客户端连接); allSockets.Add(socket); }; socket.OnClose () { Console.WriteLine( 客户端断开); allSockets.Remove(socket); }; socket.OnMessage message { Console.WriteLine($ 广播: {message}); foreach (var s in allSockets.ToList()) { s.Send(message); // 广播给所有人 } }; }); Console.WriteLine( 聊天室运行在 ws://localhost:8181); Console.ReadLine();Fleck 甚至支持 SSLwss://只需提供证书即可。6.3 方案三HttpListener 自托管如果你不想依赖任何框架可以使用HttpListener实现原生 WebSocket 支持.NET Framework 4.5 / .NET Core。using System.Net; using System.Net.WebSockets; using System.Text; var listener new HttpListener(); listener.Prefixes.Add(http://localhost:8080/); listener.Start(); Console.WriteLine( HttpListener 服务器运行在 ws://localhost:8080); while (true) { var context await listener.GetContextAsync(); if (context.Request.IsWebSocketRequest) { var wsContext await context.AcceptWebSocketAsync(null); var ws wsContext.WebSocket; _ HandleConnection(ws); } else { context.Response.StatusCode 400; context.Response.Close(); } } async Task HandleConnection(WebSocket ws) { var buffer new byte[1024 * 4]; Console.WriteLine(✅ 客户端已连接); while (ws.State WebSocketState.Open) { var result await ws.ReceiveAsync(new ArraySegmentbyte(buffer), CancellationToken.None); if (result.MessageType WebSocketMessageType.Text) { var msg Encoding.UTF8.GetString(buffer, 0, result.Count); Console.WriteLine($ 收到: {msg}); var reply Encoding.UTF8.GetBytes($Echo: {msg}); await ws.SendAsync(new ArraySegmentbyte(reply), WebSocketMessageType.Text, true, CancellationToken.None); } else if (result.MessageType WebSocketMessageType.Close) { await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, 关闭, CancellationToken.None); } } Console.WriteLine( 客户端已断开); }7. 工程实践要点7.1 心跳保活与自动重连网络抖动可能导致连接意外断开我们需要在客户端实现心跳和重连机制。常见的做法是客户端定时发送ping消息服务端回复pong若超时未收到则触发重连。以下是前端增强版客户端的核心逻辑可结合前面的 HTML 使用class ReconnectingWebSocket { constructor(url, options {}) { this.url url; this.heartbeatInterval options.heartbeatInterval || 30000; this.maxRetries options.maxRetries || 5; this.retryCount 0; this.ws null; this.heartbeatTimer null; this.reconnectTimer null; this.connect(); } connect() { this.ws new WebSocket(this.url); this.ws.onopen () { console.log(✅ 连接成功); this.retryCount 0; this.startHeartbeat(); this.onopen this.onopen(); }; this.ws.onmessage (e) { // 如果收到 pong重置心跳计时器 if (e.data pong) { this.resetHeartbeat(); } this.onmessage this.onmessage(e); }; this.ws.onclose () { console.log( 连接断开); this.stopHeartbeat(); this.reconnect(); this.onclose this.onclose(); }; this.ws.onerror (e) { this.onerror this.onerror(e); }; } startHeartbeat() { this.heartbeatTimer setInterval(() { if (this.ws.readyState WebSocket.OPEN) { this.ws.send(ping); } }, this.heartbeatInterval); } resetHeartbeat() { clearInterval(this.heartbeatTimer); this.startHeartbeat(); } stopHeartbeat() { clearInterval(this.heartbeatTimer); } reconnect() { if (this.retryCount this.maxRetries) { console.log(❌ 重连次数已达上限); return; } this.retryCount; const delay Math.min(1000 * Math.pow(2, this.retryCount), 30000); console.log( 将在 ${delay}ms 后第 ${this.retryCount} 次重连); this.reconnectTimer setTimeout(() this.connect(), delay); } send(data) { if (this.ws.readyState WebSocket.OPEN) { this.ws.send(data); } } close() { this.stopHeartbeat(); clearTimeout(this.reconnectTimer); this.ws.close(); } }在服务端以 ASP.NET Core 为例可以在收到ping消息时回复pongif (receivedText ping) { await ws.SendAsync(Encoding.UTF8.GetBytes(pong), WebSocketMessageType.Text, true, CancellationToken.None); }7.2 安全建议使用 wss://生产环境务必使用 TLS 加密防止数据被窃听或篡改。身份认证在握手阶段可以通过 URL 参数或Sec-WebSocket-Protocol携带 JWT Token服务端验证通过后才接受连接。限流控制对每个连接的消息频率进行限制防止恶意客户端发送大量数据导致服务端资源耗尽。输入校验服务端接收到的消息必须进行校验和消毒防止注入攻击如 XSS。7.3 扩展性与负载均衡当服务需要横向扩展多实例时WebSocket 连接是状态化的需要解决消息路由问题。常用方案Redis Pub/Sub当一个实例收到消息后通过 Redis 发布其他实例订阅并推送给各自连接的客户端。消息队列如 RabbitMQ、Kafka用于广播或点对点消息。粘性会话Sticky Sessions在负载均衡器如 Nginx上配置将同一个客户端的请求始终转发到同一台服务器。Nginx 配置示例location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; }7.4 连接管理使用ConcurrentDictionary存储连接确保线程安全。在连接关闭时及时清理资源。设置合理的ReceiveBufferSize和KeepAliveInterval。8. 总结通过本文你已经学习了WebSocket 的核心概念、优势及工作原理浏览器端完整的 API 和示例代码C# 服务端的三种实现方式ASP.NET Core、Fleck、HttpListener心跳、重连、安全、扩展等工程实践要点WebSocket 技术极大地丰富了 Web 应用的实时交互能力无论是在线聊天、协同编辑、游戏还是物联网它都发挥着关键作用。希望这篇教程能帮助你快速上手并在实际项目中灵活运用。阅读原文WebSocket 快速入门教程附示例源码 - 码农刚子的开发笔记