当前位置: 首页 > java >正文

WebRTC(十三):信令服务器

作用

WebRTC 本身只处理媒体流的 P2P 传输、编解码与传输优化但不包含信令协议。WebRTC 的 PeerConnection 建立流程,需要两端完成连接协商和网络打洞信息的交换。这些内容包括:

功能模块说明
SDP 协商中转 offer/answer 信息(媒体能力)
ICE 候选交换中转 NAT 穿透相关的候选地址
用户身份验证确保用户合法(如 token 登录)
房间管理支持多人房间、用户列表维护
心跳检测检测用户连接状态
广播通知通知用户上线、下线、离开房间
拓展支持可扩展为 SFU 适配、统计分析等

工作流程

                ┌──────────────┐│   Peer A     │└──────┬───────┘│ Login▼┌──────────────┐│ Signaling    ││   Server     │└──────┬───────┘│ Notify online▼┌──────────────┐│   Peer B     │└──────────────┘

当 A 和 B 都上线后,建立连接时:

Peer A                       Signaling Server                    Peer B|                                 |                              ||────── Login (userA) ────────►   |                              ||                                 |                              ||◄──── Login ack  ─────────────── |                              ||                                 |                              ||───── Signal: Offer ───────────► | ───────► Offer ───────────► ||                                 |                              ||◄──── Signal: Answer ◄────────── | ◄────── Answer ◄─────────── ||                                 |                              ||───── ICE Candidate ───────────► | ───────► Candidate ───────► ||◄──── ICE Candidate ◄────────── | ◄────── Candidate ◄──────── |

信令消息

类型说明
login用户登录
join加入房间
leave离开房间
signal转发 SDP / ICE 消息
ping心跳保活
room-users查询当前房间用户列表
user-joined广播:新用户加入房间
user-left广播:用户离开房间或掉线

核心职责

步骤描述
用户登录记录客户端 ID,与连接对象关联(如 WebSocket)
信令转发将一个客户端发来的信令(SDP / ICE)转发给目标客户端
用户管理管理在线用户、断线清理、广播状态等
会话控制(可选)支持 room、会议、group call、用户状态通知等

信令服务器部署要求

要求

要点说明
公网可访问信令服务器必须有一个公网 IP 或域名
使用 TLS/WSS推荐使用 wss://(加密 WebSocket),提升浏览器兼容性和安全性
防火墙设置打开 WebSocket 监听端口(默认如 443, 8443, 9001
使用 CDN/反代(可选)Nginx、Caddy 等反向代理支持 WSS 路由
跨网测试客户端部署在不同网络(如:4G/家宽/云主机)进行真实互通测试

部署结构图

       +--------------------------+|     信令服务器 (WSS)     ||   wss://signal.example.com  |+--------------------------+▲           ▲│           │WebRTC A     WebRTC B(家宽/4G)     (云主机/4G)

A 和 B 都通过 WebSocket 连接到信令服务器。服务器转发 offer/answer/ICE 信息后,A 和 B 就可以尝试建立直连 P2P 链接。

部署流程

  1. 在云主机或公网服务器部署信令服务器:
./webrtc-signal-server --port 9001
  1. 配置 Nginx 反向代理 + TLS(WSS):
server {listen 443 ssl;server_name signal.example.com;ssl_certificate     /etc/ssl/cert.pem;ssl_certificate_key /etc/ssl/key.pem;location / {proxy_pass http://localhost:9001;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "Upgrade";}
}
  1. 客户端连接信令服务器:
const socket = new WebSocket("wss://signal.example.com");

WebRTC整体部署流程图

                           ┌────────────────────┐│   信令服务器       ││  (wss://signal)    │└───────┬────────────┘│┌──────────────────┼──────────────────┐│                                      │┌────────▼─────────┐                ┌──────────▼──────────┐│   Client A        │                │     Client B        ││  (WebRTC App)     │                │    (WebRTC App)     │└────────┬──────────┘                └──────────┬──────────┘│                                       │┌───────▼────────┐                    ┌────────▼───────┐│   STUN/TURN    │◀──────────────────▶│   STUN/TURN    │└────────────────┘                    └────────────────┘

示例

// WebRTC 信令服务器(支持房间机制 + 用户状态广播 + 心跳 + 可扩展协议 + WSS + 查询 + 限制房间人数)
// 编译依赖: uWebSockets (v20+) + OpenSSL + pthread + nlohmann::json#include <uwebsockets/App.h>
#include <unordered_map>
#include <unordered_set>
#include <nlohmann/json.hpp>
#include <iostream>
#include <chrono>
#include <thread>
#include <optional>using json = nlohmann::json;
using namespace std::chrono;constexpr int MAX_USERS_PER_ROOM = 5;struct UserData {std::string userId;std::string roomId;std::string protocol;time_point<steady_clock> lastPing;
};using WS = uWS::WebSocket<false, true, UserData>;std::unordered_map<std::string, WS*> userMap;                // userId -> ws
std::unordered_map<std::string, std::unordered_set<std::string>> roomMap; // roomId -> userId setvoid broadcastToRoom(const std::string& roomId, const std::string& senderId, const std::string& message) {if (!roomMap.count(roomId)) return;for (const auto& userId : roomMap[roomId]) {if (userMap.count(userId)) {userMap[userId]->send(message, uWS::OpCode::TEXT);}}
}void removeUser(WS* ws) {auto userId = ws->getUserData()->userId;auto roomId = ws->getUserData()->roomId;if (!userId.empty()) {userMap.erase(userId);if (!roomId.empty()) {roomMap[roomId].erase(userId);json offline = {{"type", "user-left"},{"userId", userId},{"roomId", roomId}};broadcastToRoom(roomId, userId, offline.dump());}std::cout << "[Disconnected] " << userId << "\n";}
}int main() {std::thread([] {while (true) {std::this_thread::sleep_for(seconds(30));auto now = steady_clock::now();for (auto it = userMap.begin(); it != userMap.end();) {auto ws = it->second;if (duration_cast<seconds>(now - ws->getUserData()->lastPing).count() > 60) {std::cout << "[Timeout] " << it->first << "\n";removeUser(ws);it = userMap.erase(it);} else {++it;}}}}).detach();uWS::SSLApp({.key_file_name = "./certs/key.pem",.cert_file_name = "./certs/cert.pem"}).ws<UserData>("/*", {.open = [](WS* ws) {ws->getUserData()->lastPing = steady_clock::now();},.message = [](WS* ws, std::string_view msg, uWS::OpCode) {try {json j = json::parse(msg);std::string type = j["type"];auto& userData = *ws->getUserData();if (type == "ping") {userData.lastPing = steady_clock::now();} else if (type == "login") {std::string userId = j["userId"];userData.userId = userId;userMap[userId] = ws;if (j.contains("protocol")) {userData.protocol = j["protocol"];}json ack = {{"type", "login"},{"success", true},{"protocol", userData.protocol}};ws->send(ack.dump(), uWS::OpCode::TEXT);} else if (type == "join") {std::string roomId = j["roomId"];if (roomMap[roomId].size() >= MAX_USERS_PER_ROOM) {json err = {{"type", "join"},{"success", false},{"error", "room-full"}};ws->send(err.dump(), uWS::OpCode::TEXT);return;}userData.roomId = roomId;roomMap[roomId].insert(userData.userId);json joined = {{"type", "user-joined"},{"userId", userData.userId},{"roomId", roomId},{"protocol", userData.protocol}};broadcastToRoom(roomId, "", joined.dump());} else if (type == "leave") {std::string roomId = userData.roomId;roomMap[roomId].erase(userData.userId);userData.roomId.clear();json left = {{"type", "user-left"},{"userId", userData.userId},{"roomId", roomId}};broadcastToRoom(roomId, userData.userId, left.dump());} else if (type == "signal") {std::string roomId = userData.roomId;broadcastToRoom(roomId, userData.userId, msg);} else if (type == "room-users") {std::string roomId = j["roomId"];json resp = {{"type", "room-users"},{"roomId", roomId},{"users", json::array()}};if (roomMap.count(roomId)) {for (const auto& uid : roomMap[roomId]) {resp["users"].push_back(uid);}}ws->send(resp.dump(), uWS::OpCode::TEXT);}} catch (...) {ws->send("{\"type\":\"error\",\"msg\":\"invalid json\"}", uWS::OpCode::TEXT);}},.close = [](WS* ws, int, std::string_view) {removeUser(ws);}}).listen(9003, [](auto* token) {if (token) std::cout << "[✔] WSS signaling server running at wss://localhost:9003\n";else std::cerr << "[✘] Failed to start WSS server\n";}).run();
}
http://www.xdnf.cn/news/14700.html

相关文章:

  • python动漫周边电商网站系统
  • 视频序列中的帧间匹配技术 FrameMatcher 详解
  • 领域驱动设计(DDD)【23】之泛化:从概念到实践
  • SQL 子查询全位置解析:可编写子查询的 7 大子句
  • Web基础关键_004_CSS(二)
  • 2023国赛linux的应急响应-wp
  • JSON简介及其应用
  • 【LLIE专题】EnlightenGAN 无监督低照度图像增强
  • 实现一个AI大模型当前都无法正确实现的基础二叉树读取算法
  • 商业秘密中经营信息的法律保护探析——以客户名册为例
  • 数字孪生技术引领UI前端设计新革命:实时交互与模拟预测
  • 【Bluedroid】蓝牙启动之BTM_reset_complete源码解析
  • yolov13+bytetrack的目标跟踪实现
  • pytorch中的几个概念
  • 港澳地区,海外服务器ping通可能是地区运营商问题
  • c# sugersql 获取子表数据排序
  • MySQL彻底卸载教程
  • 桌面小屏幕实战课程:DesktopScreen 16 HTTP
  • Java锁机制知识点
  • 《Go语言高级编程》RPC 入门
  • python -日期与天数的转换
  • 量化面试绿皮书:56. 多项式求和
  • web3 docs
  • Linux进程关系
  • Flutter 网络请求指南, 从 iOS 到 Flutter 的 Dio + Retrofit 组合
  • 飞算科技依托 JavaAI 核心技术,打造企业级智能开发全场景方案
  • 数据应该如何组织,才能让Excel“读懂”?
  • Django ORM 1. 创建模型(Model)
  • 【2024 CVPR-Backbone】RepViT: Revisiting Mobile CNN From ViT Perspective
  • 什么是上证50etf期权波动率?