LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

C# WebSocket服务器开发:从入门到实战,一篇文章搞定!

admin
2025年10月24日 22:27 本文热度 17

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

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

本文将解决的核心问题:

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

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

🔍 技术难点梳理

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

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

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

🛠️ 解决方案架构设计

📐 整体架构

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

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

🔑 核心特性

  • 异步处理
    全程使用async/await避免阻塞
  • 线程安全
    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(thisthis);
            }
        }

        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(7617580)
                : Color.FromArgb(2205369);

            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服务器可以直接应用于:

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

🧪 测试你的服务器

创建简单的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>

💬 互动讨论

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

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

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

✨ 核心要点总结

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

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

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


阅读原文:原文链接


该文章在 2025/10/29 18:55:15 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved