18struct SinkSlotAccumulator {
19 std::string upstream_address;
21 std::vector<char> active_bytes;
22 std::vector<char> flush_bytes;
24 SinkSlotAccumulator() =
default;
26 SinkSlotAccumulator(
const SinkSlotAccumulator&) =
delete;
27 SinkSlotAccumulator& operator=(
const SinkSlotAccumulator&) =
delete;
29 SinkSlotAccumulator(SinkSlotAccumulator&& other)
noexcept {
30 std::lock_guard<std::mutex> lock(other.mutex);
31 upstream_address = std::move(other.upstream_address);
32 active_bytes = std::move(other.active_bytes);
33 flush_bytes = std::move(other.flush_bytes);
36 SinkSlotAccumulator& operator=(SinkSlotAccumulator&& other)
noexcept {
38 std::lock_guard<std::mutex> lock1(mutex);
39 std::lock_guard<std::mutex> lock2(other.mutex);
40 upstream_address = std::move(other.upstream_address);
41 active_bytes = std::move(other.active_bytes);
42 flush_bytes = std::move(other.flush_bytes);
54class UWSServerController {
56 UWSServerController() =
default;
58 ~UWSServerController() { stop(); }
60 void start(
unsigned int port,
const std::string& proc_name, std::atomic<bool>& running) {
61 server_thread_ = std::thread([
this, port, proc_name, &running]() {
63 uWS::App::WebSocketBehavior<std::string> behavior;
65 behavior.compression = uWS::DISABLED;
66 behavior.maxPayloadLength = 16 * 1024 * 1024;
67 behavior.idleTimeout = 16;
68 behavior.maxBackpressure = 4 * 1024 * 1024;
69 behavior.closeOnBackpressureLimit =
true;
71 behavior.open = [
this, proc_name](
auto* ws) {
72 auto new_set = std::make_shared<ClientSet>(*active_clients_.load());
74 active_clients_.store(new_set);
75 LOG(INFO) << proc_name <<
": Client connected. Active total: " << new_set->size();
78 behavior.close = [
this, proc_name](
auto* ws, int, std::string_view) {
79 auto new_set = std::make_shared<ClientSet>(*active_clients_.load());
81 active_clients_.store(new_set);
82 LOG(INFO) << proc_name
83 <<
": Client disconnected. Active total: " << new_set->size();
86 behavior.message = [](
auto* _, std::string_view, uWS::OpCode) {};
88 app.ws<std::string>(
"/*", std::move(behavior));
90 app.listen(
"0.0.0.0", port, [
this, proc_name, port](
auto* listen_socket) {
91 std::lock_guard<std::mutex> lock(loop_mutex_);
93 listen_socket_ = listen_socket;
94 server_loop_ = uWS::Loop::get();
95 LOG(INFO) << proc_name <<
": Websocket active on ws://0.0.0.0:" << port;
97 LOG(ERROR) <<
"Failed to bind WebSocket server port.";
108 std::lock_guard<std::mutex> lock(loop_mutex_);
109 auto empty_set = std::make_shared<const ClientSet>();
110 auto clients_to_close = active_clients_.exchange(empty_set);
113 server_loop_->defer([listen_sock = listen_socket_, clients_to_close]() {
114 if (clients_to_close) {
115 for (
auto* ws : *clients_to_close) {
120 us_listen_socket_close(0, listen_sock);
123 listen_socket_ =
nullptr;
124 server_loop_ =
nullptr;
127 if (server_thread_.joinable()) {
128 server_thread_.join();
132 std::shared_ptr<const ClientSet> get_clients_snapshot() {
return active_clients_.load(); }
134 void broadcast(
const std::shared_ptr<const ClientSet>& clients_snapshot,
135 const std::shared_ptr<std::vector<char>>& shared_packet) {
136 uWS::Loop* target_loop =
nullptr;
138 std::lock_guard<std::mutex> lock(loop_mutex_);
139 target_loop = server_loop_;
143 target_loop->defer([
this, clients_snapshot, shared_packet]() {
144 auto current_active = active_clients_.load();
145 std::string_view payload(shared_packet->data(), shared_packet->size());
147 for (
auto* ws : *clients_snapshot) {
148 if (current_active->count(ws)) {
149 if (ws->getBufferedAmount() < 2 * 1024 * 1024) {
150 ws->send(payload, uWS::OpCode::BINARY);
159 std::thread server_thread_;
160 alignas(std::hardware_destructive_interference_size)
161 std::atomic<std::shared_ptr<const ClientSet>> active_clients_{
162 std::make_shared<const ClientSet>()};
164 us_listen_socket_t* listen_socket_ =
nullptr;
165 uWS::Loop* server_loop_ =
nullptr;
166 std::mutex loop_mutex_;