webrtc peerconnection_server 模块介绍

peerconnection_server是webrtc一个简单的信令服务器示例,它位于 src/examples/peerconnection/server/ 目录下。它的主要目的是配合 peerconnection_client(另一个示例客户端)使用,演示两个 WebRTC 对等端(Peer)如何通过一个中间服务器交换建立连接所需的元数据(SDP 和 ICE Candidates),从而完成 P2P 握手。

• 它仅用于学习和测试 WebRTC 的信令流程。

• 它不支持大规模并发、没有身份验证、没有加密(HTTPS/WSS)、功能非常有限。

• 在实际产品中,你需要使用专业的信令服务器(如基于 WebSocket 的 Node.js/Go/Python 服务,或使用 SIP 协议的服务)。

一、核心功能

peerconnection_server 的主要职责是消息中转。它不处理任何媒体数据(音频/视频),只处理文本信令。

1.1. 用户注册与发现:

• 客户端启动时连接到服务器并注册一个名字(例如 "Alice")。

• 服务器维护一个在线用户列表。

• 当新用户加入或旧用户离开时,服务器会通知所有其他在线用户更新他们的成员列表。

1.2. 消息路由:

• 当 Alice 想呼叫 Bob 时,她通过服务器发送一条消息给 Bob。

• 服务器根据 Bob 的名字或 ID,将消息转发给 Bob 的连接。

1.3. 支持的信令类型:

• SDP Offer/Answer: 用于协商媒体能力(编解码器、分辨率等)。

• ICE Candidates: 用于网络穿透,交换双方的网络地址信息。

二、实现架构

该服务器是一个单线程、非阻塞 I/O 的 HTTP 服务器。

2.1. 网络层:

• 使用原生 BSD Socket API(跨平台封装在 SocketBase, DataSocket, ListeningSocket 中)。

• 使用 select() (Linux/Mac) 或 WSAPoll (Windows)进行 I/O 多路复用,在一个线程中同时处理多个客户端连接。

2.2. 应用层协议:

• 基于 HTTP/1.1。

• 使用 长轮询(Long-Polling) 机制来实现服务器向客户端的"推送"。

• 客户端发送一个 /wait 请求。

• 服务器挂起该请求,直到有发给该客户端的消息到达,或者超时。

• 一旦有消息,服务器立即响应 HTTP 200 OK 并带上消息体。

• 客户端收到后立即发起下一个 /wait 请求。

2.3. 核心类:

• ChannelMember:

这个类代表一个已连接到信令服务器的特定用户/客户端。每个浏览器标签页或应用程序实例在服务器上都有一个对应的 ChannelMember 对象。

• PeerChannel :

这个类代表整个聊天室或信令房间。它管理所有当前连接的 ChannelMember。在示例代码中,通常只有一个全局的 PeerChannel 实例,所有用户都在同一个"房间"里。

cpp 复制代码
// Represents a single peer connected to the server.
class ChannelMember {
 public:
  explicit ChannelMember(DataSocket* socket);
  ~ChannelMember();

  bool connected() const { return connected_; }
  int id() const { return id_; }
  void set_disconnected() { connected_ = false; }
  bool is_wait_request(DataSocket* ds) const;
  const std::string& name() const { return name_; }

  bool TimedOut();

  std::string GetPeerIdHeader() const;

  bool NotifyOfOtherMember(const ChannelMember& other);

  // Returns a string in the form "name,id\n".
  std::string GetEntry() const;

  void ForwardRequestToPeer(DataSocket* ds, ChannelMember* peer);

  void OnClosing(DataSocket* ds);

  void QueueResponse(const std::string& status,
                     const std::string& content_type,
                     const std::string& extra_headers,
                     const std::string& data);

  void SetWaitingSocket(DataSocket* ds);

 protected:
  struct QueuedResponse {
    std::string status, content_type, extra_headers, data;
  };

  DataSocket* waiting_socket_;
  int id_;
  bool connected_;
  time_t timestamp_;
  std::string name_;
  std::queue<QueuedResponse> queue_;
  static int s_member_id_;
};

// Manages all currently connected peers.
class PeerChannel {
 public:
  typedef std::vector<ChannelMember*> Members;

  PeerChannel() {}

  ~PeerChannel() { DeleteAll(); }

  const Members& members() const { return members_; }

  // Returns true if the request should be treated as a new ChannelMember
  // request.  Otherwise the request is not peerconnection related.
  static bool IsPeerConnection(const DataSocket* ds);

  // Finds a connected peer that's associated with the |ds| socket.
  ChannelMember* Lookup(DataSocket* ds) const;

  // Checks if the request has a "peer_id" parameter and if so, looks up the
  // peer for which the request is targeted at.
  ChannelMember* IsTargetedRequest(const DataSocket* ds) const;

  // Adds a new ChannelMember instance to the list of connected peers and
  // associates it with the socket.
  bool AddMember(DataSocket* ds);

  // Closes all connections and sends a "shutting down" message to all
  // connected peers.
  void CloseAll();

  // Called when a socket was determined to be closing by the peer (or if the
  // connection went dead).
  void OnClosing(DataSocket* ds);

  void CheckForTimeout();

 protected:
  void DeleteAll();
  void BroadcastChangedState(const ChannelMember& member,
                             Members* delivery_failures);
  void HandleDeliveryFailures(Members* failures);

  // Builds a simple list of "name,id\n" entries for each member.
  std::string BuildResponseForNewMember(const ChannelMember& member,
                                        std::string* content_type);

 protected:
  Members members_;
};

• DataSocket:

这个类代表一个已建立的客户端连接。当 ListeningSocket 接受一个新连接后,会创建一个 DataSocket 实例来处理该连接上的 HTTP 请求和响应。

• ListeningSocket :

这个类代表服务器的监听端点。它负责等待新的传入连接.

三、通信流程示例

3.1 信令工作流程

  1. 启动: PeerConnectionServer 创建一个 ListeningSocket 并调用 Listen(8888)。

  2. 事件循环: 服务器进入主循环,使用 select() 监视 ListeningSocket 和所有活跃的 DataSocket。

  3. 新连接:

• 如果 ListeningSocket 可读,调用 Accept()。

• 获取新的 DataSocket*,将其添加到活跃连接列表中。

  1. 接收数据:

• 如果某个 DataSocket 可读,调用其 OnDataAvailable()。

• DataSocket 内部解析 HTTP。

• 如果 request_received() 变为 true,主循环检测到该 socket 就绪且请求完整。

  1. 业务处理:

• 服务器读取 DataSocket->request_path() 和 DataSocket->data()。

• 根据路径(如 /message)将数据转发给目标用户的 DataSocket。

• 调用 DataSocket->Send("200 OK", ...) 回复当前客户端。

  1. 关闭:

• 如果 OnDataAvailable 返回错误,或业务逻辑决定关闭,从列表中移除该 DataSocket,其析构函数会关闭底层 socket。

3.2 用户A发生SDP给用户B

  1. 用户 B 等待:

• 用户 B 的客户端发送 HTTP GET /wait。

• 服务器找到 B 的 ChannelMember,调用 SetWaitingSocket(socket_B)。

• Socket B 保持打开,不返回数据,进入挂起状态。

  1. 用户 A 发送消息:

• 用户 A 的客户端发送 HTTP POST /message?peer_id=B_ID,Body 包含 SDP Offer。

• 服务器主循环收到请求,通过 PeerChannel::Lookup(socket_A) 找到用户 A。

• 通过 PeerChannel::IsTargetedRequest 解析出目标 ID 是 B。

• 找到用户 B 的 ChannelMember 对象。

  1. 转发:

• 服务器调用 member_B->ForwardRequestToPeer(socket_A, member_B) (实际逻辑在 PeerChannel 中协调)。

• 在 ChannelMember::ForwardRequestToPeer 内部,它会将 SDP 数据包装。

• 检查 member_B->waiting_socket_。发现不为空(即步骤 1 中的 Socket B)。

• 立即通过 Socket B 发送 HTTP 200 OK + SDP 数据。

• 清空 member_B->waiting_socket_。

  1. 用户 B 接收:

• 用户 B 的客户端收到 HTTP 响应,解析出 SDP。

• 用户 B 的 WebRTC 引擎处理 SDP,生成 Answer。

• 用户 B 再次发起 /wait 或发送 Answer,循环继续。

3.3 视频会话流程

  1. 连接与注册:

• Alice 连接服务器: POST /sign_in?name=Alice

• Bob 连接服务器: POST /sign_in?name=Bob

• 服务器回复 Alice: 200 OK, body 包含当前在线用户列表(包含 Bob)。

• 服务器回复 Bob: 200 OK, body 包含当前在线用户列表(包含 Alice)。

• 此时,Alice 和 Bob 都知道对方在线了。

  1. 发起呼叫 (Alice -> Bob):

• Alice 的 WebRTC 引擎生成 SDP Offer。

• Alice 客户端发送: POST /message?peer_id=Bob_ID, Body 为 SDP Offer JSON。

• 服务器收到请求,查找 Bob 的连接。

• 如果 Bob 正处于 /wait 挂起状态,服务器立即通过该连接返回 SDP Offer。

• 如果 Bob 没在等待,服务器将消息存入 Bob 的消息队列,待 Bob 下次 /wait 时发送。

  1. 接收与回应 (Bob -> Alice):

• Bob 收到 SDP Offer,WebRTC 引擎生成 SDP Answer。

• Bob 客户端发送: POST /message?peer_id=Alice_ID, Body 为 SDP Answer。

• 服务器转发给 Alice。

  1. 交换 ICE Candidates:

• 双方不断收集本地网络候选者(Host, Srflx, Relay)。

• 每收集到一个,就通过 POST /message 发送给对方。

• 服务器持续中转这些 JSON 消息。

  1. P2P 建立:

• 一旦 SDP 和足够的 ICE Candidates 交换完毕,且连通性检查成功,Alice 和 Bob 之间建立起直接的 UDP 连接。

• 此后,所有的音频、视频和数据通道流量都直接在对等端之间传输,不再经过 peerconnection_server。

相关推荐
志栋智能1 小时前
超自动化安全的技术选型与架构设计指南
运维·安全·自动化
isyangli_blog1 小时前
SDN 基本应用实践 —— 使用命令行实现简易防火墙功能实验报告
服务器·php·apache
ai_coder_ai1 小时前
在自动化脚本中操作excel文件
运维·自动化·excel
EasyGBS2 小时前
延迟直降90%!国标GB28181视频平台EasyGBS支持WebRTC WHIP推流设备接入,让万物互联更简单
音视频·webrtc
TimberWill2 小时前
Docker使用总结
运维·docker·容器
闪电悠米2 小时前
黑马点评-Redis ZSet-实现关注 Feed 流
服务器·网络·数据库·redis·缓存·junit·lua
m0_547486662 小时前
《KVM Docker OpenStack实战—虚拟化与云计算配置 管理与运维》全套课件PPT
运维·docker·openstack
yyuuuzz2 小时前
2026游戏云服务器推荐的技术判断思路
运维·服务器·开发语言·网络·人工智能·游戏·php
江华森2 小时前
Linux 运维新手入门课
linux·运维·服务器