引言
在当今实时交互日益成为核心需求的网络应用生态中,传统的 HTTP 请求-响应模式已难以满足即时通讯、在线协作、实时数据推送等高互动性场景。为突破这一瓶颈,WebSocket 协议应运而生。
作为一种建立在 TCP 基础上的全双工通信协议,WebSocket 通过在客户端与服务器之间建立持久化的单一连接,实现了高效、低延迟的双向实时数据传输。它不仅克服了轮询与长轮询带来的延迟高、头部开销大、服务器负载重等固有缺陷,更以轻量级的帧结构取代了繁复的 HTTP 请求,真正让实时交互变得简洁而强大。从即时聊天、金融看板到协同编辑、在线游戏,WebSocket 已成为支撑现代实时 Web 应用的基石技术,持续推动着交互体验迈向真正的"实时"时代。
WebSocket是什么
- 它是一种持久化连接协议:一旦客户端与服务器建立 WebSocket 连接,该连接将保持打开状态,直到显式关闭。
- 基于 TCP,但与传统的 HTTP 不同,它不需要每次通信都重新建立连接。
- 使用简单的握手过程(基于 HTTP 升级机制)从 HTTP 切换到 WebSocket 协议。
- 数据可以以"消息"形式双向传输,支持文本(如 JSON)和二进制数据。
解决了什么问题
在 WebSocket 出现之前,Web 应用若想实现"实时通信",通常使用以下技术:
- 轮询(Polling):客户端定时向服务器发送请求,询问是否有新数据。
- 长轮询(Long Polling):客户端发送请求后,服务器保持连接直到有数据才返回。
- SSE(Server-Sent Events):仅支持服务器向客户端单向推送。
这些方法存在明显缺点
- 延迟高:轮询无法做到真正实时
- 资源浪费:频繁建立 HTTP 请求,开销大(头部信息多、连接反复创建)
- 无法双向通信:SSE 仅支持服务器推;轮询仍是"请求-响应"模式。
优势与解决的问题
| 问题 | WebSocket 如何解决 |
|---|---|
| 实时性差 | 建立持久连接,数据可即时推送,延迟极低 |
| 通信单向 | 支持客户端和服务器任意一方主动发送数据(全双工) |
| 高开销 | 握手后通信头部极小,节省带宽和服务器资源 |
| 多次连接消耗 | 一个连接长期复用,减少 TCP 握手和 HTTP 开销 |
典型应用场景
- 聊天应用(如微信网页版)
- 在线协作文档编辑
- 实时游戏
- 股票行情、实时数据仪表盘
- 视频弹幕系统
代码实践
创建websocket的中间件 WebSocketMiddleware
C#
public class `WebSocketMiddleware`
{
private readonly RequestDelegate _next;
private readonly WebSocketHandler _webSocketHandler;
public WebSocketMiddleware(RequestDelegate next, WebSocketHandler webSocketHandler)
{
_next = next;
_webSocketHandler = webSocketHandler;
}
public async Task InvokeAsync(HttpContext context)
{
if (context.Request.Path == "/ws")
{
if (context.WebSockets.IsWebSocketRequest)
{
await _webSocketHandler.HandleWebSocketAsync(context);
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await _next(context);
}
}
}
websocket处理类WebSocketHandler,从cookie中获取用户的登录信息来鉴权(预留代码)
C#
public class WebSocketHandler
{
private readonly WebSocketConnectionManager _connectionManager;
public WebSocketHandler(WebSocketConnectionManager connectionManager)
{
_connectionManager = connectionManager;
}
public async Task HandleWebSocketAsync(HttpContext context)
{
// 从 Cookie 中获取身份信息
var userInfo = await ValidateAndExtractUserInfo(context);
if (userInfo == null)
{
context.Response.StatusCode = 401;
return;
}
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
var connectionId = Guid.NewGuid().ToString();
// 将连接与用户信息关联并存储
_connectionManager.AddConnection(connectionId, webSocket, userInfo);
try
{
await ProcessWebSocketMessages(connectionId, webSocket);
}
finally
{
// 确保连接断开时清理资源
_connectionManager.RemoveConnection(connectionId);
}
}
private async Task ValidateAndExtractUserInfo(HttpContext context)
{
// 从请求中提取 Cookie
//if (!context.Request.Cookies.TryGetValue("auth_token", out var authToken))
//{
// return null;
//}
// 验证身份信息(此处为预留实现)
var userInfo = await ValidateAuthTokenAsync("authToken");
return userInfo;
}
private async Task ValidateAuthTokenAsync(string authToken)
{
// 身份验证逻辑预留
// 实际实现可能包括 JWT 解析、数据库查询等
await Task.CompletedTask;
// 示例返回,实际应根据验证结果返回
return new UserInfo
{
Id = "user123",
UserName = "zhangsan",
ConnectedAt = DateTime.UtcNow
};
}
private async Task ProcessWebSocketMessages(string connectionId, WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
while (webSocket.State == WebSocketState.Open)
{
var result = await webSocket.ReceiveAsync(
new ArraySegment(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(
WebSocketCloseStatus.NormalClosure,
"Connection closed by client",
CancellationToken.None);
break;
}
// 处理收到的消息
await HandleReceivedMessage(connectionId, buffer, result);
}
}
private async Task HandleReceivedMessage(string connectionId, byte[] buffer, WebSocketReceiveResult result)
{
if (result.MessageType == WebSocketMessageType.Text)
{
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.WriteLine($"Received message from {connectionId}: {message}");
}
// 消息处理逻辑
await Task.CompletedTask;
}
}
WebSocketConnectionManager存储用户的websocket连接信息
C#
public class WebSocketConnectionManager
{
// 使用线程安全的字典存储用户连接
private static readonly ConcurrentDictionary> _connections = new();
public void AddConnection(string connectionId, WebSocket webSocket, UserInfo user)
{
if (_connections.ContainsKey(connectionId))
{
_connections[connectionId].Add(webSocket);
}
else
{
_connections.TryAdd(connectionId, new List() { webSocket });
}
}
public void RemoveConnection(string connectionId)
{
_connections.TryRemove(connectionId, out _);
}
public List GetWebSocket(string connectionId)
{
if (!_connections.ContainsKey(connectionId))
{
return [];
}
return _connections.TryGetValue(connectionId, out var socket) ? socket : [];
}
public IEnumerable GetAllSockets()
{
return _connections.Values.SelectMany(list => list).ToList();
}
}
program 配置
C#
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// 注册WebSocketHandler与WebSocketConnectionManager到DI
builder.Services.AddTransient();
// WebSocketConnectionManager要使用单例模式,因为WebSocketConnectionManager中存储了所有WebSocket连接
builder.Services.AddSingleton();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
// 启用websocket
app.UseWebSockets();
// 注册websocket中间件
app.UseMiddleware();
app.Run();
}
}
前端调用
js
// 创建 WebSocket 连接
const socket = new WebSocket('wss://localhost:7233/ws');
// 连接打开时的处理
socket.onopen = function(event) {
console.log('WebSocket 连接已建立');
};
// 接收消息时的处理
socket.onmessage = function(event) {
console.log('收到消息:', event.data);
};
// 连接关闭时的处理
socket.onclose = function(event) {
console.log('WebSocket 连接已关闭');
};
// 发生错误时的处理
socket.onerror = function(error) {
console.error('WebSocket 错误:', error);
};
// 发送消息到服务器
function sendMessage1() {
const message = document.getElementById('message').value;
console.log('发送消息:', message);
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify(message));
}
}
// 关闭连接
function closeConnection() {
socket.close();
}
<div class="text-center">
<h1 class="display-4">webSocket</h1>
<br/>
<div>
</div>
</div>
实际效果


分布式系统中使用
在分布式系统架构中,为实现高效、可扩展的实时消息推送,结合 WebSocket 与 Redis Pub/Sub 是一种经典且强大的设计模式。该模式的核心思路是通过 Redis 的发布订阅机制,解耦 WebSocket 连接所在的业务服务器与触发消息的事件源,从而实现跨进程、跨服务器的实时消息广播。
其工作流程可精炼为以下几个关键步骤:
- 连接建立与订阅 :当用户客户端与某台业务服务器成功建立 WebSocket 连接后,该服务器会立即以用户唯一标识(如 UserID)为频道名,向 Redis 发起订阅(
SUBSCRIBE)。 - 统一的事件发布 :系统中任何服务(如业务逻辑服务、数据库变更监听器)在检测到与该用户相关的数据变化时,无需感知该用户实际连接在哪台服务器,只需向 Redis 对应的用户频道发布(
PUBLISH)一条变更消息。 - 精准的消息路由与推送 :Redis 会将该消息推送至所有订阅了该频道的业务服务器。每台服务器在收到消息后,通过其内部维护的 用户-WebSocket 连接映射表,精准定位到当前连接在本机的该用户所有 WebSocket 会话,并即时将消息下发至对应的客户端。
这种架构的优势在于:
- 解耦与扩展性:发布者与订阅者(WebSocket 服务器)完全解耦,便于系统水平扩展。
- 精准推送:消息仅被推送给关联用户当前所在的服务节点,避免广播风暴。
- 状态分散:各WebSocket服务器仅维护连接到自身的会话,无需全局中央存储,架构更清晰。
综上所述,通过 WebSocket 维持实时连接 ,并借助 Redis Pub/Sub 进行高效的消息分发,共同构建了一个适合分布式环境的实时通信基础设施,有力支撑了聊天、通知、实时数据同步等高并发实时场景。