WebP2P 让你的直播免流

你在下载的时候,有没有体验过 P2P 下载,能够让你的网速从 10KB 直接提升到 10MB? 你在企业内传输文件的时候,有没有体验过文件秒传? 你在看直播的时候,想不想用别人的流量看直播呢? ...

能做到上面这些场景的技术,叫做 P2P。P2P 技术中,最出名的叫做 WebRTC。WebRTC 是一个含金量非常高的技术。做好的话你可以养活一家公司,做不好,那就只能是一个 demo。

WebRTC 虽然能做很多事,但是并不是所有场景都适合。最大的使用场景是 两个终端在同一个 NAT 内,简单来说,都在一个 wifi 内。这个场景中,最显著的效果就是带宽无限并且高速,你走的就是内部的线路,根本不消耗运营商的流量。

P2P 技术在基于 WebRTC 标准下,可以做很多事情:

  • 录屏应用
  • APP Drop
  • 视频直播
  • ...

作为 Web 开发,WebRTC 又能够给前端赋能些什么呢?

在了解这些基本内容后,接下来,我们会从底层一步一步介绍一下 P2P 在 Web 直播的应用。

P2P 穿透

P2P 穿透也可以叫做 NAT 穿透,这是 P2P 最大的一个难点。为了解决 ipv4 不够用,推出了 NAT 技术。NAT (Network Address Translation)是用来将内网私有 ip,转化为公有 ip。简单点,就是让很多台电脑公用同一个 IP。但是 NAT 有个非常重要的点:

NAT 不允许外网主机主动访问内网主机。

这个不允许访问的机制也有很多种,根据这些特性,我们可以将 NAT 分为多种:

  • 完整锥型NAT(Full Cone NAT)
  • 受限锥型NAT(Restricted Cone NAT):
  • 端口受限型NAT(Port Restricted Cone NAT)
  • 对称型NAT(Symmetric NAT)

一般情况下,前面三种 NAT 是可以穿透的,但是,对称型NAT 无法穿透。具体内容,大家网上搜一搜 NAT 穿透,资源应该很多。在穿透时,我们不仅需要考虑 NAT 还需要考虑到集群机器的防火墙设定,如果防火墙限制了 UDP 打洞,那么我们还需要切换为 TCP 打洞(TCP 打洞一般会慢一点)。

总的来说,我们穿透时需要考虑的问题就有:

  • NAT 类型
  • 两端处在 NAT 的位置:都在一层 NAT 后还是多层 NAT 后...
  • 防火墙连接协议的设定

这些问题一旦组合起来,这个复杂度就是 N*N 的关系了。如果搭建 p2p 每次都需要从头解决这个内容,P2P 也不会像现在发展的这么好了。WebRTC 就是用来解决这一问题的标准模板,通过 STUN/TURN Server 来实现打洞穿透。

WebRTC 打洞流程

这里,我们按照一个比较常见的情况作为模板讲解一下。两端都位于 NAT 层背后,并且,NAT 是可以穿透的 Full Cone NAT 类型。具体穿透流程如下:

  • A 和 B 需要和 STUN 服务器建立连接,获得 A/B 的公网 ip:port 和私网 ip:port。
  • B 往 A 发送一个打洞包,此时,已经在 B 的 NAT 上留下到 A 的 打洞 session。但是,由于该包没有 A NAT 的 session 记录,会被 A 拒绝掉。
  • A 往 B 发送一个打洞包,该包会在 A 的 NAT 上增加 B 的 session。此时,由于 B 的 NAT 上存在 A 的 session,该包是可以直接被 B 的 NAT 通过的。
  • 打洞完成

在 WebRTC 完成这里流程的 API 是: RTCPeerConnection。通过自建的一个中间 Server,来交换指定的 SDP 和 candidate。

SDP 是当前 Point 的一些基本描述信息,当前 WebRTC 版本 ICE 的描述信息,以及,对已经连接的 ICE 内容的描述,比如 video/audio 信息。SDP 这一环节,其实就是告诉了哪两个 Point 会进行连接。本身和打洞并没有太大的关联。具体内容可以参考:SDP antonomy

candidate 则是打洞的关键信息,里面会包含当前 Point 的内外网 ip:port,以及防火墙设定规则 tcp/upd。这里有一点需要注意的是,一个 point 为了能够提高 NAT 打洞的成功率,会产生多个 candidate。这里主要取决于几个 candidate 里面几个基本参数:

  • sdpMid 用来指定该次 candidate 的传输的 mediaStream 内容。例如, videoaudio
  • protocol:指定连接的协议, tcpudp
  • type: 表示能够穿透 NAT 的类型
    • host: 能够直连,或者在同一个 NAT 内
    • srflx/prflx: 外网直连通道,STUN 已经帮忙打了一个洞。如果两端不在同一个 NAT 里面,会用到该内容.
    • relay:是 TURN 服务器的中转通道。针对的是,对称型的 NAT,数据交换只能走中间的 server。

不过, srflx/prflx 类型的 candidate 只会在你初始化 RTCPeerConnection时,传入 iceServers 的 STUN 服务器 URL 时,才会获得。

config = {
     "iceServers": [{
         "urls": ["stun:stun.l.google.com:19302"]
     }],
     "iceTransportPolicy": "all",
     "iceCandidatePoolSize": "0"
 }

在 webRTC 代码层面,我们并不需要额外针对 candidate 做逻辑处理。我们只需要将 candidate 传给另外一端,通过 addIceCandidate() 注册到 RTC 内部即可。

local.addIceCandidate(remoteCandidate)
.then(e=>{
    console.log("add success");
})

通过云服务器完成 candidate 和 sdp 信息的交换后,我们就已经做完了 RTC 连接的必要准备。剩下的就是在连接建立完成之后做的状态监听和其他扩展事情。

连接状态判断

这里面最大的一个问题在于,我们完成数据添加之后,怎么判断 P2P 是否连接上。WebRTC 提供了我们 7 个基本的事件监听:

attribute EventHandler  onnegotiationneeded;
attribute EventHandler  onicecandidate;
attribute EventHandler  onicecandidateerror;
attribute EventHandler  onsignalingstatechange;
attribute EventHandler  oniceconnectionstatechange;
attribute EventHandler  onicegatheringstatechange;
attribute EventHandler  onconnectionstatechange;

通过 onconnectionstatechange 就可以得到连接状态的变化。直接绑定该方法,即可获得相关内容:

peer.addEventListener('connectionstatechange',event=>{
    // get the state from event
},false);

其能够提供的事件回调信息,可以直接参考:connection states。里面,我们只需要判断状态是否是 connected,来决定该次连接是否成功。

如果不成功,我们可以直接从 onicecandidateerror 里面获得相关的错误信息,具体可以参考:ICE gather error。当然,在连接过程中,也可以直接从 Promise 中,获得连接失败的信息,这部分内容可以直接参考:RTC Error。

上面整个代码流程可以直接参考:webRTC trickle candidate

DataChannel 数据穿洞

打洞过程是 WebRTC 最基础的一步,如果连这一步都没成功,那么后面就需要做一些其它适配的兼容。比如,直接通过 CDN 拉去资源。WebRTC 打洞成功后,我们就可以利用这个打洞包,根据用户的种子资源数、上行带宽、下载进度来判断 P2P 传输的资源。

WebRTC 原生提供了 RTC Media API、RTCDataChannel、DTMF 这三个传输通道。Media 能够直接结合 getUserMedia API 传输当前摄像头和麦克获取的音视频。并且 Chrome 浏览器在底层内嵌了很多功能强大的编码器,这里可以直接参考:webRTC FRQ.

不过,经过测试 WebRTC 回声消除和双端同时对话的效果并不是特别好。这块,大家可以考虑一下,能不能直接在底层替换编码器或者购买其他服务。

Media 和 DTMF 通常都需要建立在 getUserMedia 的前提下,但是,IOS11 并不支持,它只支持 DataChannel 传输数据的 API。所以,这里,我们只会针对 DataChannel 来做一些讲解。如何通过 DataChannel 来传输你的自定义文件内容。

DataChannel 是 PeerConnection 的一个拓展 API,可以直接通过 createDataChannel 来创建一个 SCTP 通道。SCTP 是一种高效的帧传输协议,它和 TCP/UDP 是在同一层的,集中了两者之间的优势。我们只需要在发送端创建 Channel,接收端直接监听 ondatachannel 事件即可。

let senderChannel = localPeer.createDataChannel('dataChannel');
senderChannel.onopen = ()=>{
    if(senderChannel.readyState === 'open'){
        senderChannel.send(someBuffer);
    }

};

senderChannel.birnayType = 'arrayBuffer';

remotePeer.ondatachannel = function(event){
    // receive data from event.channel
    remotePeer = event.channel;
    remotePeer.binaryType = 'arraybuffer';
    remotePeer.onmessage = onReceiveMessageCallback;
}

RTCDataChannel 一共可以发送两种数据:String 和 Binary(也可以叫做 ArrayBuffer,ArrayBufferView or Blob) 具体直接参考:dataView。

Channel 具体的用法其实就和 WebSocket 一样,通过 send 来传递相关的信息,再监听 onmessage 事件获得数据的回调。

上面的流程大致的覆盖了打洞的云端流程,比如穿透 NAT 层,P2P 数据 Channel 建立。不过,中间还有很多细节并没有解决清楚:

  • 怎么找到最优的 Point
  • 需要传输的数据是哪些
  • 传输数据的进度控制

而这些问题就需要落地到具体的业务当中了。因为现在直播行业非常的火热,本人也在该行业里面摸爬滚打,了解到该行业一些基本的痛点。最大的就是非常吃带宽,为了解决这一问题,我们完全可以利用 P2P 来做直播带宽的节省。

但是,说起来很容易,怎么做这才是关键?

不过,对于 Web 开发来说,这里只介绍一下 的基本思路。在 Web 直播中比较流行的是通过 http-chunked 模式来实现直播。而 http-chunked 协议很难得到具体播放的进度,那我们就需要一个能够很容易获得播放进度的协议--切片协议。

对于切片协议而言,最为突出的就是 HLS,但是它的延时性过高,没办法满足直播低延时的问题。针对这个点,MPEG 提出了一个 DASH 协议,来作为直播内容协议的补充。直播 DASH 是基于 HTTP-URL 的动态直播协议,通过 URL 上面的时间戳就很容易做到对流的时间记录,整个端的 P2P 流程图为:

云端这一步,就主要在 SegmentProtocol 做的事情,右边的则是本地播放的 IS/MS 处理。而如果你能够在云上做好这一整套流程,比如:

  • 确定最佳上行 Peer
  • 维护一整套种子和资源的云端管理
  • Peer 纠错机制
  • WebRTC 和 DASH 的最佳切换

那么,你的 P2P 功能和编码能力应该比一般程序员高太多了。

入门 Tip

事情需要一点一点做,饭需要一口一口的吃,DASH 由于协议比较复杂,推荐大家先从 HTTP-Chunked 协议入手,自己先搭建一个直播的 DEMO 试试水,这里推荐一下 HTTPLIVE 库。

另外,如果读者对前端音视频很感兴趣,不甘心只做一个纯纯的 UI 工程师的话,可以直接关注我的公众号前端小吉米,输入 MSE 加入 前端音视频的交流小组

原文发布于微信公众号 - 前端小吉米(villainThr)

原文发表时间:2018-02-09

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java达人

分布式系统的事务处理

当我们在生产线上用一台服务器来提供数据服务的时候,我会遇到如下的两个问题: 1)一台服务器的性能不足以提供足够的能力服务于所有的网络请求。 2)我们总是害怕我们...

21610
来自专栏IMWeb前端团队

聊聊http/2

本文作者:IMWeb 九月 原文出处:IMWeb社区 未经同意,禁止转载 随着web的发展,http/1.x 已经很难满足现在的需求,Google 因...

2107
来自专栏魏艾斯博客www.vpsss.net

搬瓦工洛杉矶电信联通直连线路速度评测

2472
来自专栏逍遥剑客的游戏开发

一个困扰我一个多星期的Nebula3的BUG

1463
来自专栏北京马哥教育

小白用Python | 十分钟学会用任意中文文本生成词云

前述 本文需要的两个Python类库 jieba:中文分词分词工具 wordcloud:Python下的词云生成工具 写作本篇文章用时一个小时半,阅读需要十...

3757
来自专栏上善若水

### 0x01 C++ 资源大全

关于 C++ 框架、库和资源的一些汇总列表,内容包括:标准库、Web应用框架、人工智能、数据库、图片处理、机器学习、日志、代码分析等。

2453
来自专栏SDNLAB

【双语频道】6分钟了解ONOS

本视频来自ONOS首席架构师Thomas Vachuska的讲解,视频在短短6分钟左右的时间内深入浅出的对ONOS的架构进行了阐述和分析,并对其功能进行了演示...

3214
来自专栏腾讯大数据的专栏

全民拥抱Docker云--Lhotse系统经验分享

前言 “只要站在风口,猪也能飞起来”,这碗心灵鸡汤不知道激励了多少英雄豪杰踏上寻风口之路。而现如今,Docker这阵龙卷风呼啸来袭,更让众人生起迎风而上、直冲云...

2419
来自专栏JarvanMo的IT专栏

Fluwx:让在Flutter中使用微信SDK成为可能

之前有了解的小伙伴可能已经看过Fluwx的一篇文章,不过那个时候Fluwx还是不太成熟。现在Fluwx的主体功能已经过成了。如果你正在或想开发一个Flutter...

7812
来自专栏程序手艺人

移植WebRTC中的VAD

7845

扫码关注云+社区