Falcon
Loading...
Searching...
No Matches
uws_server.cpp
1#include <App.h>
2#include <atomic>
3#include <cmath>
4#include <cstring>
5#include <iostream>
6#include <memory>
7#include <mutex>
8#include <sstream>
9#include <string>
10#include <string_view>
11#include <thread>
12#include <unordered_set>
13#include <vector>
14
15#include "g3log/g3log.hpp"
16#include "logging/g3loglevels.hpp"
17
18struct SinkSlotAccumulator {
19 std::string upstream_address;
20 std::mutex mutex;
21 std::vector<char> active_bytes;
22 std::vector<char> flush_bytes;
23
24 SinkSlotAccumulator() = default;
25
26 SinkSlotAccumulator(const SinkSlotAccumulator&) = delete;
27 SinkSlotAccumulator& operator=(const SinkSlotAccumulator&) = delete;
28
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);
34 }
35
36 SinkSlotAccumulator& operator=(SinkSlotAccumulator&& other) noexcept {
37 if (this != &other) {
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);
43 }
44 return *this;
45 }
46};
47
49 std::ostringstream stream;
50};
51
52using ClientSet = std::unordered_set<uWS::WebSocket<false, true, std::string>*>;
53
54class UWSServerController {
55 public:
56 UWSServerController() = default;
57
58 ~UWSServerController() { stop(); }
59
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]() {
62 uWS::App app;
63 uWS::App::WebSocketBehavior<std::string> behavior;
64
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;
70
71 behavior.open = [this, proc_name](auto* ws) {
72 auto new_set = std::make_shared<ClientSet>(*active_clients_.load());
73 new_set->insert(ws);
74 active_clients_.store(new_set);
75 LOG(INFO) << proc_name << ": Client connected. Active total: " << new_set->size();
76 };
77
78 behavior.close = [this, proc_name](auto* ws, int, std::string_view) {
79 auto new_set = std::make_shared<ClientSet>(*active_clients_.load());
80 new_set->erase(ws);
81 active_clients_.store(new_set);
82 LOG(INFO) << proc_name
83 << ": Client disconnected. Active total: " << new_set->size();
84 };
85
86 behavior.message = [](auto* _, std::string_view, uWS::OpCode) {};
87
88 app.ws<std::string>("/*", std::move(behavior));
89
90 app.listen("0.0.0.0", port, [this, proc_name, port](auto* listen_socket) {
91 std::lock_guard<std::mutex> lock(loop_mutex_);
92 if (listen_socket) {
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;
96 } else {
97 LOG(ERROR) << "Failed to bind WebSocket server port.";
98 }
99 });
100
101 if (running) {
102 app.run();
103 }
104 });
105 }
106
107 void stop() {
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);
111
112 if (server_loop_) {
113 server_loop_->defer([listen_sock = listen_socket_, clients_to_close]() {
114 if (clients_to_close) {
115 for (auto* ws : *clients_to_close) {
116 ws->close();
117 }
118 }
119 if (listen_sock) {
120 us_listen_socket_close(0, listen_sock);
121 }
122 });
123 listen_socket_ = nullptr;
124 server_loop_ = nullptr;
125 }
126
127 if (server_thread_.joinable()) {
128 server_thread_.join();
129 }
130 }
131
132 std::shared_ptr<const ClientSet> get_clients_snapshot() { return active_clients_.load(); }
133
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;
137 {
138 std::lock_guard<std::mutex> lock(loop_mutex_);
139 target_loop = server_loop_;
140 }
141
142 if (target_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());
146
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);
151 }
152 }
153 }
154 });
155 }
156 }
157
158 private:
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>()};
163
164 us_listen_socket_t* listen_socket_ = nullptr;
165 uWS::Loop* server_loop_ = nullptr;
166 std::mutex loop_mutex_;
167};
Definition uws_server.cpp:48