C#WebSocket服務端處理多客戶端連線

慕色寒枝發表於2024-12-09

最佳化 WebSocket 服務端程式碼

原始碼

using System.Text; // 用於傳送和接收資訊時處理文字
using System.Net;   // 用於處理網路連線
using System.Net.WebSockets; // 用於處理WebSocket連線

namespace Test.Program
{
    static class Program
    {
        static HttpListener listener;
        static void Main(string[] args)
        {
            const string HOST = "localhost";     // 監聽的主機名
            const int PORT = 3030;              // 監聽的埠號

            listener = new HttpListener(); // 例項化HttpListener
            listener.Prefixes.Add($"http://{HOST}:{PORT}/");  // 設定監聽的URL
            listener.Start(); // 開始監聽
            ListenClientConnections()
                .Wait();
        }

        async static Task ListenClientConnections()
        {
            while (true)
            {
                // 等待客戶端的連線
                Console.WriteLine("等待客戶端連線");
                HttpListenerContext context = await listener.GetContextAsync();
                // 判斷此連線是否為WebSocket的請求
                if (context.Request.IsWebSocketRequest)
                {
                    Console.WriteLine($"接收到客戶端連線");
                    // 這時候,我們可以用WebSocket來接受客戶端的連線
                    WebSocket webSocket = context.AcceptWebSocketAsync(null).Result.WebSocket;
                    // 這時候我們可以根據需要來處理客戶端的訊息
                    while (webSocket.State == WebSocketState.Open)
                    {
                        // 等待客戶端的訊息
                        byte[] buffer = new byte[1024]; // 定義一個緩衝區大小為1KB
                        ArraySegment<byte> segment = new ArraySegment<byte>(buffer);    // 定義一個陣列段

                        CancellationToken cancellationToken = CancellationToken.None;   // 定義一個取消標記

                        // 這時候這裡一直等待客戶端的訊息
                        Console.WriteLine("等待客戶端訊息");
                        WebSocketReceiveResult result = await webSocket.ReceiveAsync(segment, cancellationToken);

                        // 文字型別的訊息
                        if (result.MessageType == WebSocketMessageType.Text)
                        {
                            // 這裡可以對客戶端的訊息進行處理
                            string message = Encoding.UTF8.GetString(buffer, 0, result.Count);

                            // 給客戶端傳送訊息
                            string responseMessage = $"接收到客戶端的訊息: {message}";
                            Console.WriteLine(responseMessage);
                            byte[] responseBuffer = Encoding.UTF8.GetBytes(responseMessage);

                            await webSocket.SendAsync(
                                responseBuffer,     // 傳送的訊息,位元組陣列型別
                                WebSocketMessageType.Text,  // 訊息型別為文字
                                true,   // 最後一幀,如果為false,則表示後面還有資料
                                CancellationToken.None    // 取消標記
                            );
                        }
                    }
                    Console.WriteLine("客戶端連線斷開");
                }
            }
        }
    }
}

在這個原始碼中,我發現,這段程式碼只支援處理一個客戶端的連線,如果有多個客戶端連線,那麼只有第一個客戶端連線會被處理。

為了支援多個客戶端連線,我們需要修改程式碼,使得 WebSocket 服務端可以同時處理多個客戶端的連線。

1. 重構 ListenClientConnections 方法

只要將等待客戶端連線的部分、等待客戶端訊息的部分、處理訊息的部分、傳送訊息的部分都提取出來,封裝成新的方法即可

  • 建立 ListenClientConnection 方法,用於監聽客戶端連線,並返回 WebSocket 物件。
  • 建立 WebSocketHandler 方法,用於處理 WebSocket 連線,包括接收訊息、處理訊息、傳送訊息。
  • 建立 HandleMessage 方法,用於處理客戶端的訊息、處理業務邏輯。
  • 修改 ListenClientConnections 方法,用於監聽客戶端連線,並建立 WebSocketHandler 任務。

為了跟蹤客戶端連線的數量,我們可以在全域性變數中定義一個 clientCount 變數。

static int clientCount = 0;

1. 建立 ListenClientConnection 方法

用於監聽客戶端連線,並返回 WebSocket 物件。

        private static async Task<WebSocket> ListenClientConnection()
        {
            while (true)
            {
                HttpListenerContext context = await listener.GetContextAsync();
                // 判斷此連線是否為WebSocket的請求
                if (context.Request.IsWebSocketRequest)
                {
                    // 這時候,我們可以用WebSocket來接受客戶端的連線
                    WebSocket webSocket = context.AcceptWebSocketAsync(null).Result.WebSocket;
                    // 這裡我們需要把連線物件返回
                    return webSocket;
                }
            }
        }

2. 建立 WebSocketHandler 方法

用於處理 WebSocket 連線,包括接收訊息、處理訊息、傳送訊息。

private static async Task WebSocketHandler(WebSocket webSocket)
{
    while (webSocket.State == WebSocketState.Open)
    {
        // 等待客戶端的訊息
        byte[] buffer = new byte[1024]; // 定義一個緩衝區大小為1KB
        ArraySegment<byte> segment = new ArraySegment<byte>(buffer);    // 定義一個陣列段
        CancellationToken cancellationToken = CancellationToken.None;   // 定義一個取消標記

        // 這時候這裡一直等待客戶端的訊息
        WebSocketReceiveResult result = await webSocket.ReceiveAsync(segment, cancellationToken);

        // 文字型別的訊息
        if (result.MessageType == WebSocketMessageType.Text)
        {
            string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
            await HandleMessage(webSocket, message);  // 呼叫處理訊息的方法
        }
    }
    Console.WriteLine("客戶端連線斷開");
    clientCount--;  // 連線數量減一

    // 輸出服務端狀態
    Console.WriteLine("接收到客戶端連線");
    Console.WriteLine("---伺服器狀態begin---");
    Console.WriteLine($"連線數量: {clientCount}");
    Console.WriteLine("---伺服器狀態end---");
}

3. 建立 HandleMessage 方法

用於處理客戶端的訊息、處理業務邏輯。

private static async Task HandleMessage(WebSocket webSocket, string message)
{
    Console.WriteLine($"接收到客戶端的訊息: {message}");
    if (message.StartsWith("CLOSE"))
    {
        listener.Close();
    }
    // 這裡可以對客戶端的訊息進行處理
    // 給客戶端傳送訊息
    string responseMessage = $"返回給客戶端訊息: {message}";
    Console.WriteLine(responseMessage);
    byte[] responseBuffer = Encoding.UTF8.GetBytes(responseMessage);
    await webSocket.SendAsync(
        responseBuffer,     // 傳送的訊息,位元組陣列型別
        WebSocketMessageType.Text,  // 訊息型別為文字
        true,   // 最後一幀,如果為false,則表示後面還有資料
        CancellationToken.None    // 取消標記
    );
}

4. 修改 ListenClientConnections 方法

用於監聽客戶端連線,並建立 WebSocketHandler 任務。

async static Task async static Task ListenClientConnections()
{
    while (listener.IsListening)
    {
        Console.WriteLine("等待客戶端連線");
        WebSocket webSocket = await ListenClientConnection();
        clientCount++;  // 連線數量加一
        // 輸出服務端狀態
        Console.WriteLine("接收到客戶端連線");
        Console.WriteLine("---伺服器狀態begin---");
        Console.WriteLine($"連線數量: {clientCount}");
        Console.WriteLine("---伺服器狀態end---");
        WebSocketHandler(webSocket);
    }
}

2. 完整程式碼

using System.Text; // 用於傳送和接收資訊時處理文字
using System.Net;   // 用於處理網路連線
using System.Net.WebSockets; // 用於處理WebSocket連線

namespace Test.Program
{
    static class Program
    {
        static HttpListener listener;
        static int clientCount = 0;
        static void Main(string[] args)
        {
            const string HOST = "localhost";     // 監聽的主機名
            const int PORT = 3030;              // 監聽的埠號

            listener = new HttpListener(); // 例項化HttpListener
            listener.Prefixes.Add($"http://{HOST}:{PORT}/");  // 設定監聽的URL
            listener.Start(); // 開始監聽
            ListenClientConnections()
                .Wait();
        }

        private static async Task<WebSocket> ListenClientConnection()
        {
            while (true)
            {
                HttpListenerContext context = await listener.GetContextAsync();
                // 判斷此連線是否為WebSocket的請求
                if (context.Request.IsWebSocketRequest)
                {
                    // 這時候,我們可以用WebSocket來接受客戶端的連線
                    WebSocket webSocket = context.AcceptWebSocketAsync(null).Result.WebSocket;
                    // 這裡我們需要把連線物件返回
                    return webSocket;
                }
            }
        }

        private static async Task WebSocketHandler(WebSocket webSocket)
        {
            while (webSocket.State == WebSocketState.Open)
            {
                // 等待客戶端的訊息
                byte[] buffer = new byte[1024]; // 定義一個緩衝區大小為1KB
                ArraySegment<byte> segment = new ArraySegment<byte>(buffer);    // 定義一個陣列段
                CancellationToken cancellationToken = CancellationToken.None;   // 定義一個取消標記

                // 這時候這裡一直等待客戶端的訊息
                WebSocketReceiveResult result = await webSocket.ReceiveAsync(segment, cancellationToken);

                // 文字型別的訊息
                if (result.MessageType == WebSocketMessageType.Text)
                {
                    string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
                    await HandleMessage(webSocket, message);  // 呼叫處理訊息的方法
                }
            }
            Console.WriteLine("客戶端連線斷開");
            clientCount--;

            // 輸出服務端狀態
            Console.WriteLine("接收到客戶端連線");
            Console.WriteLine("---伺服器狀態begin---");
            Console.WriteLine($"連線數量: {clientCount}");
            Console.WriteLine("---伺服器狀態end---");
        }

        private static async Task HandleMessage(WebSocket webSocket, string message)
        {
            Console.WriteLine($"接收到客戶端的訊息: {message}");
            if (message.StartsWith("CLOSE"))
            {
                listener.Close();
            }
            // 這裡可以對客戶端的訊息進行處理
            // 給客戶端傳送訊息
            string responseMessage = $"返回給客戶端訊息: {message}";
            Console.WriteLine(responseMessage);
            byte[] responseBuffer = Encoding.UTF8.GetBytes(responseMessage);
            await webSocket.SendAsync(
                responseBuffer,     // 傳送的訊息,位元組陣列型別
                WebSocketMessageType.Text,  // 訊息型別為文字
                true,   // 最後一幀,如果為false,則表示後面還有資料
                CancellationToken.None    // 取消標記
            );
        }


        async static Task ListenClientConnections()
        {
            while (listener.IsListening)
            {
                Console.WriteLine("等待客戶端連線");
                WebSocket webSocket = await ListenClientConnection();
                clientCount++;
                // 輸出服務端狀態
                Console.WriteLine("接收到客戶端連線");
                Console.WriteLine("---伺服器狀態begin---");
                Console.WriteLine($"連線數量: {clientCount}");
                Console.WriteLine("---伺服器狀態end---");
                WebSocketHandler(webSocket);
            }
        }
    }
}

相關文章