You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C语言WebSocket服务器适配NGINX代理问题求助

嘿,我太懂你这种换语言实现后踩坑的感受了——原本用Node.js ws库跑的WebSocket服务在NGINX代理下顺顺当当,换成C语言实现后就出问题,大概率是C的实现没完全贴合WebSocket协议规范,或者和NGINX的代理逻辑没对上。我来帮你一步步排查解决:

第一步:先跳过NGINX,确认C服务本身能正常工作

先把NGINX暂时排除,直接用客户端连接C服务的端口,验证服务本身没问题:

  • 用工具wscat -c ws://localhost:你的端口测试,如果连不上或者通信异常,那问题肯定出在C服务本身,先把这个基础问题搞定再碰NGINX。
  • 如果直接连接正常,但过NGINX就炸,再往下看配置和协议细节。
第二步:检查C服务的WebSocket握手逻辑是否合规

NGINX能正确代理WebSocket,完全依赖标准的HTTP升级握手,很多C的“最简示例”会省略关键步骤,这是最常见的坑:

  1. 必须正确解析客户端请求头:要确保能识别Upgrade: websocketConnection: UpgradeSec-WebSocket-KeySec-WebSocket-Version: 13这些必填字段,少一个都不行。
  2. 必须返回正确的握手响应
    • 响应状态码必须是101 Switching Protocols
    • 响应头必须包含Upgrade: websocketConnection: Upgrade
    • 用客户端发来的Sec-WebSocket-Key加上固定字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11,做SHA-1哈希后转成Base64,作为Sec-WebSocket-Accept的值返回。
      很多C示例要么哈希/Base64处理出错,要么漏了响应头,直接导致NGINX不认这个WebSocket连接。
第三步:确认NGINX配置的WebSocket适配项

虽然你之前用Node.js时配置是好的,但还是再核对一遍,确保这几个关键配置没丢:

location /ws {
    proxy_pass http://localhost:你的C服务端口;
    proxy_http_version 1.1;  # 必须是1.1,WebSocket依赖HTTP/1.1的Upgrade机制
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    # 如果有跨域需求,加上跨域头
    # add_header Access-Control-Allow-Origin *;
}

尤其是proxy_http_version 1.1和那两个Upgrade相关的头,少一个NGINX都没法正确转发WebSocket流量。

第四步:检查C服务的WebSocket帧处理是否规范

握手成功后,WebSocket通信是基于帧的,C服务必须处理这些细节:

  • 客户端帧的掩码解码:RFC6455规定客户端发的帧必须带掩码,服务端必须解码;而服务端发给客户端的帧不能带掩码。很多简单的C实现直接跳过掩码处理,导致数据乱码,看起来像“异常”。
  • 帧格式完整性:要正确处理操作码(文本帧是0x1、二进制是0x2、关闭帧是0x8等)、payload长度的编码(短长度、扩展长度),如果只处理短帧,遇到大消息直接崩溃。
  • PING/PONG心跳:如果C服务不处理PING帧,NGINX可能会因为超时自动断开连接;不处理关闭帧的话,连接没法正常释放。
给你一个极简的C服务核心代码参考

如果你的C示例有问题,这里贴一个握手和掩码处理的核心片段(需要链接OpenSSL库-lcrypto做SHA-1):

// 生成Sec-WebSocket-Accept的核心逻辑
#include <openssl/sha.h>
#include <string.h>

void generate_websocket_accept(const char *key, char *accept_buf) {
    char temp_buf[100];
    strcpy(temp_buf, key);
    strcat(temp_buf, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
    
    unsigned char sha1_hash[20];
    SHA1((unsigned char*)temp_buf, strlen(temp_buf), sha1_hash);
    
    // 这里需要自己实现Base64编码,或者用现成的实现
    base64_encode(sha1_hash, 20, accept_buf);
}

// 握手响应模板
const char *handshake_response = 
    "HTTP/1.1 101 Switching Protocols\r\n"
    "Upgrade: websocket\r\n"
    "Connection: Upgrade\r\n"
    "Sec-WebSocket-Accept: %s\r\n\r\n";

// 解码客户端帧的掩码
void unmask_client_frame(unsigned char *frame, int frame_len) {
    if (!(frame[0] & 0x80)) return; // 不是客户端帧(客户端帧FIN位必设)
    
    unsigned char mask[4] = {frame[2], frame[3], frame[4], frame[5]};
    int payload_len = frame[1] & 0x7F;
    
    for (int i = 0; i < payload_len; i++) {
        frame[6 + i] ^= mask[i % 4];
    }
}

总的来说,先从“C服务能否直接被连接”开始排查,再逐步确认握手逻辑、NGINX配置、帧处理细节,大部分情况都是握手或者掩码处理出错导致的。

内容的提问来源于stack exchange,提问作者wuampa

火山引擎 最新活动