求C++实现WebRTC RTCDataChannel服务端的示例与开发指导
I get it—old samples can be frustrating when they don't compile or are out of date. Let's put together a simple, modern C++ WebRTC server that focuses solely on RTCDataChannel communication with a browser. We'll use WebRTC's core libraries, add a basic WebSocket signaling server (since WebRTC needs a way to exchange SDP/ICE), and skip all media-related code.
Prerequisites
First, you'll need the WebRTC C++ libraries. The easiest way is to use a pre-built package or compile the official WebRTC source:
- Pre-built: Grab pre-built
libwebrtcbinaries from trusted sources for your OS (e.g., Ubuntu third-party repos, or official build artifacts) - Compile from source: Follow Google's official guide to build WebRTC for your platform—stick to the native build, you don't need to enable extra media components for DataChannel.
You'll also need a lightweight WebSocket library for signaling—we'll use libwebsockets here, since it's easy to integrate.
Core Concepts to Understand
Our server handles two critical pieces:
- Signaling: Using WebSocket to exchange SDP offers/answers and ICE candidates between the browser and server.
- WebRTC PeerConnection: Manages the underlying connection, creates the RTCDataChannel, and handles data transmission.
Full Server Code
Here's a minimal, working example with comments explaining each part:
#include <iostream> #include <memory> #include <string> #include <vector> #include <cstring> // WebRTC core headers #include "api/data_channel_interface.h" #include "api/peer_connection_interface.h" #include "api/rtc_error.h" #include "api/scoped_refptr.h" #include "pc/peer_connection_factory.h" #include "rtc_base/logging.h" #include "rtc_base/thread.h" // WebSocket library headers (libwebsockets) #include <libwebsockets.h> using namespace webrtc; // Global state (simplified for this example) rtc::scoped_refptr<PeerConnectionFactoryInterface> pc_factory; rtc::scoped_refptr<PeerConnectionInterface> peer_connection; rtc::scoped_refptr<DataChannelInterface> data_channel; struct lws* active_ws = nullptr; // Observer for DataChannel events (messages, state changes) class DataChannelObserver : public DataChannelObserver { public: void OnStateChange() override { if (data_channel->state() == DataChannelInterface::kOpen) { std::cout << "✅ Data channel is open! Sending test message..." << std::endl; data_channel->Send(DataBuffer(rtc::CopyOnWriteBuffer("Hello from C++ server!"))); } else if (data_channel->state() == DataChannelInterface::kClosed) { std::cout << "❌ Data channel closed." << std::endl; } } void OnMessage(const DataBuffer& buffer) override { std::string message(reinterpret_cast<const char*>(buffer.data.data()), buffer.data.size()); std::cout << "📥 Received from browser: " << message << std::endl; // Echo the message back to the browser data_channel->Send(buffer); } void OnBufferedAmountChange(uint64_t previous_amount) override {} }; // Observer for PeerConnection events (ICE, SDP, DataChannel setup) class PeerConnectionObserver : public PeerConnectionObserver { public: void OnSignalingChange(PeerConnectionInterface::SignalingState new_state) override {} void OnIceConnectionChange(PeerConnectionInterface::IceConnectionState new_state) override { std::cout << "🔗 ICE state: " << PeerConnectionInterface::IceConnectionStateToString(new_state) << std::endl; } void OnIceGatheringChange(PeerConnectionInterface::IceGatheringState new_state) override { if (new_state == PeerConnectionInterface::kIceGatheringComplete) { // Send local SDP answer to browser via WebSocket std::string sdp; peer_connection->local_description()->ToString(&sdp); std::string msg = "{\"type\":\"answer\",\"sdp\":\"" + sdp + "\"}"; lws_write(active_ws, reinterpret_cast<const unsigned char*>(msg.c_str()), msg.size(), LWS_WRITE_TEXT); } } void OnIceCandidate(const IceCandidateInterface* candidate) override { // Send ICE candidate to browser std::string candidate_str; candidate->ToString(&candidate_str); std::string msg = "{\"type\":\"candidate\",\"candidate\":\"" + candidate_str + "\"}"; lws_write(active_ws, reinterpret_cast<const unsigned char*>(msg.c_str()), msg.size(), LWS_WRITE_TEXT); } void OnAddStream(MediaStreamInterface* stream) override {} void OnRemoveStream(MediaStreamInterface* stream) override {} void OnDataChannel(rtc::scoped_refptr<DataChannelInterface> dc) override { data_channel = dc; data_channel->RegisterObserver(new DataChannelObserver()); std::cout << "📩 Received DataChannel from browser: " << data_channel->label() << std::endl; } }; // WebSocket callback to handle incoming signaling messages static int websocket_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { switch (reason) { case LWS_CALLBACK_ESTABLISHED: std::cout << "🔌 WebSocket connection established with browser." << std::endl; active_ws = wsi; break; case LWS_CALLBACK_RECEIVE: { std::string message(reinterpret_cast<const char*>(in), len); std::cout << "📨 Signaling message received: " << message << std::endl; // Simplified parsing (use a proper JSON library like nlohmann/json in production) if (message.find("\"type\":\"offer\"") != std::string::npos) { // Extract SDP offer from message size_t sdp_start = message.find("\"sdp\":\"") + 6; size_t sdp_end = message.find("\"", sdp_start); std::string sdp_offer = message.substr(sdp_start, sdp_end - sdp_start); // Create PeerConnection auto pc_observer = new PeerConnectionObserver(); PeerConnectionInterface::RTCConfiguration config; config.sdp_semantics = SdpSemantics::kUnifiedPlan; peer_connection = pc_factory->CreatePeerConnection(config, nullptr, nullptr, pc_observer); // Set remote SDP offer SdpParseError error; auto remote_desc = CreateSessionDescription(SdpType::kOffer, sdp_offer, &error); if (!remote_desc) { std::cerr << "❌ Failed to parse SDP offer: " << error.description << std::endl; break; } peer_connection->SetRemoteDescription(remote_desc, [](const RTCError& error) { if (!error.ok()) std::cerr << "❌ Failed to set remote description: " << error.message() << std::endl; }); // Generate and send SDP answer peer_connection->CreateAnswer([](const SessionDescriptionInterface* desc, const RTCError& error) { if (!error.ok()) { std::cerr << "❌ Failed to create answer: " << error.message() << std::endl; return; } peer_connection->SetLocalDescription(desc, [](const RTCError& error) { if (!error.ok()) std::cerr << "❌ Failed to set local description: " << error.message() << std::endl; }); }, PeerConnectionInterface::RTCOfferAnswerOptions()); } else if (message.find("\"type\":\"candidate\"") != std::string::npos) { // Extract ICE candidate size_t cand_start = message.find("\"candidate\":\"") + 12; size_t cand_end = message.find("\"", cand_start); std::string cand_str = message.substr(cand_start, cand_end - cand_start); auto candidate = CreateIceCandidate("", 0, cand_str, nullptr); if (candidate) { peer_connection->AddIceCandidate(candidate); delete candidate; } } break; } default: break; } return 0; } int main() { // Initialize WebRTC threads and SSL rtc::ThreadManager::Instance()->WrapCurrentThread(); rtc::InitializeSSL(); // Create PeerConnectionFactory pc_factory = CreatePeerConnectionFactory( nullptr, nullptr, nullptr, nullptr, webrtc::PeerConnectionFactoryDependencies()); if (!pc_factory) { std::cerr << "❌ Failed to create PeerConnectionFactory!" << std::endl; return 1; } // Set up WebSocket server struct lws_context_creation_info ws_info; memset(&ws_info, 0, sizeof(ws_info)); ws_info.port = 8080; ws_info.protocols = new lws_protocols[2]{ { "webrtc-signaling", websocket_callback, 0, 1024, }, { nullptr, nullptr, 0, 0 } // End of protocol list }; ws_info.gid = -1; ws_info.uid = -1; auto ws_context = lws_create_context(&ws_info); if (!ws_context) { std::cerr << "❌ Failed to create WebSocket context!" << std::endl; return 1; } std::cout << "🚀 Server running on ws://localhost:8080" << std::endl; // Main event loop while (true) { lws_service(ws_context, 50); rtc::ThreadManager::Instance()->CurrentThread()->ProcessMessages(0); } // Cleanup (unreachable in this example, but good practice) lws_context_destroy(ws_context); rtc::CleanupSSL(); return 0; }
Browser Test Code
Create an HTML file to test the connection:
<!DOCTYPE html> <html> <head> <title>WebRTC DataChannel Test</title> <style> #output { margin-top: 20px; padding: 10px; border: 1px solid #ccc; } p { margin: 5px 0; } </style> </head> <body> <input type="text" id="messageInput" placeholder="Type a message..." style="padding: 8px;"> <button onclick="sendMessage()" style="padding: 8px 16px;">Send</button> <div id="output"></div> <script> let ws; let pc; let dataChannel; function init() { // Connect to WebSocket signaling server ws = new WebSocket('ws://localhost:8080'); ws.onmessage = handleSignaling; // Initialize PeerConnection pc = new RTCPeerConnection({ iceServers: [] }); pc.onicecandidate = e => { if (e.candidate) ws.send(JSON.stringify({ type: 'candidate', candidate: e.candidate.toJSON() })); }; pc.ondatachannel = e => { dataChannel = e.channel; dataChannel.onmessage = e => addToOutput(`Server: ${e.data}`); dataChannel.onopen = () => addToOutput('✅ Data channel is open!'); }; // Create DataChannel dataChannel = pc.createDataChannel('test-channel'); dataChannel.onmessage = e => addToOutput(`Server: ${e.data}`); // Generate and send SDP offer pc.createOffer() .then(offer => pc.setLocalDescription(offer)) .then(() => ws.send(JSON.stringify({ type: 'offer', sdp: pc.localDescription.sdp }))); } function handleSignaling(event) { const msg = JSON.parse(event.data); if (msg.type === 'answer') { pc.setRemoteDescription(new RTCSessionDescription(msg)); } else if (msg.type === 'candidate') { pc.addIceCandidate(new RTCIceCandidate(msg.candidate)); } } function sendMessage() { const input = document.getElementById('messageInput'); if (dataChannel && dataChannel.readyState === 'open') { dataChannel.send(input.value); addToOutput(`You: ${input.value}`); input.value = ''; } } function addToOutput(text) { document.getElementById('output').innerHTML += `<p>${text}</p>`; } window.onload = init; </script> </body> </html>
Compilation & Running
- Compile the server:
Adjust the paths to match your WebRTC and libwebsockets installations, then run:g++ -std=c++17 server.cpp -o webrtc-server \ -I/path/to/webrtc/include -L/path/to/webrtc/lib \ -I/path/to/libwebsockets/include -L/path/to/libwebsockets/lib \ -lwebrtc -lwebsockets -lpthread -ldl -lssl -lcrypto - Start the server:
./webrtc-server - Test in browser:
Open the HTML file in Chrome/Firefox. You'll see the data channel open, receive a test message from the server, and can send messages back that will be echoed.
Key Notes for Production
- JSON Parsing: Replace the simple string parsing with a robust library like
nlohmann/jsonto avoid bugs with complex messages. - Error Handling: Expand error handling for edge cases (e.g., connection drops, invalid SDP).
- WebRTC Version: Use a recent WebRTC build (M90+) to ensure API compatibility.
- ICE Servers: For internet-wide connections, add STUN/TURN servers to the PeerConnection configuration.
内容的提问来源于stack exchange,提问作者avi dahan

