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字节的随机掩码密钥,并用该密钥对负载数据进行异或掩码处理。你的代码完全没有处理掩码逻辑:
- 帧头第2字节未设置掩码位(
0x80),服务器收到不符合规范的帧后直接关闭连接。 - 没有生成掩码密钥,也没有对负载数据做掩码处理。
修改发送消息部分的代码,添加完整的掩码逻辑:
/* 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
相关产品推荐
相关产品推荐

