开发WSS服务器时无法捕获Sec-WebSocket-Key,求解决方法
解决方案:捕获WebSocket握手请求中的Sec-WebSocket-Key
核心思路
你的服务器两次accept是正常现象:第一次是浏览器加载网站的普通HTTP请求,第二次才是页面内发起的WebSocket握手请求。要拿到Sec-WebSocket-Key,关键是区分两种请求并正确解析WebSocket握手的请求头。
具体步骤
过滤非WebSocket请求
WebSocket握手请求有明确标识:- 请求方法为
GET - 包含
Upgrade: websocket请求头 - 包含
Connection: Upgrade请求头
第一次的网站请求不满足这些条件,直接返回标准HTTP响应即可跳过处理。
- 请求方法为
完整读取并解析请求头
基于SSL层开发时,需先完成SSL握手(用SslStream包装Socket),再读取完整的请求头(HTTP头以\r\n\r\n作为结束标记),然后逐行解析找到Sec-WebSocket-Key字段。处理WebSocket握手响应
拿到Sec-WebSocket-Key后,按照WebSocket协议生成Sec-WebSocket-Accept值,返回101状态码的响应完成握手。
代码示例
using System; using System.Net.Security; using System.Net.Sockets; using System.Security.Cryptography; using System.Text; // 初始化监听Socket与SSL证书 Socket listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listenerSocket.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Any, 443)); listenerSocket.Listen(10); while (true) { Socket clientSocket = listenerSocket.Accept(); // 完成SSL握手 SslStream sslStream = new SslStream(clientSocket.GetStream(), false); sslStream.AuthenticateAsServer(yourServerCertificate); // 读取完整请求头 StringBuilder headerBuilder = new StringBuilder(); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = sslStream.Read(buffer, 0, buffer.Length)) > 0) { headerBuilder.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead)); // 检测请求头结束标记 if (headerBuilder.ToString().Contains("\r\n\r\n")) break; } string requestHeader = headerBuilder.ToString(); // 判断是否为WebSocket握手请求 bool isWebSocketHandshake = requestHeader.StartsWith("GET") && requestHeader.Contains("Upgrade: websocket") && requestHeader.Contains("Connection: Upgrade"); if (isWebSocketHandshake) { // 提取Sec-WebSocket-Key string secWebSocketKey = null; string[] headerLines = requestHeader.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (string line in headerLines) { if (line.StartsWith("Sec-WebSocket-Key:", StringComparison.OrdinalIgnoreCase)) { secWebSocketKey = line.Split(new[] { ':' }, 2)[1].Trim(); break; } } if (!string.IsNullOrEmpty(secWebSocketKey)) { // 生成握手响应的Accept值 string acceptKey = GenerateWebSocketAcceptKey(secWebSocketKey); // 构造响应头 string responseHeader = $"HTTP/1.1 101 Switching Protocols\r\n" + $"Upgrade: websocket\r\n" + $"Connection: Upgrade\r\n" + $"Sec-WebSocket-Accept: {acceptKey}\r\n\r\n"; byte[] responseBytes = Encoding.UTF8.GetBytes(responseHeader); sslStream.Write(responseBytes); sslStream.Flush(); // 握手完成,后续可处理WebSocket帧数据 Console.WriteLine($"获取到Sec-WebSocket-Key: {secWebSocketKey}"); } } else { // 处理普通HTTP请求,返回示例响应 string httpResponse = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\n\r\nHello World!"; byte[] responseBytes = Encoding.UTF8.GetBytes(httpResponse); sslStream.Write(responseBytes); sslStream.Flush(); } // 根据业务需求决定是否关闭连接 // clientSocket.Close(); } // 生成WebSocket AcceptKey的工具方法 private static string GenerateWebSocketAcceptKey(string key) { const string websocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; string combined = key + websocketGuid; byte[] sha1Hash = SHA1.HashData(Encoding.UTF8.GetBytes(combined)); return Convert.ToBase64String(sha1Hash); }
注意事项
- 必须确保读取完整的请求头,若只读取部分字节,可能会遗漏
Sec-WebSocket-Key字段。 - SSL握手必须在读取请求头前完成,否则读取的是加密数据,无法解析文本格式的请求头。
- 第一次
accept的HTTP请求可以根据实际业务返回对应的页面内容,避免浏览器报错。
内容的提问来源于stack exchange,提问作者russb
相关产品推荐
相关产品推荐

