Websocket random generator - while loop
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();
}
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
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.
the thread ends because you set
shouldExit = true; //set exit-flag
in on_close of each socket. See the condition of the while loop.
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();
}
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;
}
}