You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

求C++实现WebRTC RTCDataChannel服务端的示例与开发指导

Minimal C++ WebRTC Server with RTCDataChannel (No Media/P2P)

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 libwebrtc binaries 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:

  1. Signaling: Using WebSocket to exchange SDP offers/answers and ICE candidates between the browser and server.
  2. 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

  1. 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
    
  2. Start the server:
    ./webrtc-server
    
  3. 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/json to 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:58:19