在构建现代Web应用时,网页与服务器之间往往需要低延迟的数据交换能力。C#作为后端开发的主流语言,可以通过标准WebSocket协议与浏览器建立持久化的双向通道,从而摆脱HTTP短连接的限制。这种通信方式不仅适用于聊天系统,也能支撑实时仪表盘、在线协作编辑和游戏状态同步等场景。

WebSocket协议基础与C#服务端握手原理
WebSocket是一种独立的网络协议,它在TCP之上工作,通过一次HTTP升级请求完成握手,之后便复用同一条TCP连接进行全双工通信。在C#中,ASP.NET Core内置了对WebSocket的支持,核心类型位于System.Net.WebSockets命名空间。当浏览器发送带有Upgrade: websocket头的请求时,服务端可调用HttpContext.WebSockets.AcceptWebSocketAsync方法将连接升级为WebSocket,此时HTTP响应状态码为101,后续数据均以WebSocket帧格式传输。
理解握手过程有助于排查连接失败问题。客户端随机生成Sec-WebSocket-Key,服务端使用固定算法拼接魔法字符串并计算SHA1,再进行Base64编码返回Sec-WebSocket-Accept。C#的AcceptWebSocketAsync已封装该逻辑,但开发者仍须确保中间件管道中未拦截或提前终止该请求。下面代码展示了一个最简的ASP.NET Core端点,用于接受WebSocket连接并回显消息。
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
public class WebSocketHandler
{
public async Task HandleAsync(HttpContext context)
{
if (!context.WebSockets.IsWebSocketRequest)
{
context.Response.StatusCode = 400;
return;
}
// 接受WebSocket连接,完成协议升级
using WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
var buffer = new byte[1024 * 4];
while (webSocket.State == WebSocketState.Open)
{
// 接收客户端发送的文本消息
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "关闭", CancellationToken.None);
break;
}
string received = Encoding.UTF8.GetString(buffer, 0, result.Count);
// 将收到的内容原样回发给客户端
var sendBuffer = Encoding.UTF8.GetBytes("服务端收到:" + received);
await webSocket.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
}
上述代码虽然简单,却揭示了C#处理WebSocket的两个关键点:一是循环调用ReceiveAsync读取帧数据,二是通过SendAsync主动推送。实际项目中,单连接的处理逻辑应抽离为独立服务,以便支持多客户端广播。许多初学者误以为WebSocket像HTTP控制器那样一次请求就结束,实际上AcceptWebSocketAsync后的方法会一直阻塞在接收循环,直到连接关闭。
网页前端如何与C#服务端建立实时双向通道
浏览器原生提供WebSocket构造函数,无需引入第三方库即可连接C#后端。前端在创建实例时传入ws://或wss://地址,随后监听onopen、onmessage、onclose等事件。当C#服务端调用SendAsync发送文本帧时,网页的onmessage会立即触发,从而实现了服务器向客户端的主动推送,这是传统Ajax轮询无法做到的。
为了演示完整链路,下面给出一个HTML页面示例,它连接本地C#服务端的/ws路径,发送用户输入并展示服务端回显。注意在pre代码块中,所有标签名如<input>都已转义,以避免被浏览器解析。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebSocket测试</title>
</head>
<body>
<input type="text" id="msg" placeholder="输入消息" />
<button onclick="sendMsg()">发送</button>
<div id="log"></div>
<script>
var ws = new WebSocket("ws://localhost:5000/ws");
ws.onopen = function () {
document.getElementById("log").innerHTML += "连接已建立<br/>";
};
ws.onmessage = function (evt) {
document.getElementById("log").innerHTML += "收到:" + evt.data + "<br/>";
};
ws.onclose = function () {
document.getElementById("log").innerHTML += "连接断开<br/>";
};
function sendMsg() {
var text = document.getElementById("msg").value;
ws.send(text);
}
</script>
</body>
</html>
在真实网络中,WebSocket连接可能因网络抖动或服务重启而中断,因此前端必须实现重连机制。一种常见做法是在onclose事件中使用setTimeout延迟重新实例化WebSocket对象,并采用指数退避策略避免雪崩。C#服务端则应在CloseAsync时传递明确的关闭状态,方便前端区分正常维护和异常掉线。通过这种前后端配合,双向通信的鲁棒性会大幅提升。
进阶场景下的连接管理与性能优化策略
当系统需要支撑数千并发WebSocket连接时,C#服务端不能对每个连接都开启独立线程。ASP.NET Core基于异步I/O模型,ReceiveAsync和SendAsync均为可等待方法,底层使用完成端口,因此单台服务器可维持大量空闲连接而不消耗线程资源。但开发者仍需维护连接字典,以便向特定用户或群组广播消息。例如使用ConcurrentDictionary<string, WebSocket>存储已认证的连接标识。
消息序列化也是进阶重点。直接发送字符串虽简单,但结构化数据应使用JSON或MessagePack。C#侧可借助System.Text.Json将对象转为UTF8字节再发送;网页端用JSON.parse还原。需要注意的是,WebSocket帧有大小限制,超大负载应分片发送,C#中通过将SendAsync的endOfMessage参数设为false实现分片,前端则拼接多次onmessage数据。以下代码展示服务端向所有连接广播JSON的片段。
using System.Collections.Concurrent;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
public class BroadcastService
{
private readonly ConcurrentDictionary<string, WebSocket> _clients = new();
public void Add(string id, WebSocket socket) => _clients.TryAdd(id, socket);
public async Task BroadcastAsync(object payload)
{
var json = JsonSerializer.Serialize(payload);
var bytes = Encoding.UTF8.GetBytes(json);
foreach (var client in _clients.Values)
{
if (client.State == WebSocketState.Open)
{
await client.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, CancellationToken.None);
}
}
}
}
除了广播,还应关注心跳机制。由于代理或负载均衡器可能回收空闲TCP连接,C#服务端可定时发送Ping帧,浏览器自动回复Pong。在System.Net.WebSockets中,发送WebSocketMessageType.Text之外的控制帧需调用特定API,或依赖底层实现。若使用第三方库如WebSocketSharp,则提供了更直观的SendPing方法。综合来看,C#与网页的实时双向通信不仅是协议连通,更涉及连接生命周期、异常恢复与资源占用的系统工程考量。