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

C语言WebSocket客户端连接Deribit失败,发送消息后被关闭

问题

出于学习兴趣,我编写了一个简易C语言应用,通过SSL实现WebSocket客户端以连接Deribit平台。该应用能正常完成与Deribit的连接及WebSocket握手,但发送消息后无法正常接收响应,反而收到连接关闭帧。

程序代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define PRINTERR() fprintf(stderr, "%s:L%i: error\n", __FILE__, __LINE__)

int main()
{
    /* connection*/
    const char *host = "www.deribit.com";
    const char *path = "/ws/api/v2";
    int port = 443;

    struct sockaddr_in server_addr;
    struct hostent *server;

    // Create socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        PRINTERR();
        return -1;
    }

    // Configure server address
    server = gethostbyname(host);
    if (server == NULL) {
        PRINTERR();
        return -1;
    }

    bzero((char *) &server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr,
          (char *)&server_addr.sin_addr.s_addr,
          server->h_length);
    server_addr.sin_port = htons(port);

    // Connect to server
    if (connect(sockfd,(struct sockaddr *) &server_addr,sizeof(server_addr)) < 0) {
        PRINTERR();
        return -1;
    }

    // Initialize SSL context
    SSL_CTX *ssl_context;
    SSL *ssl;
    SSL_library_init();
    SSL_load_error_strings();
    ssl_context = SSL_CTX_new(TLS_client_method());
    if (!ssl_context) {
        PRINTERR();
        return -1;
    }

    SSL_CTX_set_verify(ssl_context, SSL_VERIFY_PEER, NULL);

    // NOTE: assuming Ubuntu
    if (SSL_CTX_load_verify_locations(ssl_context, "/etc/ssl/certs/ca-certificates.crt", NULL) != 1) {
        PRINTERR();
        return -1;
    }


    // Connect SSL over the socket
    ssl = SSL_new(ssl_context);
    SSL_set_fd(ssl, sockfd);

    int ret = SSL_connect(ssl);
    if (ret != 1) {
        PRINTERR();
        return -1;
    } else {
        long ret = SSL_get_verify_result(ssl);
        if (ret != X509_V_OK) {
            PRINTERR();
            return -1;
        }
    }

    printf("connected to deribit (sockfd: %i)\n", sockfd);

    /* handshake */
    char request[1024];
    int request_len;
    char response[1024];
    int response_len;

    // Prepare the WebSocket handshake request
    request_len = sprintf(request, "GET %s HTTP/1.1\r\n"
                                   "Host: %s\r\n"
                                   "Upgrade: websocket\r\n"
                                   "Connection: Upgrade\r\n"
                                   "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
                                   "Sec-WebSocket-Version: 13\r\n\r\n",
                                   path, host);
    // Send the WebSocket handshake request
    if (SSL_write(ssl, request, request_len) < 0) {
        PRINTERR();
        return -1;
    }

    // Receive the WebSocket handshake response
    response_len = SSL_read(ssl, response, sizeof(response));
    if (response_len < 0) {
        PRINTERR();
        return -1;
    }

    // Check if the connection has been upgraded to a WebSocket connection
    if (strstr(response, "HTTP/1.1 101 Switching Protocols") &&
        strstr(response, "upgrade: websocket") &&
        strstr(response, "Connection: upgrade")) {
            printf("WebSocket handshake successful\n");
    } else {
        printf("WebSocket handshake failed\n");
        return -1;
    }

    /* send message */
    char req[1024];
    snprintf(
        req,
        sizeof(req),
        "{\"jsonrpc\":\"2.0\",\"id\":8212,\"method\":\"public/test\",\"params\":{}}"
    );

    int message_length = strlen(req);
    int sent_bytes = 0;
    char frame[10 + message_length];
    int frame_length = 2 + message_length;

    // Set the frame header
    frame[0] = 0x81; // Fin + Text opcode
    if (message_length <= 125)
    {
        frame[1] = (char) message_length;
        frame_length = 2 + message_length;
    }
    else if (message_length <= 65535)
    {
        frame[1] = 126;
        frame[2] = (char) (message_length >> 8);
        frame[3] = (char) (message_length & 0xFF);
        frame_length = 4 + message_length;
    }
    else
    {
        frame[1] = 127;
        frame[2] = (char) (message_length >> 56);
        frame[3] = (char) (message_length >> 48);
        frame[4] = (char) (message_length >> 40);
        frame[5] = (char) (message_length >> 32);
        frame[6] = (char) (message_length >> 24);
        frame[7] = (char) (message_length >> 16);
        frame[8] = (char) (message_length >> 8);
        frame[9] = (char) (message_length & 0xFF);
        frame_length = 10 + message_length;
    }

    // Copy the message into the frame
    memcpy(frame + 2, req, message_length);

    // Send the frame over SSL
    sent_bytes = SSL_write(ssl, frame, frame_length);

    /* receive message */
    unsigned char buf[4096];
    unsigned char mask[4];
    int bytes_received;
    int payload_length;
    unsigned char frame_header[2];
    bytes_received = SSL_read(ssl, frame_header, 2);

    if (bytes_received < 0) {
        PRINTERR();
        return -1;
    }


    if (frame_header[0] == 0x88) {
        printf("connection closed by deribit\n");
    }

    payload_length = (frame_header[1] & 0x7F);
    if (payload_length == 126) {
        bytes_received = SSL_read(ssl, &payload_length, 2);
        if (bytes_received < 0) {
            PRINTERR();
            return -1;
        }
    } else if (payload_length == 127) {
        bytes_received = SSL_read(ssl, &payload_length, 8);
        if (bytes_received < 0) {
            PRINTERR();
            return -1;
        }
    }

   if (frame_header[1] & 0x80) {
        bytes_received = SSL_read(ssl, mask, 4);
        if (bytes_received < 0) {
            PRINTERR();
            return -1;
        }
    }

    bytes_received = SSL_read(ssl, buf, payload_length);
    if (bytes_received < 0) {
        PRINTERR();
        return -1;
    }
    if (frame_header[1] & 0x80) {
        //unmask_message(buf, mask, bytes_received);
    }

    printf("frame header received: [0]: %d, [1]: %d\n", frame_header[0], frame_header[1]);
    printf("payload_length: %i\n", (frame_header[1] & 0x7F));
    printf("mask:           %i\n", (frame_header[1] & 0x80));
    printf("buffer: %s\n", buf);

    return 0;
}

运行代码后可见,无论发送何种消息,应用都会立即收到Deribit发送的连接关闭消息(frame_header[1] & 0x80为真)。请问该问题源于SSL处理还是WebSocket实现?我已尝试直接使用sockfd不通过SSL、检查证书一致性、发送不同消息,但问题仍存在。

分析与解决

问题出在WebSocket帧的掩码处理,和SSL无关。

根据WebSocket规范(RFC 6455),客户端发送给服务器的所有帧必须携带掩码:帧头第2字节的最高位必须设为1,同时要在帧头后附加4字节的随机掩码密钥,并用该密钥对负载数据进行异或掩码处理。你的代码完全没有处理掩码逻辑:

  1. 帧头第2字节未设置掩码位(0x80),服务器收到不符合规范的帧后直接关闭连接。
  2. 没有生成掩码密钥,也没有对负载数据做掩码处理。

修改发送消息部分的代码,添加完整的掩码逻辑:

/* send message */
char req[1024];
snprintf(
    req,
    sizeof(req),
    "{\"jsonrpc\":\"2.0\",\"id\":8212,\"method\":\"public/test\",\"params\":{}}"
);

int message_length = strlen(req);
int sent_bytes = 0;
// 预留4字节掩码空间
char frame[10 + 4 + message_length];
int frame_length = 0;

// 设置帧头
frame[0] = 0x81; // Fin + Text opcode
// 客户端帧必须带掩码,所以第2字节最高位设为1
if (message_length <= 125)
{
    frame[1] = 0x80 | (char) message_length; // 掩码位+负载长度
    frame_length = 2 + 4 + message_length;
}
else if (message_length <= 65535)
{
    frame[1] = 0x80 | 126;
    frame[2] = (char) (message_length >> 8);
    frame[3] = (char) (message_length & 0xFF);
    frame_length = 4 + 4 + message_length;
}
else
{
    frame[1] = 0x80 | 127;
    frame[2] = (char) (message_length >> 56);
    frame[3] = (char) (message_length >> 48);
    frame[4] = (char) (message_length >> 40);
    frame[5] = (char) (message_length >> 32);
    frame[6] = (char) (message_length >> 24);
    frame[7] = (char) (message_length >> 16);
    frame[8] = (char) (message_length >> 8);
    frame[9] = (char) (message_length & 0xFF);
    frame_length = 10 + 4 + message_length;
}

// 生成随机掩码密钥
unsigned char mask[4];
for (int i=0; i<4; i++) {
    mask[i] = rand() % 256;
}
// 将掩码复制到帧中
int mask_offset = frame_length - 4 - message_length;
memcpy(frame + mask_offset, mask, 4);

// 对负载数据进行掩码处理并复制到帧中
int payload_offset = frame_length - message_length;
for (int i=0; i<message_length; i++) {
    frame[payload_offset + i] = req[i] ^ mask[i % 4];
}

// 发送帧
sent_bytes = SSL_write(ssl, frame, frame_length);

同时需要实现接收部分的解掩码函数,用于处理服务器返回的带掩码帧:

void unmask_message(unsigned char *buf, unsigned char *mask, int length) {
    for (int i=0; i<length; i++) {
        buf[i] ^= mask[i % 4];
    }
}

修改后,客户端发送的帧符合WebSocket规范,Deribit服务器会正常返回响应而非关闭连接。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 12:15:33