WebSocket连接因子协议不匹配终止:C#客户端对接C++服务器问题
我用C#实现WebSocket客户端,对接现有C++编写的WebSocket服务器,服务器支持两个子协议:mySubprotocolOne、mySubProtocolTwo,且无法修改服务器代码。
初始连接代码:
var wsClient = new ClientWebSocket(); wsClient.Options.AddSubProtocol("mySubprotocolOne"); await wsClient.ConnectAsync(new Uri(wsUri), cToken);
通过Wireshark观察到的交互流程:
Client -> Server | HTTP | GET / HTTP/1.1 Server -> Client | HTTP | HTTP/1.1 101 Switching Protocols Client -> Server | TCP | [FIN, ACK] Server -> Client | TCP | [ACK] Server -> Client | TCP | [FIN, ACK]
客户端抛出异常:
Could not establish websocket connection to host ws://ip:port/ - System.Net.WebSockets.WebSocketException (0x80004005): The WebSocket client request requested 'mySubprotocolOne' protocol(s), but server is only accepting 'mySubprotocolOne,mySubProtocolTwo' protocol(s)
服务器响应头:
HTTP/1.1 101 Switching Protocols Upgrade: WebSocket Connection: Upgrade Sec-WebSocket-Accept: someBase64Key Sec-WebSocket-Protocol: mySubprotocolOne,mySubProtocolTwo
添加第二个子协议后问题依旧:
wsClient.Options.AddSubProtocol("mySubProtocolTwo");
子协议名称符合RFC规范,若名称有误服务器不会返回HTTP/1.1 101 Switching Protocols。
问题出在服务器违反了RFC 6455 WebSocket规范:
根据RFC 6455第4.1节的规定,服务器响应握手时,Sec-WebSocket-Protocol头只能返回一个客户端请求过的子协议值,不能返回多个用逗号分隔的列表。C#的ClientWebSocket严格遵循该规范,因此检测到服务器返回多值子协议时,会判定握手失败并主动断开连接。
因为无法修改服务器,只能绕过ClientWebSocket的子协议校验逻辑,手动完成握手流程:
- 用
TcpClient建立TCP连接,手动构造WebSocket握手请求:
using var tcpClient = new TcpClient(); await tcpClient.ConnectAsync("目标IP", 目标端口); using var networkStream = tcpClient.GetStream(); // 生成符合规范的Sec-WebSocket-Key string GenerateSecWebSocketKey() { var randomBytes = new byte[16]; Random.Shared.NextBytes(randomBytes); return Convert.ToBase64String(randomBytes); } // 构造包含子协议的握手请求 var request = $"GET / HTTP/1.1\r\n" + $"Host: 目标IP:目标端口\r\n" + $"Upgrade: websocket\r\n" + $"Connection: Upgrade\r\n" + $"Sec-WebSocket-Key: {GenerateSecWebSocketKey()}\r\n" + $"Sec-WebSocket-Version: 13\r\n" + $"Sec-WebSocket-Protocol: mySubprotocolOne,mySubProtocolTwo\r\n\r\n"; var requestBytes = Encoding.UTF8.GetBytes(request); await networkStream.WriteAsync(requestBytes, 0, requestBytes.Length);
- 读取服务器响应,验证握手成功(忽略多值子协议的问题):
var responseBuffer = new byte[4096]; var bytesRead = await networkStream.ReadAsync(responseBuffer, 0, responseBuffer.Length); var response = Encoding.UTF8.GetString(responseBuffer, 0, bytesRead); if (!response.Contains("HTTP/1.1 101 Switching Protocols")) { throw new InvalidOperationException("WebSocket握手失败"); }
- 将已握手的流包装成
WebSocket对象,指定实际要用的子协议:
var webSocket = WebSocket.CreateFromStream( networkStream, isServer: false, subProtocol: "mySubprotocolOne", // 选一个服务器支持的子协议即可 keepAliveInterval: TimeSpan.FromSeconds(30)); // 后续用webSocket对象进行消息收发
- 再用抓包工具核对服务器返回的
Sec-WebSocket-Protocol头,确认确实是多值格式,排除抓包数据误读的可能 - 尝试第三方WebSocket客户端库(比如WebSocketSharp),部分库对非规范服务器的兼容性更好
- 测试只请求单个子协议时,服务器是否仍返回多值响应,确认服务器的子协议返回逻辑是否是固定问题
- 核对子协议名称的大小写:RFC 6455规定子协议名称大小写敏感,确认客户端请求的名称和服务器返回的完全一致(比如
mySubprotocolOne和mySubProtocolTwo的大小写差异)
内容的提问来源于stack exchange,提问作者Zatox11

