C++ websocket for binance futures stream stuck after connection upgrade

Hi , I’m trying to subscribe to binance futures stream , but I’m stuck after the websocket handshake occurs. Please check my code:

#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <cstring>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/sha.h>
#include <fcntl.h>
using namespace std;

SSL_CTX* initSSLContext() {
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();

    SSL_CTX* sslContext = SSL_CTX_new(TLS_client_method());
    if (!sslContext) {
        cerr << "Error creating SSL context" << endl;
        return nullptr;
    }

    return sslContext;
}

string base64_encode(const unsigned char* input, size_t length) {
    BIO *bio, *b64;
    BUF_MEM *bufferPtr;

    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);

    BIO_write(bio, input, static_cast<int>(length));
    BIO_flush(bio);

    BIO_get_mem_ptr(bio, &bufferPtr);

    string result(bufferPtr->data, bufferPtr->length - 1);  // Exclude newline
    BIO_free_all(bio);

    return result;
}

string generateWebSocketKey() {
    unsigned char nonce[16];
    RAND_bytes(nonce, sizeof(nonce));
    return base64_encode(nonce, sizeof(nonce));
}

int main() {
    // Initialize SSL
    SSL_CTX* sslContext = initSSLContext();
    if (!sslContext) {
        return -1;
    }

    // Create a socket
    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == -1) {
        cerr << "Error creating socket" << endl;
        SSL_CTX_free(sslContext);
        return -1;
    }
    // Resolve the domain name to an IP address
    struct addrinfo hints, *result;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET; 
    if (getaddrinfo("fstream.binance.com", nullptr, &hints, &result) != 0) {
        cerr << "Error resolving domain name" << endl;
        close(clientSocket);
        SSL_CTX_free(sslContext);
        return -1;
    }

    // Set up the server address structure
    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(443);
    serverAddress.sin_addr = ((struct sockaddr_in*)result->ai_addr)->sin_addr;

    // Connect to the server
    if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
        cerr << "Error connecting to the server" << endl;
        close(clientSocket);
        SSL_CTX_free(sslContext);
        return -1;
    }

    // Initialize SSL on the socket
    SSL* ssl = SSL_new(sslContext);
    if (!ssl) {
        cerr << "Error creating SSL structure" << endl;
        close(clientSocket);
        SSL_CTX_free(sslContext);
        return -1;
    }

    SSL_set_fd(ssl, clientSocket);
    if (SSL_connect(ssl) <= 0) {
        cerr << "Error establishing SSL connection" << endl;
        close(clientSocket);
        SSL_free(ssl);
        SSL_CTX_free(sslContext);
        return -1;
    }

    // Free the result of getaddrinfo
    freeaddrinfo(result);

    const string webSocketKey = generateWebSocketKey();
    // Create the WebSocket handshake request with the correct HTTPS scheme
    const string request = "GET /ws HTTP/1.1\r\n"
                          "Host: fstream.binance.com\r\n"
                          "Upgrade: websocket\r\n"
                          "Connection: Upgrade\r\n"
                          "Sec-WebSocket-Key: " + webSocketKey + "\r\n"
                          "Sec-WebSocket-Version: 13\r\n"
                          "\r\n";

    // Send the WebSocket handshake request
    SSL_write(ssl, request.c_str(), request.size());

    char response[4096];
    memset(response, 0, sizeof(response));
    SSL_read(ssl, response, sizeof(response));
    cout << "Server Response:\n" << response << endl;

    const char* subscriptionMessage = " { \"method\": \"SUBSCRIBE\", \"params\": [ \"bnbusdt@aggTrade\" ], \"id\": 45 } ";


    if(SSL_write(ssl, subscriptionMessage, strlen(subscriptionMessage)) <= 0)
    {
        cerr<<"Error sending subscription message"<<endl;
        return -1;

    }
    cout << "Sent Subscription Message:\n" << subscriptionMessage << endl;

    int bytesRead;

    do {
        memset(response, 0, sizeof(response));
        bytesRead = SSL_read(ssl, response, sizeof(response));
        if (bytesRead > 0) {
            cout << "Received WebSocket Data:\n" << response << endl;
            // Check if this is the end of the WebSocket frame or message
            if (strstr(response, "\r\n\r\n") != nullptr) {
                break;
            }
        } else if (bytesRead == 0) {
            cerr << "Still reading" << endl;
            continue;
        } else {
            cerr << "Error reading WebSocket data." << endl;
            break;
        }
    } while (true);

    SSL_shutdown(ssl);
    SSL_free(ssl);
    SSL_CTX_free(sslContext);
    close(clientSocket);

    return 0;
}

The output I’m getting is:

Server Response:
HTTP/1.1 101 Switching Protocols
Date: Wed, 27 Dec 2023 07:14:31 GMT
Connection: upgrade
Upgrade: websocket
Sec-WebSocket-Accept: uCgNFVN5DacUn1fL99CVpQ2lM64=


Sent Subscription Message:
 { "method": "SUBSCRIBE", "params": [ "bnbusdt@aggTrade" ], "id": 45 } 

The execution is stuck at the SSL_read after sending the subscription message.

Please help.

It appears that the issue is related to the way the WebSocket messages are being handled after the initial handshake. WebSocket messages are framed and may not align exactly with the read buffer size. In your current implementation, you are checking for the end of the WebSocket frame by searching for the \r\n\r\n sequence, which may not be accurate.

To properly handle WebSocket messages, you need to implement a more sophisticated frame parsing mechanism. You should probably add a separate new function to to process WebSocket frames. Example:

void processWebSocketFrame(const char* frame, int length) {
    // Implement your WebSocket frame processing logic here
    // You may need to handle fragmentation, opcode, masking, etc.
    // See the WebSocket RFC (https://tools.ietf.org/html/rfc6455) for details.
}

WebSocket messages can be fragmented and must be reassembled to reconstruct the complete message. The WebSocket RFC (RFC 6455) provides details on how to handle WebSocket frames correctly. You may want to consider using an existing WebSocket library in C++ for a more robust solution.

Thanks for the reply. I have successfully implemented the above using Boost library.
But I am worried about the latency , that is why I was trying different approach.
I’ll look into your suggestion and get back if i come across something.