1+ /* *
2+ * signaling server example for libdatachannel
3+ * Copyright (c) 2024 Tim Schneider
4+ *
5+ * This Source Code Form is subject to the terms of the Mozilla Public
6+ * License, v. 2.0. If a copy of the MPL was not distributed with this
7+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
8+ */
9+
10+ #include " rtc/rtc.hpp"
11+
12+ #include < csignal>
13+ #include < iostream>
14+ #include < memory>
15+ #include < string>
16+ #include < thread>
17+ #include < unordered_map>
18+
19+ #include < nlohmann/json.hpp>
20+ using nlohmann::json;
21+
22+
23+ using namespace std ::chrono_literals;
24+ using std::shared_ptr;
25+ using std::weak_ptr;
26+ template <class T > weak_ptr<T> make_weak_ptr (shared_ptr<T> ptr) { return ptr; }
27+
28+ std::string get_user (weak_ptr<rtc::WebSocket> wws) {
29+ const auto &ws = wws.lock ();
30+ const auto path_ = ws->path ().value ();
31+ const auto user = path_.substr (path_.rfind (' /' ) + 1 );
32+ return user;
33+ }
34+
35+ void signalHandler (int signum) {
36+ std::cout << " Interrupt signal (" << signum << " ) received.\n " ;
37+ // terminate program
38+ exit (signum);
39+ }
40+
41+ int main (int argc, char *argv[]) {
42+ rtc::WebSocketServerConfiguration config;
43+ config.port = 8000 ;
44+ config.enableTls = false ;
45+ config.certificatePemFile = std::nullopt ;
46+ config.keyPemFile = std::nullopt ;
47+ config.keyPemPass = std::nullopt ;
48+ config.bindAddress = std::nullopt ;
49+ config.connectionTimeout = std::nullopt ;
50+
51+ // Check command line arguments.
52+ for (int i = 1 ; i < argc; i++) {
53+ if (strcmp (argv[i], " --help" ) == 0 ) {
54+ const size_t len = strlen (argv[0 ]);
55+ char *path = (char *)malloc (len + 1 );
56+ strcpy (path, argv[0 ]);
57+ path[len] = 0 ;
58+
59+ char *app_name = NULL ;
60+ // app_name = last_path_segment(path, "\\/");
61+ fprintf (stderr,
62+ " Usage: %s [-p <port>] [-a <bind-address>] [--connection-timeout <timeout>] "
63+ " [--enable-tls] [--certificatePemFile <file>] [--keyPemFile <keyPemFile>] "
64+ " [--keyPemPass <pass>]\n "
65+ " Example:\n "
66+ " %s -p 8000 -a 127.0.0.1 \n " ,
67+ app_name, app_name);
68+ free (path);
69+ return EXIT_FAILURE;
70+ }
71+ if (strcmp (argv[i], " -p" ) == 0 ) {
72+ config.port = atoi (argv[++i]);
73+ continue ;
74+ }
75+ if (strcmp (argv[i], " -a" ) == 0 ) {
76+ config.bindAddress = argv[++i];
77+ continue ;
78+ }
79+ if (strcmp (argv[i], " --connection-timeout" ) == 0 ) {
80+ config.connectionTimeout = std::chrono::milliseconds (atoi (argv[++i]));
81+ continue ;
82+ }
83+ if (strcmp (argv[i], " --enable-tls" ) == 0 ) {
84+ config.enableTls = true ;
85+ continue ;
86+ }
87+ if (strcmp (argv[i], " --certificatePemFile" ) == 0 ) {
88+ config.certificatePemFile = argv[++i];
89+ continue ;
90+ }
91+ if (strcmp (argv[i], " --keyPemFile" ) == 0 ) {
92+ config.keyPemFile = argv[++i];
93+ continue ;
94+ }
95+ if (strcmp (argv[i], " --keyPemPass" ) == 0 ) {
96+ config.keyPemPass = argv[++i];
97+ continue ;
98+ }
99+ }
100+
101+ auto wss = std::make_shared<rtc::WebSocketServer>(config);
102+ std::unordered_map<std::string, std::shared_ptr<rtc::WebSocket>> clients_map;
103+
104+ wss->onClient ([&clients_map](std::shared_ptr<rtc::WebSocket> ws) {
105+ std::promise<void > wsPromise;
106+ auto wsFuture = wsPromise.get_future ();
107+ std::cout << " WebSocket client (remote-address: " << ws->remoteAddress ().value () << " )"
108+ << std::endl;
109+
110+ ws->onOpen ([&clients_map, &wsPromise, wws = make_weak_ptr (ws)]() {
111+ const auto user = get_user (wws);
112+ std::cout << " WebSocket connected (user: " << user << " )" << std::endl;
113+ clients_map.insert_or_assign (user, wws.lock ());
114+ wsPromise.set_value ();
115+ });
116+ ws->onError ([&clients_map, &wsPromise, wws = make_weak_ptr (ws)](std::string s) {
117+ wsPromise.set_exception (std::make_exception_ptr (std::runtime_error (s)));
118+ const auto user = get_user (wws);
119+ std::cout << " WebSocket error (user: " << user << " )" << std::endl;
120+ clients_map.erase (user);
121+ });
122+ ws->onClosed ([&clients_map, &wsPromise, wws = make_weak_ptr (ws)]() {
123+ const auto user = get_user (wws);
124+ std::cout << " WebSocket closed (user: " << user << " )" << std::endl;
125+ clients_map.erase (user);
126+ });
127+ ws->onMessage ([&clients_map, wws = make_weak_ptr (ws)](auto data) {
128+ // data holds either std::string or rtc::binary
129+ if (!std::holds_alternative<std::string>(data))
130+ return ;
131+
132+ json message = json::parse (std::get<std::string>(data));
133+
134+ auto it = message.find (" id" );
135+ if (it == message.end ())
136+ return ;
137+
138+ auto id = it->get <std::string>();
139+
140+ auto client_dst = clients_map.find (id);
141+ if (client_dst == clients_map.end ()) {
142+ std::cout << " not found" << std::endl;
143+ } else {
144+ const auto user = get_user (wws);
145+
146+ message[" id" ] = user;
147+ auto &[id_dst, ws_dst] = *client_dst;
148+ std::cout << user << " ->" << id << " : " << message.dump () << std::endl;
149+ ws_dst->send (message.dump ());
150+ }
151+ });
152+ std::cout << " Waiting for client to be connected..." << std::endl;
153+ wsFuture.get ();
154+ });
155+
156+ signal (SIGINT, signalHandler);
157+ while (true ) {
158+ std::this_thread::sleep_for (1s);
159+ }
160+
161+ return EXIT_SUCCESS;
162+ }
0 commit comments