Skip to content

Commit 7c04028

Browse files
committed
feat: add examples/signaling-server-libdatachannel-ws
added simple signaling-server implemented using libdatachannel cpp-api
1 parent 541d646 commit 7c04028

File tree

10 files changed

+243
-0
lines changed

10 files changed

+243
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ if(NOT NO_EXAMPLES)
544544
if(NOT NO_WEBSOCKET)
545545
add_subdirectory(examples/client)
546546
add_subdirectory(examples/client-benchmark)
547+
add_subdirectory(examples/signaling-server-libdatachannel-ws)
547548
endif()
548549
if(NOT NO_MEDIA)
549550
add_subdirectory(examples/media-receiver)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
cmake_minimum_required(VERSION 3.15)
2+
project(signaling-server-libdatachannel-ws
3+
VERSION 0.1
4+
LANGUAGES CXX)
5+
6+
set(MEDIA_UWP_RESOURCES
7+
uwp/Logo.png
8+
uwp/package.appxManifest
9+
uwp/SmallLogo.png
10+
uwp/SmallLogo44x44.png
11+
uwp/SplashScreen.png
12+
uwp/StoreLogo.png
13+
uwp/Windows_TemporaryKey.pfx
14+
)
15+
16+
if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
17+
add_executable(signaling-server-libdatachannel-ws signaling-server.cpp ${MEDIA_UWP_RESOURCES})
18+
else()
19+
add_executable(signaling-server-libdatachannel-ws signaling-server.cpp)
20+
endif()
21+
22+
set_target_properties(signaling-server-libdatachannel-ws PROPERTIES
23+
CXX_STANDARD 17
24+
OUTPUT_NAME signaling-server)
25+
26+
set_target_properties(signaling-server-libdatachannel-ws PROPERTIES
27+
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.github.paullouisageneau.libdatachannel.examples.signaling-server-libdatachannel-ws)
28+
29+
find_package(Threads REQUIRED)
30+
target_link_libraries(signaling-server-libdatachannel-ws LibDataChannel::LibDataChannel Threads::Threads nlohmann_json::nlohmann_json)
31+
32+
if(MSVC)
33+
add_custom_command(TARGET signaling-server-libdatachannel-ws POST_BUILD
34+
COMMAND ${CMAKE_COMMAND} -E copy_if_different
35+
"$<TARGET_FILE_DIR:datachannel>/datachannel.dll"
36+
$<TARGET_FILE_DIR:signaling-server-libdatachannel-ws>
37+
)
38+
endif()
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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+
}
488 Bytes
Loading
167 Bytes
Loading
265 Bytes
Loading
909 Bytes
Loading
227 Bytes
Loading
Binary file not shown.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Package
3+
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
4+
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
5+
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
6+
xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4"
7+
xmlns:iot2="http://schemas.microsoft.com/appx/manifest/iot/windows10/2"
8+
IgnorableNamespaces="uap mp">
9+
10+
<Identity Name="274EF42C-A3FB-3D6C-B127-E39C508A4F0E" Publisher="CN=CMake" Version="1.0.0.0" />
11+
<mp:PhoneIdentity PhoneProductId="274EF42C-A3FB-3D6C-B127-E39C508A4F0E" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
12+
<Properties>
13+
<DisplayName>datachannel-client</DisplayName>
14+
<PublisherDisplayName>CMake</PublisherDisplayName>
15+
<Logo>StoreLogo.png</Logo>
16+
</Properties>
17+
<Dependencies>
18+
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
19+
</Dependencies>
20+
<Resources>
21+
<Resource Language="x-generate" />
22+
</Resources>
23+
<Applications>
24+
<Application
25+
Id="App"
26+
Executable="client.exe"
27+
EntryPoint="datachannel-client.App"
28+
desktop4:Subsystem="console"
29+
desktop4:SupportsMultipleInstances="true"
30+
iot2:Subsystem="console"
31+
iot2:SupportsMultipleInstances="true">
32+
<uap:VisualElements
33+
DisplayName="datachannel-client"
34+
Description="datachannel-client"
35+
BackgroundColor="#336699"
36+
Square150x150Logo="Logo.png"
37+
Square44x44Logo="SmallLogo44x44.png">
38+
<uap:SplashScreen Image="SplashScreen.png" />
39+
</uap:VisualElements>
40+
</Application>
41+
</Applications>
42+
</Package>

0 commit comments

Comments
 (0)