C or C++ websocket client working example
Asked Answered
M

1

8

I am new to C and C++. I am trying to find small working example for any websocket library in C or C++ that can connect to websocket server. So, far I have explored, uWebsockets, libwebsockets, websocketpp, and boost::beast. None of them seem to have detailed documentation. I found some examples on boost::beast website at https://www.boost.org/doc/libs/develop/libs/beast/doc/html/beast/examples.html, however they are not working either. If I can find make a single working example I can work on it to learn more.

I tried this command, and it is connecting to yahoo endpoint: wscat -c "wss://streamer.finance.yahoo.com/" -H 'Origin: https://finance.yahoo.com' and print a random string.

wscat -c "wss://streamer.finance.yahoo.com/" -H 'Origin: https://finance.yahoo.com'
Connected (press CTRL+C to quit)
> {"subscribe":["ES=F","YM=F","NQ=F","RTY=F","CL=F","GC=F","SI=F","EURUSD=X","^TNX","^VIX","GBPUSD=X","JPY=X","BTC-USD","^CMC200","^FTSE","^N225","INTC"]}
< CgdCVEMtVVNEFduJQ0cYoP2/2/VeIgNVU0QqA0NDQzApOAFFlmEuP0iAgL/AwQJVlwxHR139ST1HZYBWqUNqC0JpdGNvaW4gVVNEsAGAgL/AwQLYAQTgAYCAv8DBAugBgIC/wMEC8gEDQlRD+gENQ29pbk1hcmtldENhcIECAAAAADbvcUGJAgAAhAG9ZWtC
< CgdCVEMtVVNEFQTtQkcY4KbH2/VeIgNVU0QqA0NDQzApOAFFUznHPkiAgMzPwQJVlwxHR139ST1HZQBrQUNqC0JpdGNvaW4gVVNEsAGAgMzPwQLYAQTgAYCAzM/BAugBgIDMz8EC8gEDQlRD+gENQ29pbk1hcmtldENhcIECAAAAADbvcUGJAgAAND7DT2tC

I tried simple python code like this

from websocket import create_connection
import json
import pprint
import re
import time
import datetime



def subscribe_yahoo ():        
    headers = {
        'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0',
        'Accept': '*/*',
        'Accept-Language': 'en-US,en;q=0.5',
        'Sec-WebSocket-Version': '13',
        'Origin': 'https://finance.yahoo.com',
        'Sec-WebSocket-Key': 'nNtGm/0ZJcrR+goawlJz9w==',
        'DNT': '1',
        'Connection': 'keep-alive, Upgrade',
        'Sec-Fetch-Dest': 'websocket',
        'Sec-Fetch-Mode': 'websocket' ,
        'Sec-Fetch-Site': 'same-site' ,
        'Pragma': 'no-cache',
        'Cache-Control': 'no-cache',
        'Upgrade': 'websocket',
    }


    messages='{"subscribe":["INTC"]}'

    # Initialize the headers needed for the websocket connection
    initMessages = [
       messages,
       
    ]


    
    websocketUri = """wss://streamer.finance.yahoo.com/"""
    print (websocketUri)
           
    ws = create_connection(websocketUri,header=headers)
    for m in initMessages:
        print ("sending ", m)
        ws.send(m)
            
    message_stream = True
    i=0
    while message_stream:
        result = ws.recv()
        i=i+1
        print (str(i),' -- ', result)


subscribe_yahoo()

and it is working too.

I would really appreciate if someone can help me with a code that works similarly in c or c++.

Can someone explain if it is possible to use firefox source code https://searchfox.org/mozilla-central/source/netwerk/protocol/websocket to implement a websocket client in C++ or not, or if someone has used firefox code successfully for websocket client.

I haven't asked for any recommended library, any library will do for my learning purpose. Thanks in advance :)

Following example is copied as is from https://www.boost.org/doc/libs/develop/libs/beast/example/websocket/client/sync-ssl/websocket_client_sync_ssl.cpp

#include "example/common/root_certificates.hpp"

#include <boost/beast/core.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <cstdlib>
#include <iostream>
#include <string>

namespace beast = boost::beast;         // from <boost/beast.hpp>
namespace http = beast::http;           // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio;            // from <boost/asio.hpp>
namespace ssl = boost::asio::ssl;       // from <boost/asio/ssl.hpp>
using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>

// Sends a WebSocket message and prints the response
int main(int argc, char** argv)
{
    try
    {
        // Check command line arguments.
        if(argc != 4)
        {
            std::cerr <<
                "Usage: websocket-client-sync-ssl <host> <port> <text>\n" <<
                "Example:\n" <<
                "    websocket-client-sync-ssl echo.websocket.org 443 \"Hello, world!\"\n";
            return EXIT_FAILURE;
        }
        std::string host = argv[1];
        auto const  port = argv[2];
        auto const  text = argv[3];

        // The io_context is required for all I/O
        net::io_context ioc;

        // The SSL context is required, and holds certificates
        ssl::context ctx{ssl::context::tlsv12_client};

        // This holds the root certificate used for verification
        load_root_certificates(ctx);

        // These objects perform our I/O
        tcp::resolver resolver{ioc};
        websocket::stream<beast::ssl_stream<tcp::socket>> ws{ioc, ctx};

        // Look up the domain name
        auto const results = resolver.resolve(host, port);

        // Make the connection on the IP address we get from a lookup
        auto ep = net::connect(get_lowest_layer(ws), results);

        // Set SNI Hostname (many hosts need this to handshake successfully)
        if(! SSL_set_tlsext_host_name(ws.next_layer().native_handle(), host.c_str()))
            throw beast::system_error(
                beast::error_code(
                    static_cast<int>(::ERR_get_error()),
                    net::error::get_ssl_category()),
                "Failed to set SNI Hostname");

        // Update the host_ string. This will provide the value of the
        // Host HTTP header during the WebSocket handshake.
        // See https://tools.ietf.org/html/rfc7230#section-5.4
        host += ':' + std::to_string(ep.port());

        // Perform the SSL handshake
        ws.next_layer().handshake(ssl::stream_base::client);

        // Set a decorator to change the User-Agent of the handshake
        ws.set_option(websocket::stream_base::decorator(
            [](websocket::request_type& req)
            {
                req.set(http::field::user_agent,
                    std::string(BOOST_BEAST_VERSION_STRING) +
                        " websocket-client-coro");
            }));

        // Perform the websocket handshake
        ws.handshake(host, "/");

        // Send the message
        ws.write(net::buffer(std::string(text)));

        // This buffer will hold the incoming message
        beast::flat_buffer buffer;

        // Read a message into our buffer
        ws.read(buffer);

        // Close the WebSocket connection
        ws.close(websocket::close_code::normal);

        // If we get here then the connection is closed gracefully

        // The make_printable() function helps print a ConstBufferSequence
        std::cout << beast::make_printable(buffer.data()) << std::endl;
    }
    catch(std::exception const& e)
    {
        std::cerr << "Error: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

Compiled using :

g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0

g++ boost_test.cpp -o websocket-client-sync-ssl -lboost_system -pthread -lssl -lcrypto

./websocket-client-sync-ssl
Usage: websocket-client-sync-ssl <host> <port> <text>
Example:
    websocket-client-sync-ssl echo.websocket.org 443 "Hello, world!"

then as suggested:

./websocket-client-sync-ssl echo.websocket.org 443 "Hello, world!"

doesn't work

/websocket-client-sync-ssl streamer.finance.yahoo.com 443 "Hello, world!"
Error: The WebSocket stream was gracefully closed at both endpoints
Mcafee answered 3/9, 2021 at 22:28 Comment(10)
"None of them seem to have detailed documentation". Welcome to the world of open source software. The reality is that this level of documentation is common and you'll need to learn to work with it. I know for a fact that websocketpp docs whilst not perfect are definetely good enough to get started. Again, show code you have tried so we can answer a more specific question.Thalweg
@kaylum: I compiled this example: boost.org/doc/libs/develop/libs/beast/example/websocket/client/… ; and tried using command recommended by the code, which is not working either. ``` websocket-client-sync-ssl echo.websocket.org 443 \"Hello, world!\"\n" ```Mcafee
I've used easywsclient successfully. It includes a couple of sample client programs (one in old C++, one in C++11).Belly
Doesn't look like echo.websocket.org/is available anymore, have you tried using a different server?Stanislaus
@AlanBirtles I tried wss;//streamer.finance.yahoo.com 443 as well.Mcafee
Did you pass wss://... on the command line or just streamer.finance.yahoo.com as shown in the example?Stanislaus
I tried all possible iterations, without any luck.Mcafee
For C++ only, take a look at the Boost "beast" websocket library, here: github.com/boostorg/beast. I haven't tried it yet, but it looks like there is a "Simple WebSocket Client" example here: boost.org/doc/libs/develop/libs/beast/doc/html/beast/….Eldwin
More Boost beast WebSocket exampleshere: github.com/boostorg/beast/tree/develop/example/websocketEldwin
I wonder if sending those headers is counterproductive.Monteith
B
13

Here's a quick demo using easywsclient:

#include "easywsclient.hpp"
#include <iostream>
#include <string>
#include <memory>
#include <mutex>
#include <deque>
#include <thread>
#include <chrono>
#include <atomic>

// a simple, thread-safe queue with (mostly) non-blocking reads and writes
namespace non_blocking {
template <class T>
class Queue {
    mutable std::mutex m;
    std::deque<T> data;
public:
    void push(T const &input) { 
        std::lock_guard<std::mutex> L(m);
        data.push_back(input);
    }

    bool pop(T &output) {
        std::lock_guard<std::mutex> L(m);
        if (data.empty())
            return false;
        output = data.front();
        data.pop_front();
        return true;
    }
};
}

// eastwsclient isn't thread safe, so this is a really simple
// thread-safe wrapper for it.
class Ws {
    std::thread runner;
    non_blocking::Queue<std::string> outgoing;
    non_blocking::Queue<std::string> incoming;
    std::atomic<bool> running { true };

public:
    void send(std::string const &s) { outgoing.push(s); }
    bool recv(std::string &s) { return incoming.pop(s); }

    Ws(std::string url) {
        using easywsclient::WebSocket;

        runner = std::thread([=] {
            std::unique_ptr<WebSocket> ws(WebSocket::from_url(url));
            while (running) {
                if (ws->getReadyState() == WebSocket::CLOSED)
                    break;
                std::string data;
                if (outgoing.pop(data))
                    ws->send(data);
                ws->poll();
                ws->dispatch([&](const std::string & message) {
                    incoming.push(message);
                });
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
            }
            ws->close();
            ws->poll();
        });
    }
    void close() { running = false; }
    ~Ws() { if (runner.joinable()) runner.join(); }
};

int main() {
    Ws socket("ws://localhost:40800");

    std::atomic<bool> run{true};
    auto receiver = std::thread([&] {
        std::string s;
        while (run) {
            if (socket.recv(s))
                std::cout << s << '\n';
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    });

    std::string line;
    while (std::getline(std::cin, line))
        socket.send(line);
    run = false;
    receiver.join();
    socket.close();
}

I tested it with a server using Crow:

// A simple websocket-based echo server
#include "crow_all.h"

int main() { 
    crow::SimpleApp app;

    CROW_ROUTE(app, "/")
        .websocket()
        .onopen([&](crow::websocket::connection& conn){
                CROW_LOG_INFO << "new websocket connection";
                })
        .onclose([&](crow::websocket::connection& conn, const std::string& reason){
                CROW_LOG_INFO << "websocket connection closed: " << reason;
                })
        .onmessage([&](crow::websocket::connection& conn, const std::string& data, bool is_binary){
                    std::cout << "Received message: " << data << "\n";
                    if (is_binary)
                        conn.send_binary(data);
                    else
                        conn.send_text(data);
                });

    app.port(40800)
        .multithreaded()
        .run();
}

I built using this Makefile:

both: client server

INC = -Iexternal/easywsclient/ -Iexternal/crow/build/amalgamate/

LIBS = -leasywsclient -Lexternal/easywsclient -lboost_system -pthread

CXXFLAGS += ${INC}

client: client.o
    ${CXX} -o client client.o ${LIBS}

server: server.o
    ${CXX} -o server server.o ${LIBS}

To test, start the server, then the client. Then you can type random stuff into the client. It'll be sent to the server, printed out there, echoed back to the client, and printed back out there. Pretty much your typical, useless (but enough to prove they're communicating) network demo kind of thing.

Belly answered 4/9, 2021 at 10:38 Comment(8)
Thank you, I compiled your code and it works fine, but I am getting an error when I change first line in main to Ws socket("wss://streamer.finance.yahoo.com/"). ERROR: Could not parse WebSocket url: wss://streamer.finance.yahoo.com/.Mcafee
I don't think easywsclient supports sslStanislaus
@MichaelAuten: Sorry, I didn't notice you wanted to do ssl. For that I'd use IXWebSockets instead. A little more work to use, but supports TLS.Belly
@JerryCoffin: Thank you very much, IXWebSockets worked! The git has working example with wss endpoint. The example compilled after I installed libdeflate. The code was compiled with g++ hello1.cpp -lixwebsocket -pthread -lz -ldeflate -lssl -lcryptoMcafee
Is there a Boost library for websockets?Wise
@user997112: If memory serves, Boost Beast does websockets.Belly
How did to manage to set headers ? like in question.Godunov
This answer looks easy and straightforward, but you first need to download and install the libraries and perhaps update the Makefile. I recommend testing this in a separate VM to try it out first.Margiemargin

© 2022 - 2024 — McMap. All rights reserved.