你是否曾经为了实现实时通信功能而苦恼?聊天室、在线游戏、实时数据推送...这些场景都离不开WebSocket技术。作为C#开发者,我们经常需要构建WebSocket服务器,但市面上的教程要么过于简单,要么缺乏完整的生产级代码。
今天这篇文章,我将带你从零开始构建一个功能完整、界面精美、代码健壮的WebSocket服务器应用。不仅包含核心的WebSocket处理逻辑,还提供了WinForms可视化管理界面,让你能够实时监控连接状态、管理客户端、广播消息。
本文将解决的核心问题:
在C#中开发WebSocket服务器,开发者通常会遇到以下问题:
这些问题在实际项目中经常出现,往往让开发者花费大量时间调试。
我们采用分层架构设计,将功能模块化:
C#WebSocketServer/
├── WebSocketServerCore.cs // 核心服务器逻辑
├── ClientConnection.cs // 客户端连接管理
├── Form1.cs // UI逻辑控制
├── Form1.Designer.cs // 界面设计
└── Program.cs // 程序入口
首先实现单个客户端连接的封装类:
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
{
}
}
}
}
接下来实现服务器核心逻辑:
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);
}
}
}
这个WebSocket服务器可以直接应用于:
创建简单的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>
看完这篇文章,我想问问大家:
欢迎在评论区分享你的经验和想法!如果你在实际应用中遇到问题,也可以贴出代码一起讨论。
通过这篇文章,我们完整实现了一个生产级的WebSocket服务器,掌握了三个关键要点:
这套方案不仅适用于WebSocket开发,其中的异步编程最佳实践和WinForms现代化界面设计理念,可以直接应用到其他C#项目中。
觉得这篇文章对你有帮助吗?请转发给更多需要的同行,让我们一起提升C#开发水平! 🚀
关注我,获取更多C#实战干货,从入门到架构师的路上,我们一起成长!
相关信息
通过网盘分享的文件:AppWebSocketServer.zip 链接: https://pan.baidu.com/s/1q_Hb62980Zwt0Y8f0pkd0g?pwd=vn6u 提取码: vn6u --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!