尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Webrtc本地服务器hub实现细节和信令交互
webrtc本地服务器hub的搭建bool start(uint16_t port) { if (port 0) { return false; } stop(); try { rtc::WebSocketServerConfiguration scfg; scfg.port port; scfg.bindAddress 0.0.0.0; auto server std::make_sharedrtc::WebSocketServer(std::move(scfg)); server-onClient([](std::shared_ptrrtc::WebSocket client) { if (client) { bind_client(std::move(client)); } }); { std::lock_guardstd::mutex lk(g_mtx); g_server std::move(server); } return true; } catch (const std::exception e) { stop(); return false; } }hub:start函数里面做了什么1. 参数检查port 0 直接返回失败调 stop()避免重复 listen2. 起一个 WebSocket 服务端 并 listenbindAddress 0.0.0.0 port 传入的8888端口 → new rtc::WebSocketServer → 存到 g_server此后本机及局域网可以连 ws://设备IP:port/......3. 有连接时的注册回调server-onClient([](client) { bind_client(client); });start 不会自己去 open也不转发消息只是挂好钩子函数。真正有客户端连上并握手成功后bind_client 才会执行bind_client给每一个刚连上 Hub 的客户端挂回调握手成功onOpen后再登记、收消息。static void bind_client(std::shared_ptrrtc::WebSocket ws) { ws-onOpen([ws]() { const std::string path_str ws-path().value_or(); const std::string id peer_id_from_path(path_str); { std::lock_guardstd::mutex lk(g_mtx); g_clients[id] ws; } ws-onMessage([id, ws](rtc::message_variant data) { if (!std::holds_alternativertc::string(data)) { return; } on_client_message(id, std::getrtc::string(data)); }); ws-onClosed([id, ws]() { unregister_client(id, ws); }); ws-onError([id](std::string err) { }); }); }从 URL 路径取出 id如 /server → server放进 g_clients[id]在 onMessage 里做 JSON 按 id 转发在指定端口8888开 WS 信令服务器并等连接登记客户端、改 id、转发都在后续 onClient / bind_client 里发生。Hub相当于本机信令中转站只帮设备和浏览器互相递 WebRTC 信令 JSON不传视频。具体干什么listen 一个 WebSocket 端口默认 8888登记谁连上来路径 → id如 /server本机、/alice浏览器id转发消息看 JSON 里的 id 寄给谁再把 id 改成发送方名字设备和浏览器都连上 Hub 之后才能交换 request / offer / answer / candidate。-------------------------------------------------------static void on_client_message(const std::string from_id, const std::string utf8) { json msg; try { msg json::parse(utf8); } catch (const std::exception e) { return; } if (!msg.contains(id) || !msg[id].is_string()) { return; } const std::string dest_id msg[id].getstd::string(); msg[id] from_id; const std::string out msg.dump(); std::shared_ptrrtc::WebSocket dest; { std::lock_guardstd::mutex lk(g_mtx); auto it g_clients.find(dest_id); if (it g_clients.end() || !it-second) { return; } dest it-second; } try { if (!dest-isOpen()) { return; } dest-send(out); } catch (const std::exception e) { } } g_clients结构如下图 g_clients │ │ key (string) value (shared_ptr) │ ───────────────── ───────────────────────────── ├─ alice ───────────► shared_ptr ──► rtc::WebSocket (浏览器A) ├─ bob ───────────► shared_ptr ──► rtc::WebSocket (浏览器B) └─ server ───────────► shared_ptr ──► rtc::WebSocket (设备ldc) map 本体 节点1 节点2 节点3 ┌──────────┐ ┌──────────┐ ┌──────────┐ │ key:alice│ │ key:bob │ │key:server│ │ value:●──┼──┐ │ value:●──┼──┐ │ value:●──┼──┐ └──────────┘ │ └──────────┘ │ └──────────┘ │ ▼ ▼ ▼ WebSocket WebSocket WebSocket (alice那条) (bob那条) (server那条) 代码同样是: auto it g_clients.find(nobody); 第1步: it → [0] bob ! nobody 第2步: it → [1] alice ! nobody 第3步: it → [2] server ! nobody 第4步: it → [end] map结构看完了没有 [0] bob [1] alice [2] server [end] ◄── it 停在这里 代码: if (it g_clients.end() ...) → 条件真return 后面的 dest ...、send 都不会执行改完后发给设备侧登记名为 server 的那条连接流程是浏览器发出{ id: server, type: request }Hub 改成{ id: alice, type: request }Hub 按原来的收件人 dest_id server执行g_clients[server]-send(...)g_clients[server] 就是设备 ldc 连上的那条ws://127.0.0.1:8888/server。消息到 ldc 的 onMessage → handle_signaling_message用 alice 建这一路 PeerConnection后面实现浏览器和设备webrtc的信令交互。//**********************************************************************////**********************************************************************//浏览器和设备webrtc的信令交互流程handle_signaling_message这是线程里真正处理业务1.对端 → 设备 request{ type: request, id: peer_id }[16:30:30.862] [webrtc_ldc] signaling RX?xml version1.0 encodingUTF-8?signalingidB2nPJBgiG8/idtyperequest/type/signaling浏览器请求预览 →设备端收到后创建 PeerConnection→ 加视频/音频/DataChannel 轨→ 发 offer ICE candidate2.设备 → 对端 candidate{type: candidate, id: peer_id, candidate: candidate:…, mid: … }candidate 消息相当于告诉对方可以用来传媒体的地址供ICE候选试连通用不传视频只交换地址信息。浏览器本端收集到地址后同样 ws.send两边都要把自己的 IP:端口告诉对方ICE 才能试通。只有一边发 candidate往往对不上或很难连通。[16:30:30.862] [webrtc_ldc] request from peer idB2nPJBgiG8 (enable_videotrue enable_datachanneltrue accept_inbound_dctrue)[16:30:30.898] [webrtc_ldc] signaling TX typecandidate idB2nPJBgiG8 (no sdp field)[16:30:30.900] [webrtc_ldc] PC state1 peerB2nPJBgiG8[16:30:30.959] [webrtc_ldc] signaling TX typecandidate idB2nPJBgiG8 (no sdp field)3.设备 → 对端 offer设备发出的媒体协同告诉浏览器我要怎么推流、用什么编码、ICE/DTLS 参数是什么。浏览器收到后才能setRemoteDescription(offer)据此生成 answer 回给设备双方再交换 candidate走 ICE连接没有 offer后面的answer建联都不会开始。[16:30:30.964] [webrtc_ldc] signaling TX typeoffer idB2nPJBgiG8 sdp_chars1136[16:30:30.964] [webrtc_ldc] sent local SDP typeoffer4.对端 → 设备answer收到浏览器 answer → setRemoteDescription → 完成协商[16:30:30.979] [webrtc_ldc] signaling RX?xml version1.0 encodingUTF-8?signalingidB2nPJBgiG8/idtypeanswer/typesdp![CDATA[v0o- 1432640720720693603 2 IN IP4 127.0.0.1s-t0 0agroup:BUNDLE cam-video 0amsid-semantic: WMSmvideo 9 UDP/TLS/RTP/SAVPF 102cIN IP4 0.0.0.0artcp:9 IN IP4 0.0.0.0acandidate:2719503686 1 udp 2113937151 67ae8833-aee7-43b7-87c1-8bab0d36e5ff.local 63708 typ host generation 0 network-cost 999aice-ufrag:kGflaice-pwd:Zg0oVICjNPQvWJfZKQSJ4iFHaice-options:trickleafingerprint:sha-256 3D:BF:01:6E:3B:CB:4F:26:CE:52:C1:77:7B:58:27:3B:6E:72:A1:7B:2E:72:C0:11:F9:83:28:BC:7B:03:59:81asetup:activeamid:cam-videoaextmap:4 urn:ietf:params:rtp-hdrext:sdes:midarecvonlyartcp-muxartpmap:102 H264/90000artcp-fb:102 goog-rembartcp-fb:102 nackartcp-fb:102 nack pliafmtp:102 level-asymmetry-allowed1;packetization-mode1;profile-level-id42e01fmapplication 9 UDP/DTLS/SCTP webrtc-datachannelcIN IP4 0.0.0.0aice-ufrag:kGflaice-pwd:Zg0oVICjNPQvWJfZKQSJ4iFHaice-options:trickleafingerprint:sha-256 3D:BF:01:6E:3B:CB:4F:26:CE:52:C1:77:7B:58:27:3B:6E:72:A1:7B:2E:72:C0:11:F9:83:28:BC:7B:03:59:81asetup:activeamid:0asctp-port:5000amax-message-size:262144]]/sdp/signaling[16:30:30.979] [webrtc_ldc] answer sdp first line: v0[16:30:30.980] [webrtc_ldc] RTP MID header ext_id4 midcam-video uriurn:ietf:params:rtp-hdrext:sdes:mid[16:30:30.982] [webrtc_ldc] setRemoteDescription(answer)这是浏览器对设备 offer 的应答同意怎么收流、本端 ICE/DTLS 参数是什么。设备收到后会 setRemoteDescription(answer)协商才算双边对齐。candidate收到远端 ICE candidate → addRemoteCandidate浏览器(alice) Hub 设备 ldc │ │ │ │── request ──────────►│ 改 id→alice ───────────►│ 建 PC / 出 offer │ │ │ │◄─ offer ─────────────│◄─ offer (idalice) ─────│ │ │ │ │───────answer ───────►│ 改 id→alice ───────────►│ setRemoteDescription │ │ │ │◄─ candidate ─────────│◄─ candidate ────────────│ (交叉多次) │── candidate ────────►│────────────────────────►│ │ │ │ │ ICE/DTLS不经 Hub→ 出画 │ │ │ │ │── bye ──────────────►│────────────────────────►│ remove peer信令交互日志[16:29:16.588] [webrtc_cam_track] stopped [16:29:16.588] [webrtc_cam_track] venc callback registered ch0 [16:29:16.589] [webrtc] libdatachannel session started [16:29:16.589] [service] webrtc started [16:29:16.589] camera init done. [16:29:16.657] [webrtc_ldc] opening ws://192.168.6.233:8000/server [16:29:16.683] [webrtc_ldc] signaling WebSocket open /tmp/tmp # /tmp/tmp # /tmp/tmp # I-Frame insertion must occur in less than 100 ms to be NDI|HX compliant [16:30:30.862] [webrtc_ldc] signaling RX ?xml version1.0 encodingUTF-8? signaling idB2nPJBgiG8/id typerequest/type /signaling [16:30:30.862] [webrtc_ldc] request from peer idB2nPJBgiG8 (enable_videotrue enable_datachanneltrue accept_inbound_dctrue) [16:30:30.898] [webrtc_ldc] signaling TX typecandidate idB2nPJBgiG8 (no sdp field) [16:30:30.900] [webrtc_ldc] PC state1 peerB2nPJBgiG8 [16:30:30.959] [webrtc_ldc] signaling TX typecandidate idB2nPJBgiG8 (no sdp field) 2026-05-14 16:30:30.961 WARN [2135] [rtc::impl::IceTransport::LogCallback390] juice: Send failed, errno101 [16:30:30.964] [webrtc_ldc] signaling TX typeoffer idB2nPJBgiG8 sdp_chars1136 [16:30:30.964] [webrtc_ldc] sent local SDP typeoffer [16:30:30.979] [webrtc_ldc] signaling RX ?xml version1.0 encodingUTF-8? signaling idB2nPJBgiG8/id typeanswer/type sdp![CDATA[v0 o- 1432640720720693603 2 IN IP4 127.0.0.1 s- t0 0 agroup:BUNDLE cam-video 0 amsid-semantic: WMS mvideo 9 UDP/TLS/RTP/SAVPF 102 cIN IP4 0.0.0.0 artcp:9 IN IP4 0.0.0.0 acandidate:2719503686 1 udp 2113937151 67ae8833-aee7-43b7-87c1-8bab0d36e5ff.local 63708 typ host generation 0 network-cost 999 aice-ufrag:kGfl aice-pwd:Zg0oVICjNPQvWJfZKQSJ4iFH aice-options:trickle afingerprint:sha-256 3D:BF:01:6E:3B:CB:4F:26:CE:52:C1:77:7B:58:27:3B:6E:72:A1:7B:2E:72:C0:11:F9:83:28:BC:7B:03:59:81 asetup:active amid:cam-video aextmap:4 urn:ietf:params:rtp-hdrext:sdes:mid arecvonly artcp-mux artpmap:102 H264/90000 artcp-fb:102 goog-remb artcp-fb:102 nack artcp-fb:102 nack pli afmtp:102 level-asymmetry-allowed1;packetization-mode1;profile-level-id42e01f mapplication 9 UDP/DTLS/SCTP webrtc-datachannel cIN IP4 0.0.0.0 aice-ufrag:kGfl aice-pwd:Zg0oVICjNPQvWJfZKQSJ4iFH aice-options:trickle afingerprint:sha-256 3D:BF:01:6E:3B:CB:4F:26:CE:52:C1:77:7B:58:27:3B:6E:72:A1:7B:2E:72:C0:11:F9:83:28:BC:7B:03:59:81 asetup:active amid:0 asctp-port:5000 amax-message-size:262144 ]]/sdp /signaling [16:30:30.979] [webrtc_ldc] answer sdp first line: v0 [16:30:30.980] [webrtc_ldc] RTP MID header ext_id4 midcam-video uriurn:ietf:params:rtp-hdrext:sdes:mid [16:30:30.982] [webrtc_ldc] setRemoteDescription(answer) [16:30:31.030] [webrtc_ldc] video track open (requested IDR for decoder) [16:30:31.032] [webrtc_ldc] PC state2 peerB2nPJBgiG8 [16:30:31.034] [webrtc_ldc] DataChannel open taglocal labelapp I-Frame insertion must occur in less than 100 ms to be NDI|HX compliant
RELATED

相关推荐

淘宝店小蜜有必要开吗?商家实测后的取舍

淘宝店小蜜有必要开吗?商家实测后的取舍

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/22 7:22:12
【研发类-框架和库Skills】django-pro 技能

【研发类-框架和库Skills】django-pro 技能

掌握Django 5.x,包括异步视图、DRF、Celery和Django Channels。构建具有适当架构、测试和部署的可扩展Web应用 技能概述 django-pro 技能是一个专门用于Django开发的专家级技能,专注于Django 5.x最佳实践、可扩展架构和现代Web应用开发。该技能帮助开发…

📅 2026/10/22 7:22:12
深耕智能汽车安全合规|MUNIK秒尼科亮相AutoSec2026第十届中国汽车网络安全周,斩获行业殊荣

深耕智能汽车安全合规|MUNIK秒尼科亮相AutoSec2026第十届中国汽车网络安全周,斩获行业殊荣

近日,AutoSec 2026第十届中国汽车网络安全周(谈思汽车)在上海顺利举办。本次大会汇聚整车企业、零部件供应商、芯片厂商以及第三方技术服务机构,围绕功能安全、网络安全、数据合规、AI智能安全、欧盟EUCC法规等热点议题展开深度研…

📅 2026/10/22 7:17:12
MORE NEWS

更多资讯

📰

基于WiFi的课堂考勤系统:从MAC地址识别到Python实战部署

简介:基于WiFi的课堂考勤系统是一套利用WiFi网络自动识别学生设备MAC地址来完成课堂考勤的实用方案,主要面向高校教师、教务管理人员以及教育信息化产品开发者。资源系统梳理了考勤实现原理:通过接入点采集设备物理地址,并与学生信…

📰

32岁程序员转行大模型:四个月实操路线图

32岁,写了十来年代码,想转行大模型。这个念头估计在不少程序员脑子里转了好几个月。打开收藏夹,几十篇“Transformer原理详解”“大模型微调教程”“转行大模型学习路线”全都在吃灰。收藏从未停止,行动从未开始。我就是从这一步走…

📰

从基础RAG到高级RAG:解决召回精度、上下文污染与幻觉的实战指南

先把结论放前面:如果你还在用“文档切块 向量检索 塞进上下文”这种三段式流水线,那你做的其实是基础 RAG。在演示 Demo 里它效果很好,但只要数据量上来、问题变复杂、答案要求可验证,这套方案会立刻撞上召回精度、上下文污染、…

📰

企业AI原生架构深度拆解:从编排到交互的落地关键

最近跟几个团队聊AI落地,听到最多的一句话是“模型很强,但集成进业务很难”。这句话恰好点中了企业AI原生架构的核心矛盾:模型的智能只是能力起点,要变成业务价值,中间还隔着一层编排、一层交互。很多团队恰恰栽在这两…

📰

RAG架构下Dify知识库优化实战:让AI助手回答更精准

做知识库类的AI应用也有一段时间了,最常听到的抱怨就是:我已经把资料传进Dify知识库了,AI助手回答还是不准。这里头有一个很大的误会——大家总把回答质量的好坏,全算在“大模型聪不聪明”头上。实际上在RAG架构下,模型…

📰

双系统伪距单点定位:数学模型到数据处理与精度调优

简介:从GNSS定位基础概念讲起,伪距单点定位是绝对定位中最基础的模式,通过最小二乘解算即可获得经纬高。双系统融合因增加卫星数而改善几何强度,但若时钟偏差、权重模型和误差修正处理不当,精度反而可能下降。文章详解…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬