你是否曾经为了实现实时通信功能而苦恼?聊天室、在线游戏、实时数据推送…这些场景都离不开WebSocket技术。作为C#开发者,我们经常需要构建WebSocket服务器,但市面上的教程要么过于简单,要么缺乏完整的生产级代码。
今天这篇文章,我将带你从零开始构建一个功能完整、界面精美、代码健壮的WebSocket服务器应用。不仅包含核心的WebSocket处理逻辑,还提供了WinForms可视化管理界面,让你能够实时监控连接状态、管理客户端、广播消息。
本文将解决的核心问题:
- 如何构建生产级WebSocket服务器
- 如何处理多客户端并发连接
- 如何避免UI线程死锁问题
- 如何优雅地关闭服务器资源
💡 问题分析:WebSocket开发的常见痛点
🔍 技术难点梳理
在C#中开发WebSocket服务器,开发者通常会遇到以下问题:
- 并发处理复杂
- 资源管理困难
- UI线程阻塞
- 状态同步问题
这些问题在实际项目中经常出现,往往让开发者花费大量时间调试。
🛠️ 解决方案架构设计
📐 整体架构
我们采用分层架构设计,将功能模块化:
WebSocketServer/
├── WebSocketServerCore.cs // 核心服务器逻辑
├── ClientConnection.cs // 客户端连接管理
├── Form1.cs // UI逻辑控制
├── Form1.Designer.cs // 界面设计
└── Program.cs // 程序入口
🔑 核心特性
- 异步处理
- 线程安全ConcurrentDictionary管理客户端连接
- 优雅关闭
- 实时监控
💻 代码实战:核心模块实现
🎯 客户端连接管理器
首先实现单个客户端连接的封装类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
namespace AppWebSocketServer
{
publicclass ClientConnection
{
publicstring Id { get; }
public WebSocket WebSocket { get; }
publicstring RemoteEndPoint { get; }
public DateTime ConnectedTime { get; }
publicbool IsConnected => WebSocket.State == WebSocketState.Open;
public event EventHandler<string> MessageReceived;
public event EventHandler<ClientConnection> Disconnected;
private readonly CancellationTokenSource _cancellationTokenSource;
public ClientConnection(WebSocket webSocket, string remoteEndPoint)
{
Id = Guid.NewGuid().ToString("N")[0..8];
WebSocket = webSocket;
RemoteEndPoint = remoteEndPoint;
ConnectedTime = DateTime.Now;
_cancellationTokenSource = new CancellationTokenSource();
// 开始监听消息
Task.Run(async () => await ListenForMessages());
}
private async Task ListenForMessages()
{
var buffer = new byte[4096];
try
{
while (WebSocket.State == WebSocketState.Open && !_cancellationTokenSource.Token.IsCancellationRequested)
{
var result = await WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationTokenSource.Token);
if (result.MessageType == WebSocketMessageType.Text)
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
MessageReceived?.Invoke(this, message);
}
elseif (result.MessageType == WebSocketMessageType.Close)
{
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"客户端 {Id} 监听消息时发生错误: {ex.Message}");
}
finally
{
Disconnected?.Invoke(this, this);
}
}
public async Task SendMessageAsync(string message)
{
if (WebSocket.State == WebSocketState.Open)
{
try
{
var buffer = Encoding.UTF8.GetBytes(message);
await WebSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);
}
catch (Exception ex)
{
Console.WriteLine($"向客户端 {Id} 发送消息时发生错误: {ex.Message}");
}
}
}
public async Task CloseAsync()
{
try
{
_cancellationTokenSource.Cancel();
if (WebSocket.State == WebSocketState.Open)
{
// 设置关闭超时
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
await WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "服务器关闭连接", timeoutCts.Token);
}
}
catch
{
// 忽略关闭时的异常,确保能够继续执行
}
finally
{
try
{
WebSocket?.Dispose();
}
catch
{
}
}
}
public void Dispose()
{
try
{
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
WebSocket?.Dispose();
}
catch
{
}
}
}
}
🏗️ WebSocket服务器核心
接下来实现服务器核心逻辑:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace AppWebSocketServer
{
publicclass WebSocketServerCore
{
private HttpListener _httpListener;
private readonly ConcurrentDictionary<string, ClientConnection> _clients;
private CancellationTokenSource _cancellationTokenSource;
privatebool _isRunning;
public event EventHandler<ClientConnection> ClientConnected;
public event EventHandler<ClientConnection> ClientDisconnected;
public event EventHandler<(ClientConnection Client, string Message)> MessageReceived;
public event EventHandler<string> LogMessage;
publicbool IsRunning => _isRunning;
publicint ClientCount => _clients.Count;
public WebSocketServerCore()
{
_clients = new ConcurrentDictionary<string, ClientConnection>();
}
public Task StartAsync(int port)
{
if (_isRunning)
return Task.CompletedTask;
try
{
_httpListener = new HttpListener();
_httpListener.Prefixes.Add($"http://localhost:{port}/");
_cancellationTokenSource = new CancellationTokenSource();
_httpListener.Start();
_isRunning = true;
LogMessage?.Invoke(this, $"WebSocket 服务器已在端口 {port} 启动");
// 启动后台任务处理连接
_ = Task.Run(ListenForConnections);
return Task.CompletedTask;
}
catch (Exception ex)
{
LogMessage?.Invoke(this, $"启动服务器时发生错误: {ex.Message}");
throw;
}
}
public async Task StopAsync()
{
if (!_isRunning)
return;
try
{
_isRunning = false;
// 取消监听任务
_cancellationTokenSource?.Cancel();
var timeout = TimeSpan.FromSeconds(5);
var cts = new CancellationTokenSource(timeout);
if (_clients.Count > 0)
{
var disconnectTasks = _clients.Values.Select(async client =>
{
try
{
await client.CloseAsync().WaitAsync(TimeSpan.FromSeconds(2));
}
catch
{
}
});
try
{
await Task.WhenAll(disconnectTasks).WaitAsync(timeout);
}
catch (TimeoutException)
{
LogMessage?.Invoke(this, "关闭客户端连接超时,强制关闭");
}
}
_clients.Clear();
try
{
_httpListener?.Stop();
_httpListener?.Close();
}
catch
{
}
LogMessage?.Invoke(this, "WebSocket 服务器已停止");
}
catch (Exception ex)
{
LogMessage?.Invoke(this, $"停止服务器时发生错误: {ex.Message}");
}
}
private async Task ListenForConnections()
{
while (_isRunning && !_cancellationTokenSource.Token.IsCancellationRequested)
{
try
{
var context = await _httpListener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
_ = Task.Run(async () => await HandleWebSocketConnection(context));
}
else
{
context.Response.StatusCode = 400;
context.Response.Close();
}
}
catch (Exception ex) when (_isRunning)
{
LogMessage?.Invoke(this, $"监听连接时发生错误: {ex.Message}");
}
}
}
private async Task HandleWebSocketConnection(HttpListenerContext context)
{
WebSocketContext webSocketContext = null;
ClientConnection client = null;
try
{
webSocketContext = await context.AcceptWebSocketAsync(null);
var remoteEndPoint = context.Request.RemoteEndPoint?.ToString() ?? "Unknown";
client = new ClientConnection(webSocketContext.WebSocket, remoteEndPoint);
client.MessageReceived += OnClientMessageReceived;
client.Disconnected += OnClientDisconnected;
_clients.TryAdd(client.Id, client);
LogMessage?.Invoke(this, $"客户端已连接: {client.Id} ({client.RemoteEndPoint})");
ClientConnected?.Invoke(this, client);
// 发送欢迎消息
await client.SendMessageAsync($"欢迎连接到WebSocket服务器! 您的连接ID: {client.Id}");
// 等待连接断开
while (client.IsConnected && _isRunning)
{
await Task.Delay(1000);
}
}
catch (Exception ex)
{
LogMessage?.Invoke(this, $"处理WebSocket连接时发生错误: {ex.Message}");
if (client != null)
{
OnClientDisconnected(this, client);
}
}
}
private void OnClientMessageReceived(object sender, string message)
{
var client = sender as ClientConnection;
LogMessage?.Invoke(this, $"收到来自客户端 {client.Id} 的消息: {message}");
MessageReceived?.Invoke(this, (client, message));
}
private void OnClientDisconnected(object sender, ClientConnection client)
{
_clients.TryRemove(client.Id, out _);
LogMessage?.Invoke(this, $"客户端已断开连接: {client.Id}");
ClientDisconnected?.Invoke(this, client);
client.Dispose();
}
public async Task BroadcastMessageAsync(string message)
{
if (string.IsNullOrWhiteSpace(message))
return;
var broadcastTasks = new Task[_clients.Count];
var index = 0;
foreach (var client in _clients.Values)
{
broadcastTasks[index++] = client.SendMessageAsync($"[广播消息] {message}");
}
await Task.WhenAll(broadcastTasks);
LogMessage?.Invoke(this, $"已向 {_clients.Count} 个客户端广播消息: {message}");
}
public async Task SendMessageToClientAsync(string clientId, string message)
{
if (_clients.TryGetValue(clientId, out var client))
{
await client.SendMessageAsync(message);
LogMessage?.Invoke(this, $"已向客户端 {clientId} 发送消息: {message}");
}
}
public void Dispose()
{
Task.Run(async () => await StopAsync()).Wait();
_cancellationTokenSource?.Dispose();
}
}
}
🎨 用户界面实现
创建现代化的管理界面:
using Timer = System.Windows.Forms.Timer;
namespace AppWebSocketServer
{
public partial class Form1 : Form
{
private WebSocketServerCore _server;
private Timer _updateTimer;
privatebool _isClosing;
public Form1()
{
InitializeComponent();
InitializeServer();
InitializeUI();
InitializeTimer();
}
private void InitializeServer()
{
_server = new WebSocketServerCore();
_server.ClientConnected += OnClientConnected;
_server.ClientDisconnected += OnClientDisconnected;
_server.MessageReceived += OnMessageReceived;
_server.LogMessage += OnLogMessage;
}
private void InitializeUI()
{
// 设置ListView列
listViewConnections.Columns.Add("连接ID", 100);
listViewConnections.Columns.Add("远程地址", 150);
listViewConnections.Columns.Add("连接时间", 150);
listViewConnections.Columns.Add("状态", 80);
// 绑定事件
btnStart.Click += async (s, e) => await StartServer();
btnStop.Click += async (s, e) => await StopServer();
btnBroadcast.Click += async (s, e) => await BroadcastMessage();
btnClearLogs.Click += (s, e) => richTextBoxLogs.Clear();
// 设置初始状态
UpdateUI();
LogMessage("应用程序已启动", Color.Green);
}
private void InitializeTimer()
{
_updateTimer = new Timer { Interval = 1000 };
_updateTimer.Tick += (s, e) => UpdateConnectionsDisplay();
_updateTimer.Start();
}
private async System.Threading.Tasks.Task StartServer()
{
try
{
if (!int.TryParse(txtPort.Text, out int port) || port < 1 || port > 65535)
{
MessageBox.Show("请输入有效的端口号 (1-65535)", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
await _server.StartAsync(port);
UpdateUI();
}
catch (Exception ex)
{
MessageBox.Show($"启动服务器失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async System.Threading.Tasks.Task StopServer()
{
try
{
await _server.StopAsync();
UpdateUI();
}
catch (Exception ex)
{
MessageBox.Show($"停止服务器失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async System.Threading.Tasks.Task BroadcastMessage()
{
if (string.IsNullOrWhiteSpace(txtBroadcastMessage.Text))
{
MessageBox.Show("请输入要广播的消息", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
try
{
await _server.BroadcastMessageAsync(txtBroadcastMessage.Text);
txtBroadcastMessage.Clear();
}
catch (Exception ex)
{
MessageBox.Show($"广播消息失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void UpdateUI()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateUI));
return;
}
bool isRunning = _server.IsRunning;
btnStart.Enabled = !isRunning;
btnStop.Enabled = isRunning;
txtPort.Enabled = !isRunning;
lblStatus.Text = isRunning ? "运行中" : "已停止";
lblStatus.ForeColor = isRunning
? Color.FromArgb(76, 175, 80)
: Color.FromArgb(220, 53, 69);
lblClientCount.Text = _server.ClientCount.ToString();
}
private void UpdateConnectionsDisplay()
{
if (InvokeRequired)
{
Invoke(new Action(UpdateConnectionsDisplay));
return;
}
}
private void OnClientConnected(object sender, ClientConnection client)
{
if (InvokeRequired)
{
Invoke(new Action<object, ClientConnection>(OnClientConnected), sender, client);
return;
}
var item = new ListViewItem(client.Id);
item.SubItems.Add(client.RemoteEndPoint);
item.SubItems.Add(client.ConnectedTime.ToString("HH:mm:ss"));
item.SubItems.Add("已连接");
item.Tag = client;
listViewConnections.Items.Add(item);
LogMessage($"新客户端连接: {client.Id} ({client.RemoteEndPoint})", Color.Green);
UpdateUI();
}
private void OnClientDisconnected(object sender, ClientConnection client)
{
if (InvokeRequired)
{
Invoke(new Action<object, ClientConnection>(OnClientDisconnected), sender, client);
return;
}
var itemToRemove = listViewConnections.Items
.Cast<ListViewItem>()
.FirstOrDefault(item => ((ClientConnection)item.Tag).Id == client.Id);
if (itemToRemove != null)
{
listViewConnections.Items.Remove(itemToRemove);
}
LogMessage($"客户端断开连接: {client.Id}", Color.Orange);
UpdateUI();
}
private void OnMessageReceived(object sender, (ClientConnection Client, string Message) data)
{
if (InvokeRequired)
{
Invoke(new Action<object, (ClientConnection, string)>(OnMessageReceived), sender, data);
return;
}
LogMessage($"[{data.Client.Id}] {data.Message}", Color.Blue);
}
private void OnLogMessage(object sender, string message)
{
LogMessage(message, Color.Black);
}
private void LogMessage(string message, Color color)
{
if (InvokeRequired)
{
Invoke(new Action<string, Color>(LogMessage), message, color);
return;
}
richTextBoxLogs.SelectionStart = richTextBoxLogs.TextLength;
richTextBoxLogs.SelectionLength = 0;
richTextBoxLogs.SelectionColor = color;
richTextBoxLogs.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}{Environment.NewLine}");
richTextBoxLogs.SelectionColor = richTextBoxLogs.ForeColor;
richTextBoxLogs.ScrollToCaret();
}
private void CleanupResources()
{
try
{
_updateTimer?.Stop();
_updateTimer?.Dispose();
_server?.Dispose();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"清理资源时发生错误: {ex.Message}");
}
}
private void ForceClose()
{
if (InvokeRequired)
{
Invoke(new Action(ForceClose));
return;
}
CleanupResources();
Environment.Exit(0);
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
if (!_isClosing)
{
_isClosing = true;
// 如果服务器正在运行,先停止它
if (_server?.IsRunning == true)
{
// 显示关闭提示
this.Text = "正在关闭服务器...";
this.Enabled = false;
_ = Task.Run(async () =>
{
try
{
await _server.StopAsync();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"关闭服务器时发生错误: {ex.Message}");
}
finally
{
if (!IsDisposed)
{
Invoke(new Action(() =>
{
_updateTimer?.Stop();
_updateTimer?.Dispose();
Application.Exit();
}));
}
}
});
// 取消当前关闭操作,让异步任务处理
e.Cancel = true;
}
else
{
// 服务器未运行,直接清理资源
CleanupResources();
}
}
base.OnFormClosing(e);
}
}
}





🎯 实际应用场景
这个WebSocket服务器可以直接应用于:
- 实时聊天系统
- 在线游戏
- 数据监控
- 协作工具
🧪 测试你的服务器
创建简单的HTML测试页面:
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Client Test</title>
</head>
<body>
<div>
<button onclick="connect()">连接</button>
<button onclick="disconnect()">断开</button>
<button onclick="sendMessage()">发送消息</button>
</div>
<div>
<input type="text" id="messageInput" placeholder="输入消息">
</div>
<div id="messages"></div>
<script>
let ws = null;
function connect() {
ws = new WebSocket('ws://localhost:9000');
ws.onopen = function(event) {
addMessage('已连接到服务器');
};
ws.onmessage = function(event) {
addMessage('收到消息: ' + event.data);
};
ws.onclose = function(event) {
addMessage('连接已关闭');
};
}
function disconnect() {
if (ws) {
ws.close();
}
}
function sendMessage() {
if (ws && ws.readyState === WebSocket.OPEN) {
const message = document.getElementById('messageInput').value;
ws.send(message);
document.getElementById('messageInput').value = '';
}
}
function addMessage(message) {
const div = document.createElement('div');
div.textContent = newDate().toLocaleTimeString() + ': ' + message;
document.getElementById('messages').appendChild(div);
}
</script>
</body>
</html>
💬 互动讨论
看完这篇文章,我想问问大家:
- 你在WebSocket开发中遇到过哪些棘手问题?
- 对于实时通信场景,你更倾向于用WebSocket还是SignalR?
欢迎在评论区分享你的经验和想法!如果你在实际应用中遇到问题,也可以贴出代码一起讨论。
✨ 核心要点总结
通过这篇文章,我们完整实现了一个生产级的WebSocket服务器,掌握了三个关键要点:
- 🏗️ 架构设计
- ⚡ 性能优化Task.Run + ConcurrentDictionary + 超时控制,解决并发和死锁问题
- 🛡️ 健壮性保证完善的异常处理 + 优雅关闭机制,确保生产环境稳定运行
这套方案不仅适用于WebSocket开发,其中的异步编程最佳实践和WinForms现代化界面设计理念,可以直接应用到其他C#项目中。
阅读原文:原文链接
该文章在 2025/10/29 18:55:15 编辑过