编辑
2025-10-11
C#
00

目录

💡 问题分析:WebSocket开发的常见痛点
🔍 技术难点梳理
🛠️ 解决方案架构设计
📐 整体架构
🔑 核心特性
💻 代码实战:核心模块实现
🎯 客户端连接管理器
🏗️ WebSocket服务器核心
🎨 用户界面实现
🎯 实际应用场景
🧪 测试你的服务器
💬 互动讨论
✨ 核心要点总结

你是否曾经为了实现实时通信功能而苦恼?聊天室、在线游戏、实时数据推送...这些场景都离不开WebSocket技术。作为C#开发者,我们经常需要构建WebSocket服务器,但市面上的教程要么过于简单,要么缺乏完整的生产级代码。

今天这篇文章,我将带你从零开始构建一个功能完整、界面精美、代码健壮的WebSocket服务器应用。不仅包含核心的WebSocket处理逻辑,还提供了WinForms可视化管理界面,让你能够实时监控连接状态、管理客户端、广播消息。

本文将解决的核心问题:

  • 如何构建生产级WebSocket服务器
  • 如何处理多客户端并发连接
  • 如何避免UI线程死锁问题
  • 如何优雅地关闭服务器资源

💡 问题分析:WebSocket开发的常见痛点

🔍 技术难点梳理

在C#中开发WebSocket服务器,开发者通常会遇到以下问题:

  1. 并发处理复杂:多个客户端同时连接,如何保证线程安全?
  2. 资源管理困难:连接异常断开时,如何正确释放资源?
  3. UI线程阻塞:异步操作导致界面卡死,用户体验极差
  4. 状态同步问题:客户端连接状态与UI显示不同步

这些问题在实际项目中经常出现,往往让开发者花费大量时间调试。

🛠️ 解决方案架构设计

📐 整体架构

我们采用分层架构设计,将功能模块化:

C#
WebSocketServer/ ├── WebSocketServerCore.cs // 核心服务器逻辑 ├── ClientConnection.cs // 客户端连接管理 ├── Form1.cs // UI逻辑控制 ├── Form1.Designer.cs // 界面设计 └── Program.cs // 程序入口

🔑 核心特性

  • 异步处理:全程使用async/await避免阻塞
  • 线程安全:ConcurrentDictionary管理客户端连接
  • 优雅关闭:支持超时控制和资源释放
  • 实时监控:可视化界面实时显示连接状态

💻 代码实战:核心模块实现

🎯 客户端连接管理器

首先实现单个客户端连接的封装类:

C#
using System; using System.Collections.Generic; using System.Linq; using System.Net.WebSockets; using System.Text; using System.Threading.Tasks; namespace AppWebSocketServer { public class ClientConnection { public string Id { get; } public WebSocket WebSocket { get; } public string RemoteEndPoint { get; } public DateTime ConnectedTime { get; } public bool 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); } else if (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服务器核心

接下来实现服务器核心逻辑:

C#
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 { public class WebSocketServerCore { private HttpListener _httpListener; private readonly ConcurrentDictionary<string, ClientConnection> _clients; private CancellationTokenSource _cancellationTokenSource; private bool _isRunning; public event EventHandler<ClientConnection> ClientConnected; public event EventHandler<ClientConnection> ClientDisconnected; public event EventHandler<(ClientConnection Client, string Message)> MessageReceived; public event EventHandler<string> LogMessage; public bool IsRunning => _isRunning; public int 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(); } } }

🎨 用户界面实现

创建现代化的管理界面:

C#
using Timer = System.Windows.Forms.Timer; namespace AppWebSocketServer { public partial class Form1 : Form { private WebSocketServerCore _server; private Timer _updateTimer; private bool _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); } } }

image.png

image.png

image.png

image.png

image.png

🎯 实际应用场景

这个WebSocket服务器可以直接应用于:

  1. 实时聊天系统:多人聊天室、客服系统
  2. 在线游戏:实时状态同步、游戏大厅
  3. 数据监控:股票行情、服务器监控面板
  4. 协作工具:在线文档编辑、白板应用

🧪 测试你的服务器

创建简单的HTML测试页面:

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 = new Date().toLocaleTimeString() + ': ' + message; document.getElementById('messages').appendChild(div); } </script> </body> </html>

💬 互动讨论

看完这篇文章,我想问问大家:

  1. 你在WebSocket开发中遇到过哪些棘手问题? 是并发处理,还是资源管理?
  2. 对于实时通信场景,你更倾向于用WebSocket还是SignalR? 各有什么优劣?

欢迎在评论区分享你的经验和想法!如果你在实际应用中遇到问题,也可以贴出代码一起讨论。

✨ 核心要点总结

通过这篇文章,我们完整实现了一个生产级的WebSocket服务器,掌握了三个关键要点:

  1. 🏗️ 架构设计:分层架构 + 事件驱动模式,让代码清晰可维护
  2. ⚡ 性能优化:Task.Run + ConcurrentDictionary + 超时控制,解决并发和死锁问题
  3. 🛡️ 健壮性保证:完善的异常处理 + 优雅关闭机制,确保生产环境稳定运行

这套方案不仅适用于WebSocket开发,其中的异步编程最佳实践WinForms现代化界面设计理念,可以直接应用到其他C#项目中。

觉得这篇文章对你有帮助吗?请转发给更多需要的同行,让我们一起提升C#开发水平! 🚀


关注我,获取更多C#实战干货,从入门到架构师的路上,我们一起成长!

相关信息

通过网盘分享的文件:AppWebSocketServer.zip 链接: https://pan.baidu.com/s/1q_Hb62980Zwt0Y8f0pkd0g?pwd=vn6u 提取码: vn6u --来自百度网盘超级会员v9的分享

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!