Crow icon indicating copy to clipboard operation
Crow copied to clipboard

Websocket random generator - while loop

Open DonNagual opened this issue 1 year ago • 3 comments

Hello

I want to generate a random number on the websocket and send it periodically to a client. When I generate a random number and connect to a client, I get this number. But as soon as I do it in a while loop, I get nothing. I will keep looking for a solution. Would be nice if someone could give me a nudge in the right direction.

This is my code:

#include "crow.h"
#include <iostream>
#include <random>
#include <nlohmann/json.hpp>
#include <chrono>
#include <thread>

using json = nlohmann::json;
constexpr int thread_sleep_periode = 1000; //millisecond

// Function for generating a random number between min and max
int generateRandomNumber(int min, int max) {
	// initializing the random number generator
	std::random_device rd;	// 'real' random generator
	std::mt19937 gen(rd()); // Mersenne Twister-Algorithm
	std::uniform_int_distribution<int> distribution(min, max); // uniform distribution
	return distribution(gen);
}

int main()
{
	crow::SimpleApp app; // define your crow application

	// Route-Definition
	CROW_WEBSOCKET_ROUTE(app, "/")
		.onopen([&](crow::websocket::connection& conn) {
			// Debug
				std::cout << "Websocket connection opened: " << std::endl;
			while(true){
				// Generate random number
				int randomNumber = generateRandomNumber(1, 100);
				// Send random number to the client
				json jsonData = {
					{"randomNumber", randomNumber}
				};
				// Send JSON as text to the client
				conn.send_text(jsonData.dump());
				std::this_thread::sleep_for(std::chrono::milliseconds(thread_sleep_periode));
			}
		})
		.onclose([&](crow::websocket::connection& conn, const std::string& reason) {
			// Debug
				std::cout << "Websocket connection closed: " << reason << std::endl;
		});

	// Start the Websocket_Server on port 8080
	app.bindaddr("127.0.0.1")
		.port(8080)
		.multithreaded()
		.run();
}

DonNagual avatar May 03 '24 09:05 DonNagual

I have an idea, but don't know, whether this is idiomatic for crow

#include <iostream>
#include <thread>
#include <atomic>
#include <random>
#include <chrono>
#include <crow.h>
#include <asio.hpp>
#include <nlohmann/json.hpp>

constexpr int max_threads{999};
constexpr int min_random{1};
constexpr int max_random{100};
constexpr std::chrono::milliseconds thread_sleep_periode{1000};
asio::io_context io_context; //needed for thread_pool
asio::thread_pool our_thread_pool(max_threads); //datastructure that holds the threads
std::atomic<bool> shouldExit{false}; //needed for closing threads on exit

using json = nlohmann::json;

void workerThread(crow::websocket::connection& conn) {

    // your workload

    std::random_device rd;	// 'real' random generator
    std::mt19937 gen(rd()); // Mersenne Twister-Algorithm
    std::uniform_int_distribution<int> distribution(min_random, max_random);

    while (!shouldExit) {
        int randomNumber = distribution(gen);
        json jsonData = { {"randomNumber", randomNumber} };
        conn.send_text(jsonData.dump());
        std::this_thread::sleep_for(thread_sleep_periode);
    }
}

int main() {
    crow::SimpleApp app;

    CROW_ROUTE(app, "/websocket")
    .websocket()
    .onopen([](crow::websocket::connection& conn) {
        asio::post(our_thread_pool, [&conn]() { workerThread(conn); }); 
    })
    .onclose([](crow::websocket::connection& conn, const std::string& reason) {
        shouldExit = true; //set exit-flag
    });

    // Start the Websocket_Server on port 8080
    app.bindaddr("127.0.0.1")
		.port(8080)
		.multithreaded()
		.run();
    our_thread_pool.join(); //kill all threads when server exits

    return 0;
}

In this example, I use a asio::thread_pool. The most interesting part IMHO is .onopen([]crow::websocket::connection& conn) {asio::post(our_thread_pool, [&conn]() {workerThread(conn);});}) This uses the asio::post method to send a lambda to a given thread_pool. The lambda captures the connection by reference, since it needs the actual link to the client that connected. This reference is given to the function, that handles your application code.

This is not tested but maybe it's a good direction ¯_(ツ)_/¯ hope this helps

MaxClerkwell avatar May 03 '24 13:05 MaxClerkwell

The idea worked really well. Thanks for that. Now I'm faced with the next problem. As soon as I close the thread, I can't establish a new connection. I have to restart the websocket server, only then can I establish a new connection. I will continue to work on the solution.

DonNagual avatar May 03 '24 15:05 DonNagual

the thread ends because you set

shouldExit = true; //set exit-flag

in on_close of each socket. See the condition of the while loop.

gittiver avatar May 03 '24 18:05 gittiver

In the original code, a single global std::atomic<bool> shouldExit is used to track the state whether the programme should be terminated or not. As a result, as soon as a client logs out, the programme is also terminated for all other clients and a new login is not possible. The problem is solved by introducing a std::unordered_map<crow::websocket::connection*, ClientData> to store individual status information for each WebSocket client. ClientData is a struct that contains a std::atomic<bool> shouldExit to track the state of the respective client. With this change, it is possible to keep the status information for each client separately and thus manage them independently of each other. Here is the customised code:

#include "crow.h"
#include <iostream>
#include <random>
#include <nlohmann/json.hpp>
#include <chrono>
#include <thread>
#include <atomic>
#include <asio.hpp>
#include <unordered_map>

constexpr int max_threads{999};
constexpr int min_random{1};
constexpr int max_random{100};
constexpr std::chrono::milliseconds thread_sleep_periode{1000};
asio::io_context io_context; //needed for thread_pool
asio::thread_pool our_thread_pool(max_threads); // datastructure that holds the threads

using json = nlohmann::json;

// Struct to hold per-clinet data
struct ClientData {
        std::atomic<bool> shouldExit{false};    // needed for closing threads on exit
};

// Map to store client data using the connection pointer as key
std::unordered_map<crow::websocket::connection*, ClientData> clientDataMap;

void workerThread(crow::websocket::connection& conn, ClientData& clientData) {

        std::random_device rd;  // 'real' random generator
        std::mt19937 gen(rd()); // Mersenne Twister-Algorithm
        std::uniform_int_distribution<int> distribution(min_random, max_random); // uniform distribution

        while (!clientData.shouldExit) {
                int randomNumber = distribution(gen);
                json jsonData = {
                        {"randomNumber", randomNumber}
                };
                conn.send_text(jsonData.dump());
                std::this_thread::sleep_for(thread_sleep_periode);
        }
}

int main() {
        crow::SimpleApp app; // define your crow application

        // Route-Definition
        CROW_WEBSOCKET_ROUTE(app, "/")
                .onopen([&](crow::websocket::connection& conn) {
                        // Debug
                        std::cout << "Websocket connection opened: " << std::endl;
                        // Create client data for this connection
                        ClientData& clientData = clientDataMap[&conn];
                        clientData.shouldExit = false;
                        // Start worker thread for this client
                        asio::post(our_thread_pool, [&conn, &clientData]() {
                                workerThread(conn, clientData);
                        });

                })
                .onclose([&](crow::websocket::connection& conn, const std::string& reason) {
                        // Debug
                        std::cout << "Websocket connection closed: " << reason << std::endl;
                        // Set exit-flag for this client
                        ClientData& clientData = clientDataMap[&conn];
                        clientData.shouldExit = true;
                        // Erase client data from the map
                        clientDataMap.erase(&conn);
                });

        // Start the Websocket_Server on port 8080
        app.bindaddr("127.0.0.1")
                .port(8080)
                .multithreaded()
                .run();
}

DonNagual avatar May 06 '24 13:05 DonNagual

And adding this snippet to the last example ensures that active connections won't prevent the server from shutting down:

int main() {

    // as before...

    // Shut down still active connections
    for (auto &clientData : clientDataMap) {
        clientData.second.shouldExit = true;
    }
}

dehre avatar Sep 20 '24 08:09 dehre