头图

导读:信令作为实时音视频技术架构中的重要一环,是对建立实时音视频通信起到关键桥梁性的作用。本文将从信令的概念着手,分享在网易云信新一代音视频技术架构下,信令的基本交互流程设计以及信令网络库的模块设计和重连优化等。

什么是信令

我们都知道,WebRTC 是通过 RTCPeerConnection 来做到端与端之间的实时音视频通信的,那他们是怎么知道对方网络位置(网络数据)?支持何种编解码器(媒体元数据)?何时打开或关闭通信(会话控制)的呢?

这就需要建立一条通道来交换这些信息,而这协商的信息就是信令,这条通道也就是信令通道。我们这里所说的信令网络库的作用就是为端和信令服务器交换信令提供网络传输的通道。那信令的作用到底是什么?

在实时通信中,信令的主要作用体现在以下四个方面:

  • 媒体功能的协商和设置。
  • 标识和验证会话参与者的身份(交换 SDP 对象中的信息:媒体类型、编解码器、带宽等元数据)。
  • 控制媒体会话、指示进度、更改会话和终止会话。
  • 当会话双方同时尝试建立或更改会话时,实施双占用分解。

总之,信令就是协调音视频实时通信的过程,一旦信令服务建立好了,两个客户端之间建立了连接,理论上它们就可以进行点对点通讯了。而值得注意的是,WebRTC 标准本身没有规定信令交换的通讯方式,信令服务根据自身的情况实现。这也为我们自定义信令服务器提供了可能。

单 PeerConnection 方案

2020年,在疫情的冲击下,音视频通信市场得到了爆发式的增长,在视频会议、在线教育、线上金融、云游戏等各个领域都得到了长足的发展。同时,客户也对各种应用场景下的音视频的高性能、低延时等方面提出了更高的要求。

在2020年11月,网易云信发布了新一代音视频技术架构(简称 NERTC),详细内容可查看文末介绍,为了提升产品性能,我们从高可用、高并发、高性能、高扩展等方向进行了全流程的技术升级,包括新一代音视频融合通信服务端系统、新一代音视频 SDK 以及新一代音视频引擎,其中就包括单 PeerConnection 的重构方案。

单 PeerConnection 方案涉及了信令服务器的重构。其基本设计原则是主要是:

  • iOS/Android/windows/Mac/Web 多端协议一致,均采用 Websocket 交互,节省服务器资源,简化服务器代码逻辑。
  • 端侧只创建 2 个 Peerconnection,一个只负责发送(sendonly),一个只负责接收(recvonly)。
  • 采用自定义信令协议进行交互。
  • 简化协商流程,减少首屏时间。

单 PeerConnection 方案基本的信令交互流程如下:

WebSocket 通信简介

前文可知,为了达到多端(Web)信令协议交互一致性,所以采用了 WebSocket 作为信令传输。那 WebSocket 有什么特点?

说到 WebSocket,就离不开 HTTP。HTTP 一般限制每次连接只处理一个请求,服务器处理完客户的请求,并收到客户的应答后,即断开连接,不过 HTTP 也有 Keep-Alive 功能保持连接,使得单次连接内可以发送多次请求。

WebSocket 是基于 HTTP 协议的,而区别于 HTTP 的最大特点在于服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话;而 HTTP 只能从端侧发起请求。从下图可以清晰看出 HTTP 与 WebSocket 之前的区别。

2.jpg

握手(Handshake)

WebSocket 是建立在 TCP 长连接基础之上的,为了实现 WebSocket 的通信,借用 HTTP 协议完成了一次握手过程。具体的握手过程是这样的:

  1. 客户端发起握手请求
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
  1. 服务器返回应答
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

这样表示握手成功。握手成功之后,客户端和服务器就建立了双向的数据通道,可以互发消息(数据),不需要客户端每次都发起请求。

连接保持

常规做法大家都知道,那就是心跳机制,通过判断心跳是否有响应来判断链接是否可用。WebSocket 也不例外,其协议规定了 ping/pong 机制来保持连接。我们可以看下 ping/pong 在 RFC6455 具体是怎么规定的。

5.5.2. Ping
The Ping frame contains an opcode of 0x9.
A Ping frame MAY include "Application data".
Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in response, unless it already received a Close frame. It SHOULD respond with Pong frame as soon as is practical. Pong frames are discussed in Section 5.5.3.
An endpoint MAY send a Ping frame any time after the connection is established and before the connection is closed.
NOTE: A Ping frame may serve either as a keepalive or as a means to verify that the remote endpoint is still responsive.

5.5.3. Pong
The Pong frame contains an opcode of 0xA.
Section 5.5.2 details requirements that apply to both Ping and Pong frames.
A Pong frame sent in response to a Ping frame must have identical "Application data" as found in the message body of the Ping frame being replied to.
If an endpoint receives a Ping frame and has not yet sent Pong frame(s) in response to previous Ping frame(s), the endpoint MAY elect to send a Pong frame for only the most recently processed Ping frame.
A Pong frame MAY be sent unsolicited. This serves as a unidirectional heartbeat. A response to an unsolicited Pong frame is not expected.

由上可知,一端通过发送 ping 帧,对端收到 ping 帧后,响应一个 pong 帧,这样就完成了一次连接的检测,并且它是单向的。

关闭连接

当关闭 WebSocket 通信的时候,发起端需要发送一个关闭帧,对方收到关闭帧,如果不曾发送过关闭帧,则需要发送一个关闭帧作为响应。关闭帧可以携带应用数据,用来说明关闭的原因。最后等到底层 TCP 连接关闭后,整个 WebSocket 通信就完全关闭了。

在 NERTC 中的应用

网易云信在新一代音视频技术架构中,是如何使用这些技术来实现信令的传输通道呢?下面我们先从模块设计方面和重连方面的设计做一些介绍。

一个基本原则:应用层不需要关心消息丢失、乱序、重发等问题。众所周知,TCP 提供可靠的传输服务,通过 TCP 连接传送的数据无差错、不丢失、不重复且按序到达。TCP 通过校验、重传控制、序号标识、滑动窗口、确认应答来实现可靠传输,如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。这样,我们只需要解决的就是重连和重发问题。

网络库架构设计

下图是 NERTC 的网络库架构图设计:

3.jpg

我们简单介绍其中的几个模块:

  • API 部分定义的是网络库模块的对外接口,由应用层调用。
  • Transport 模块主要抽象了消息收发和连接管理的功能,ws/wss 以及未来的 quic 协议扩展等都基于其虚接口实现。
  • Message 模块管理 WebSocket 子协议的组包和解包。
  • SendBuffer 管理 Message 的缓存和重发。
  • Peer 作为核心模块,实现对 Transport、Message 等所有模块的控制。

接下去我们看下快连接快重试和重连设计优化。

快速连接和重连设计

下面我们按不同的场景来介绍下网络库是如何运作以及做了哪些工作,部分场景可以结合下图来理解。

  • 场景一:首次连接时的退避策略。当应用层(简称 SDK)首次尝试连接时,如果 WebSocket 连接成功,则返回 SDK,SDK 发起信令协商;如果连接失败,使用每隔 200/400/600ms 的快速重试多次连接的策略。如果最终都失败的话,则回调给应用层 onFail 事件。
  • 场景二:连接成功后的断开重连策略。 如果已经连接成功,因为网络原因或者服务器等原因导致连接断开,则触发此策略。触发方式分为两种,一种方式是,WebSocket 通知了 onClose 事件;另外一种是主动探测的方式,通过 ping/pong 机制来触发重连。后者通过发送 ping 帧三次,每次间隔 3s,超时1.5s,如果三次均无法得到 pong 帧,则认为网络或者服务器异常,触发此场景的重连策略。具体的策略是,每隔 2/4/6s 触发一次连接尝试,如果最终还是失败的话,回调给应用层 onDisconnected 事件。
  • 场景三:应用层收到 onDisconnected 事件,会切换新的 server 地址,重新通过网络库尝试连接,直到 server 用尽,退出当次通话。
  • 场景四:当网络切换或者恢复可用时,应用层会主动触发重连。
  • 场景五:服务器主动关闭连接时,网络库清空消息缓存,并且不再尝试重连。

重连场景如下图所示:

4.jpg

消息缓存和重发

其实上面的时序图已经包含了消息发送和重发的时机。消息分为两种:

  • Request 类型消息,需要等待服务器的 Response,需要缓存。
  • Notification 类型消息,不进入消息缓存,当然也有可能是服务器发送过来的通知。

我们这里只讲端侧的消息发送。消息的发送和重发都只在连接成功后触发。

每一条消息都会先进入发送缓存队列(即 SendBuffer),如果当前连接正常,则直接发送,如果连接断开则会暂停发送,等待连接或重连成功之后才会恢复发送。因为依赖于 TCP 的可靠传输,所以消息的重发均只发生在重连成功之后。当连接被服务器 kick off 或者应用层主动关闭时,peer 对象会清空所有缓存消息,不再做发送或重发的尝试。

弱网下的表现

在弱网下,我们的测试数据发现,最多能抗50%的丢包率。在实测场景中,高丢包率情况下,媒体流 的 QoS 激进策略下会发送大量的冗余包和重传包,从而导致带宽资源几近耗尽。这会导致什么呢?

WebSocket 连接可能会失败,消息可能无法收发。我们都知道 TCP 是通过滑动窗口和拥塞窗口来做的流控。当已发字节数等于对方的接收窗口字节数时,就会导致发送窗口满(TCP Window FULL)了,从而无法再发送数据。有兴趣的同学可以了解 TCP 相关的概念。这种情况下基本只能通过触发断开重连的机制,关闭老的连接,新建连接进行消息的重发。

未来规划

为了增强弱网情况下的抗性,我们会考虑 UDP 作为优化的方向,比如说 QUIC(Quick UDP Internet Connection)协议等,目前也基本已经接近产品化阶段,这里就不再赘述,敬请期待 QUIC 版本的表现。

总结

本文从信令的基本概念以及作用入手,简要地讲述了网易云信新一代音视频技术架构中信令网络库基本交互流程、模块化设计思路以及网络库基本运作方式。另外,也对连接重试和重连的设计和优化做了一些论述。同时,也非常欢迎跟我们交流更多关于信令网络库的实现。

作者简介

丁永锋,网易云信资深客户端开发工程师,一直致力于客户端跨平台开发,目前负责音视频客户端跨平台 SDK 开发,曾负责 Unity&Cocos2dx SDK 以及网易云信 IM SDK 的跨平台开发工作。

延伸阅读

更多技术干货,欢迎关注【网易智企技术+】微信公众号


网易数智
619 声望140 粉丝

欢迎关注网易云信 GitHub: