Skip to content

Commit 3d12b14

Browse files
Added libdatachannel echo client and server
1 parent 57bc10e commit 3d12b14

File tree

6 files changed

+246
-0
lines changed

6 files changed

+246
-0
lines changed

.gitmodules

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[submodule "libdatachannel/deps/cpp-httplib"]
2+
path = libdatachannel/deps/cpp-httplib
3+
url = https://github.com/yhirose/cpp-httplib.git
4+
[submodule "libdatachannel/deps/libdatachannel"]
5+
path = libdatachannel/deps/libdatachannel
6+
url = https://github.com/paullouisageneau/libdatachannel

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The developers likely to interested are those involved in WebRTC projects.
1313
The libraries that currently have a Client and Server implementation are:
1414

1515
- [aiortc](https://github.com/aiortc/aiortc): WebRTC and ORTC implementation for Python using asyncio.
16+
- [libdatachannel](https://github.com/paullouisageneau/libdatachannel): C/C++ WebRTC Data Channels and Media Transport standalone library (bindings for [Rust](https://github.com/lerouxrgd/datachannel-rs), [Node.js](https://github.com/murat-dogan/node-datachannel), and [Unity](https://github.com/hanseuljun/datachannel-unity))
1617
- [Pion](https://github.com/pion/webrtc): Pure Go implementation of the WebRTC API.
1718
- [SIPSorcery](https://github.com/sipsorcery-org/sipsorcery) - A WebRTC, SIP and VoIP library for C# and .NET Core. Designed for real-time communications apps.
1819
- [werift-webrtc](https://github.com/shinyoshiaki/werift-webrtc): WebRTC Implementation for TypeScript (Node.js)

libdatachannel/CMakeLists.txt

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
cmake_minimum_required(VERSION 3.7)
2+
project(webrtc-libdatachannel
3+
VERSION 0.1.0
4+
LANGUAGES CXX)
5+
set(PROJECT_DESCRIPTION "WebRTC Data Channels Library")
6+
7+
# Dependencies
8+
9+
option(NO_WEBSOCKET "Disable WebSocket support for libdatachannel" ON)
10+
add_subdirectory(deps/libdatachannel EXCLUDE_FROM_ALL)
11+
add_subdirectory(deps/cpp-httplib EXCLUDE_FROM_ALL)
12+
13+
# Client
14+
15+
add_executable(webrtc-libdatachannel-client client.cpp)
16+
set_target_properties(webrtc-libdatachannel-client PROPERTIES
17+
VERSION ${PROJECT_VERSION}
18+
CXX_STANDARD 17
19+
OUTPUT_NAME client)
20+
21+
target_link_libraries(webrtc-libdatachannel-client datachannel httplib nlohmann_json)
22+
23+
# Server
24+
25+
add_executable(webrtc-libdatachannel-server server.cpp)
26+
set_target_properties(webrtc-libdatachannel-server PROPERTIES
27+
VERSION ${PROJECT_VERSION}
28+
CXX_STANDARD 17
29+
OUTPUT_NAME server)
30+
31+
target_link_libraries(webrtc-libdatachannel-server datachannel httplib nlohmann_json)
32+

libdatachannel/client.cpp

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* libdatachannel echo client
3+
* Copyright (c) 2021 Paul-Louis Ageneau
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 2
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include <httplib.h>
20+
#include <nlohmann/json.hpp>
21+
#include <rtc/rtc.hpp>
22+
23+
#include <chrono>
24+
#include <future>
25+
#include <iostream>
26+
#include <memory>
27+
28+
namespace http = httplib;
29+
using json = nlohmann::json;
30+
31+
using namespace std::chrono_literals;
32+
33+
int main(int argc, char **argv) try {
34+
const std::string server = argc > 1 ? argv[1] : "http://localhost:8080";
35+
const std::string message = "Hello world!";
36+
37+
rtc::InitLogger(rtc::LogLevel::Warning);
38+
39+
http::Client cl(server.c_str());
40+
rtc::PeerConnection pc{rtc::Configuration{}};
41+
42+
std::promise<void> promise;
43+
auto future = promise.get_future();
44+
45+
pc.onGatheringStateChange([&pc, &cl, &promise](rtc::PeerConnection::GatheringState state) {
46+
if (state == rtc::PeerConnection::GatheringState::Complete) {
47+
try {
48+
auto local = pc.localDescription().value();
49+
json msg;
50+
msg["sdp"] = std::string(local);
51+
msg["type"] = local.typeString();
52+
53+
auto res = cl.Post("/offer", msg.dump().c_str(), "application/json");
54+
if (!res)
55+
throw std::runtime_error("HTTP request failed");
56+
57+
if (res->status != 200)
58+
throw std::runtime_error("HTTP request failed with status " +
59+
std::to_string(res->status));
60+
61+
auto parsed = json::parse(res->body);
62+
rtc::Description remote(parsed["sdp"].get<std::string>(),
63+
parsed["type"].get<std::string>());
64+
pc.setRemoteDescription(std::move(remote));
65+
66+
} catch (...) {
67+
promise.set_exception(std::current_exception());
68+
}
69+
}
70+
});
71+
72+
pc.onStateChange([&pc, &promise](rtc::PeerConnection::State state) {
73+
if (state == rtc::PeerConnection::State::Disconnected) {
74+
pc.close();
75+
promise.set_exception(
76+
std::make_exception_ptr(std::runtime_error("Peer Connection failed")));
77+
}
78+
});
79+
80+
auto dc = pc.createDataChannel("echo");
81+
82+
dc->onOpen([dc, message]() { dc->send(message); });
83+
84+
dc->onMessage([message, &promise](auto msg) {
85+
if (std::holds_alternative<std::string>(msg)) {
86+
auto str = std::get<std::string>(msg);
87+
if (str == message)
88+
promise.set_value();
89+
else
90+
promise.set_exception(
91+
std::make_exception_ptr(std::runtime_error("Echo test failed")));
92+
}
93+
});
94+
95+
if (future.wait_for(10s) != std::future_status::ready) {
96+
if (dc->isOpen())
97+
throw std::runtime_error("Timeout waiting on echo message");
98+
else if (pc.state() == rtc::PeerConnection::State::Connected)
99+
throw std::runtime_error("Timeout waiting on Data Channel opening");
100+
else
101+
throw std::runtime_error("Timeout waiting on Peer Connection");
102+
}
103+
return 0;
104+
105+
} catch (const std::exception &e) {
106+
std::cerr << "Error: " << e.what() << std::endl;
107+
return -1;
108+
}

libdatachannel/compile_commands.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/compile_commands.json

libdatachannel/server.cpp

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* libdatachannel echo server
3+
* Copyright (c) 2021 Paul-Louis Ageneau
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU General Public License
7+
* as published by the Free Software Foundation; either version 2
8+
* of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program; If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include <httplib.h>
20+
#include <nlohmann/json.hpp>
21+
#include <rtc/rtc.hpp>
22+
23+
#include <chrono>
24+
#include <future>
25+
#include <iostream>
26+
#include <memory>
27+
28+
namespace http = httplib;
29+
using json = nlohmann::json;
30+
31+
using namespace std::chrono_literals;
32+
33+
int main(int argc, char **argv) try {
34+
const std::string host = "0.0.0.0";
35+
const int port = 8080;
36+
37+
rtc::InitLogger(rtc::LogLevel::Warning);
38+
39+
http::Server srv;
40+
srv.Post("/offer", [](const httplib::Request &req, httplib::Response &res) {
41+
auto parsed = json::parse(req.body);
42+
rtc::Description remote(parsed["sdp"].get<std::string>(), parsed["type"].get<std::string>());
43+
44+
auto pc = std::make_shared<rtc::PeerConnection>(rtc::Configuration{});
45+
46+
std::promise<std::string> promise;
47+
auto future = promise.get_future();
48+
49+
pc->onGatheringStateChange([pc, &promise](rtc::PeerConnection::GatheringState state) {
50+
if (state == rtc::PeerConnection::GatheringState::Complete) {
51+
try {
52+
auto local = pc->localDescription().value();
53+
json msg;
54+
msg["sdp"] = std::string(local);
55+
msg["type"] = local.typeString();
56+
promise.set_value(msg.dump());
57+
58+
} catch (...) {
59+
promise.set_exception(std::current_exception());
60+
}
61+
}
62+
});
63+
64+
pc->onStateChange([pc](rtc::PeerConnection::State state) {
65+
if (state == rtc::PeerConnection::State::Disconnected) {
66+
pc->close();
67+
}
68+
});
69+
70+
pc->onDataChannel([](std::shared_ptr<rtc::DataChannel> dc) {
71+
dc->onMessage([dc](auto msg) { dc->send(msg); });
72+
});
73+
74+
pc->onTrack([](std::shared_ptr<rtc::Track> tr) {
75+
tr->onMessage([tr](auto msg) { tr->send(msg); });
76+
});
77+
78+
pc->setRemoteDescription(std::move(remote));
79+
80+
auto body = future.get();
81+
res.set_content(body, "application/json");
82+
});
83+
84+
srv.set_exception_handler([](const http::Request &req, http::Response &res, std::exception &e) {
85+
res.status = 500;
86+
res.set_content("500 Internal Server Error", "text/plain");
87+
});
88+
89+
std::cout << "Listening on " << host << ":" << port << "..." << std::endl;
90+
if (!srv.listen(host.c_str(), port))
91+
throw std::runtime_error("Failed to listen on port " + std::to_string(port));
92+
93+
return 0;
94+
95+
} catch (const std::exception &e) {
96+
std::cerr << "Fatal error: " << e.what() << std::endl;
97+
return -1;
98+
}

0 commit comments

Comments
 (0)