首页
/ MDN项目指南:使用C编写WebSocket服务器完整教程

MDN项目指南:使用C编写WebSocket服务器完整教程

2025-07-07 03:13:24作者:裴麒琰

WebSocket作为一种全双工通信协议,在现代Web应用中扮演着重要角色。本文将基于MDN技术文档,深入讲解如何使用C#语言从零开始构建一个符合RFC 6455标准的WebSocket服务器。

一、WebSocket服务器基础架构

1.1 TCP监听器搭建

WebSocket底层基于TCP协议,C#提供了TcpListener类来实现TCP通信:

using System.Net;
using System.Net.Sockets;

class Server {
    public static void Main() {
        // 创建监听本地80端口的TCP监听器
        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);
        
        server.Start();
        Console.WriteLine("服务器已启动,等待客户端连接...");
        
        // 接受客户端连接
        TcpClient client = server.AcceptTcpClient();
        Console.WriteLine("客户端已连接");
    }
}

1.2 网络数据流处理

建立连接后,需要通过NetworkStream进行数据传输:

NetworkStream stream = client.GetStream();

while (true) {
    // 等待数据到达
    while (!stream.DataAvailable);
    
    // 读取可用数据
    byte[] bytes = new byte[client.Available];
    stream.Read(bytes, 0, bytes.Length);
    
    // 后续处理逻辑...
}

二、WebSocket握手协议实现

2.1 识别握手请求

客户端连接时会发送HTTP升级请求,我们需要识别并处理:

string data = Encoding.UTF8.GetString(bytes);

if (Regex.IsMatch(data, "^GET")) {
    // 处理WebSocket握手请求
    HandleHandshake(stream, data);
} else {
    // 其他类型请求处理
}

2.2 构造握手响应

握手响应需要遵循RFC 6455规范:

void HandleHandshake(NetworkStream stream, string request) {
    // 提取Sec-WebSocket-Key
    string swk = Regex.Match(request, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim();
    
    // 拼接GUID并计算SHA1哈希
    string swkAndSalt = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    byte[] hash = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swkAndSalt));
    string base64Hash = Convert.ToBase64String(hash);

    // 构造响应头
    byte[] response = Encoding.UTF8.GetBytes(
        "HTTP/1.1 101 Switching Protocols\r\n" +
        "Connection: Upgrade\r\n" +
        "Upgrade: websocket\r\n" +
        $"Sec-WebSocket-Accept: {base64Hash}\r\n\r\n");
    
    stream.Write(response, 0, response.Length);
}

三、WebSocket消息解析

3.1 消息帧结构分析

WebSocket消息采用特定帧格式:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +

3.2 消息解码实现

bool fin = (bytes[0] & 0b10000000) != 0;
bool mask = (bytes[1] & 0b10000000) != 0;  // 客户端消息必须带掩码
int opcode = bytes[0] & 0b00001111;        // 操作码
ulong msgLen = bytes[1] & 0b01111111;      // 有效载荷长度
ulong offset = 2;

// 处理扩展长度
if (msgLen == 126) {
    msgLen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);
    offset = 4;
} else if (msgLen == 127) {
    msgLen = BitConverter.ToUInt64(new byte[] { 
        bytes[9], bytes[8], bytes[7], bytes[6], 
        bytes[5], bytes[4], bytes[3], bytes[2] 
    }, 0);
    offset = 10;
}

// 解码掩码数据
if (mask && msgLen > 0) {
    byte[] masks = new byte[4] { 
        bytes[offset], bytes[offset+1], 
        bytes[offset+2], bytes[offset+3] 
    };
    offset += 4;
    
    byte[] decoded = new byte[msgLen];
    for (ulong i = 0; i < msgLen; ++i) {
        decoded[i] = (byte)(bytes[offset+i] ^ masks[i % 4]);
    }
    
    string text = Encoding.UTF8.GetString(decoded);
    Console.WriteLine("收到消息: " + text);
}

四、完整服务器实现

将上述组件整合后,完整的WebSocket服务器代码如下:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Security.Cryptography;

class WebSocketServer {
    static void Main() {
        var server = new TcpListener(IPAddress.Parse("127.0.0.1"), 80);
        server.Start();
        
        Console.WriteLine("WebSocket服务器已启动");
        
        var client = server.AcceptTcpClient();
        var stream = client.GetStream();
        
        while (true) {
            while (!stream.DataAvailable);
            while (client.Available < 3);
            
            byte[] bytes = new byte[client.Available];
            stream.Read(bytes, 0, bytes.Length);
            
            string data = Encoding.UTF8.GetString(bytes);
            
            if (Regex.IsMatch(data, "^GET")) {
                // 握手处理
                string swk = Regex.Match(data, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim();
                string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
                byte[] hash = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swka));
                string base64 = Convert.ToBase64String(hash);
                
                byte[] response = Encoding.UTF8.GetBytes(
                    "HTTP/1.1 101 Switching Protocols\r\n" +
                    "Connection: Upgrade\r\n" +
                    "Upgrade: websocket\r\n" +
                    $"Sec-WebSocket-Accept: {base64}\r\n\r\n");
                
                stream.Write(response, 0, response.Length);
            } else {
                // 消息处理
                ProcessWebSocketMessage(bytes, stream);
            }
        }
    }
    
    static void ProcessWebSocketMessage(byte[] bytes, NetworkStream stream) {
        // 消息解码逻辑...
        // 可在此添加消息响应逻辑
    }
}

五、客户端测试方案

5.1 HTML测试页面

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket测试</title>
    <script>
        const ws = new WebSocket("ws://localhost:80");
        
        ws.onopen = () => {
            console.log("连接已建立");
            ws.send("Hello WebSocket!");
        };
        
        ws.onmessage = (e) => {
            console.log("收到消息:", e.data);
        };
    </script>
</head>
<body>
    <h1>WebSocket测试客户端</h1>
</body>
</html>

六、进阶开发建议

  1. 多客户端支持:当前实现仅处理单个客户端,可通过异步编程支持多客户端
  2. 错误处理:添加完善的异常处理机制
  3. 协议扩展:支持WebSocket子协议和扩展
  4. 性能优化:使用缓冲区池减少内存分配

通过本文的详细讲解,您应该已经掌握了使用C#实现WebSocket服务器的核心技术。实际开发中可根据需求进行扩展和优化,构建更强大的实时通信应用。