You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的子协议校验逻辑,手动完成握手流程:

  1. 用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);
  1. 读取服务器响应,验证握手成功(忽略多值子协议的问题):
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握手失败");
}
  1. 将已握手的流包装成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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 13:42:42