详解WebRTC-网页实时通信技术

简介

全称是:

Web browser Real Time Communication 特点如下:

  • 是基于浏览器的实时音视频(数据)通信技术
  • 免插件
  • 开源
  • 已被W3C纳入HTML5标准
  • 跨平台,跨浏览器,跨移动应用
  • Mac OSX、Windows、iOS、Android、Linux

应用场景

适用于网页间音视频实时通信,点对点数据共享,QQ、腾讯视频已有应用

优势

1.方便。对于用户来说,在WebRTC出现之前想要进行实时通信就需要安装插件和客户端,但是对于很多用户来说,插件的下载、软件的安装和更新这些操作是复杂而且容易出现问题的,现在WebRTC技术内置于浏览器中,用户不需要使用任何插件或者软件就能通过浏览器来实现实时通信。 2.免费。虽然WebRTC技术已经较为成熟,其集成了最佳的音/视频引擎,十分先进的codec,但是Google对于这些技术不收取任何费用。 3.强大的打洞能力。WebRTC技术包含了使用STUN、ICE、TURN、RTP-over-TCP的关键NAT和防火墙穿透技术,并支持代理。

缺点

1.传输质量难以保证,比如跨地区、跨运营商、低带宽、高丢包、P2P连接率、呼叫成功率。 2.设备端适配,如回声、录音失败等问题层出不穷。这一点在安卓设备上尤为突出。由于安卓设备厂商众多,每个厂商都会在标准的安卓框架上进行定制化,导致很多可用性问题(访问麦克风失败)和质量问题(如回声、啸叫)。

WebRTC媒体会话原理

WebRTC内部结构简化图

WebRTC架构图(截图来自官网https://webrtc.org/)

WebRTC核心技术点,简要概括为三部分

下文详细介绍WebRTC核心API和信令服务器部分

WebRTC 核心API详解

运用RTCPeerConnection和RTCDataChannel两个核心API,能够实现任意数据的点对点交换,官网Demo如下:

该Demo不需要servers,因为呼叫方(发送数据)和呼叫应答方(接收数据)在同一页面上,这样能够清晰的了解RTCPeerConnection API的原理,页面上的RTCPeerConnection对象可以直接交换数据和消息,而无需使用信令服务器。 可以用开发者工具查看WebRTC统计信息

  • Chrome:chrome://webrtc-internals
  • Opera:opera://webrtc-internals
  • FireFox:about:webrtc chrome开发者工具中查看WebRTC,如下图所示:

Demo 代码分析

以Demo为例,分析Web P2P创建、通信、传输数据等流程,具体分析API中各个关键属性、方法、事件的含义和标准操作姿势 完整源码见Github

function createConnection() {  sendButton.disabled = true;  megsToSend.disabled = true;  var servers = null;  bytesToSend = Math.round(megsToSend.value) * 1024 * 1024;  // 创建连接,servers可以传入一些描述信息,由于这个demo不需要验证连接信息,在同一个页面上可以直接连接,该参数传null即可  localConnection = localConnection = new RTCPeerConnection(servers);  //打印log  trace('Created local peer connection object localConnection');  var dataChannelParams = {ordered: false};  if (orderedCheckbox.checked) {    dataChannelParams.ordered = true;  }  //创建数据通道 语法:dataChannel = RTCPeerConnection .createDataChannel(label [,options ]);,lable:通道的名称;optins:是个可选参数,传入数据通道配置参数,有很多参数可选,例子中的ordered:true表示有序模式,false即为无序模式,还有其他参数maxPacketLifeTime 、maxRetransmits等等  sendChannel = localConnection.createDataChannel(      'sendDataChannel', dataChannelParams);  sendChannel.binaryType = 'arraybuffer';  trace('Created send data channel');  //绑定onopen、onclose、onicecandidate(当RTCPeerConnection被createPeerConnection()成功创建时触发,回调会返回待连接端的配置信息)  sendChannel.onopen = onSendChannelStateChange;  sendChannel.onclose = onSendChannelStateChange;  localConnection.onicecandidate = function(e) {    onIceCandidate(localConnection, e);  };  //创建呼叫实例  localConnection.createOffer().then(    gotDescription1,    onCreateSessionDescriptionError  );  //创建远端接收连接实例  remoteConnection = remoteConnection = new RTCPeerConnection(servers);  trace('Created remote peer connection object remoteConnection');  remoteConnection.onicecandidate = function(e) {    onIceCandidate(remoteConnection, e);  };  //当一个RTC数据通道已被远端调用createDataChannel()添加到连接中时触发  remoteConnection.ondatachannel = receiveChannelCallback;}function receiveChannelCallback(event) {  trace('Receive Channel Callback');  receiveChannel = event.channel;  receiveChannel.binaryType = 'arraybuffer';  //接收到数据时触发  receiveChannel.onmessage = onReceiveMessageCallback;  receivedSize = 0;}function onReceiveMessageCallback(event) {  receivedSize += event.data.length;  receiveProgress.value = receivedSize;  if (receivedSize === bytesToSend) {    closeDataChannels();    sendButton.disabled = false;    megsToSend.disabled = false;  }}function onSendChannelStateChange() {  var readyState = sendChannel.readyState;  trace('Send channel state is: ' + readyState);  if (readyState === 'open') {    sendGeneratedData();  }}function sendGeneratedData() {  sendProgress.max = bytesToSend;  receiveProgress.max = sendProgress.max;  sendProgress.value = 0;  receiveProgress.value = 0;  var chunkSize = 16384;  var stringToSendRepeatedly = randomAsciiString(chunkSize);  var bufferFullThreshold = 5 * chunkSize;  var usePolling = true;  if (typeof sendChannel.bufferedAmountLowThreshold === 'number') {    trace('Using the bufferedamountlow event for flow control');    usePolling = false;    // 缓冲区大小限值    bufferFullThreshold = chunkSize / 2;    // 缓冲区大小控制    sendChannel.bufferedAmountLowThreshold = bufferFullThreshold;  }  // bufferedamountlow 事件处理  var listener = function() {    sendChannel.removeEventListener('bufferedamountlow', listener);    sendAllData();  };  var sendAllData = function() {    // 把一堆数据排队进行处理,在数据通道被填满时停止,这里不建议每次发送后设置Timeout,这样会降低吞吐量    while (sendProgress.value < sendProgress.max) {      if (sendChannel.bufferedAmount > bufferFullThreshold) {        if (usePolling) {          setTimeout(sendAllData, 250);        } else {          sendChannel.addEventListener('bufferedamountlow', listener);        }        return;      }      sendProgress.value += chunkSize;      // send方法发送数据,RTCDataChannel的语法跟WebSocket语法非常相似,都有message事件和sand方法      sendChannel.send(stringToSendRepeatedly);    }  };  setTimeout(sendAllData, 0);}

WebRTC核心API兼容性

MediaStream and getUserMedia

  • Chrome desktop 18.0.1008+; Chrome for Android 29+
  • Opera 18+; Opera for Android 20+
  • Opera 12, Opera Mobile 12 (基于Presto引擎)
  • Firefox 17+
  • Microsoft Edge

RTCPeerConnection

  • Chrome desktop 20+ (now ‘flagless’, i.e. no need to set about:flags); * * Chrome for Android 29+ (flagless)
  • Opera 18+ (默认开启); Opera for Android 20+ (默认开启)
  • Firefox 22+ (默认开启)

RTCDataChannel

  • Chrome 25中的实验版本,在Chrome 26+中更稳定(and with Firefox interoperability); Chrome for Android 29+
  • Opera 18+中的稳定版本(and with Firefox interoperability); Opera for * * * Android 20+
  • Firefox 22+ (默认开启)

信令服务器

信令就是协调通讯的过程,为了建立一个webRTC的通讯过程,客户端需要交换如下信息:

  • 会话控制信息,用来开始和结束通话,即开始视频、结束视频这些操作指令。
  • 处理错误的消息。
  • 元数据,如各自的音视频解码方式、带宽。
  • 网络数据,对方的公网IP、端口、内网IP及端口。 我们需要一个中间服务器来在客户端之间交换信令消息和数据,这个过程在WebRTC里面是没有实现的,但WebRTC协议没有规定与服务器的通信方式,因此可以采用各种方式,比如WebSocket。初学者可以用NodeJS搭建简易的信令服务器,交换双方的元数据,真实项目里还会有STUN和TURN服务器 。

下面是NodeJS创建信令服务器的源码:

'use strict';var os = require('os');var nodeStatic = require('node-static');var http = require('http');var socketIO = require('socket.io');var fileServer = new(nodeStatic.Server)();var app = http.createServer(function(req, res) {  fileServer.serve(req, res);}).listen(8080);var io = socketIO.listen(app);io.sockets.on('connection', function(socket) {  // 打印日志功能  function log() {    var array = ['Message from server:'];    array.push.apply(array, arguments);    socket.emit('log', array);  }  socket.on('message', function(message) {    log('Client said: ', message);    // 本示例使用广播方式,真实项目中应该是指定房间号(Socket.IO适用于学习WebRTC信号,因为它内置了'房间'的概念)    socket.broadcast.emit('message', message);  });  socket.on('create or join', function(room) {    log('Received request to create or join room ' + room);    var clientsInRoom = io.sockets.adapter.rooms[room];    var numClients = clientsInRoom ? Object.keys(clientsInRoom.sockets).length : 0;    log('Room ' + room + ' now has ' + numClients + ' client(s)');    if (numClients === 0) {      socket.join(room);      log('Client ID ' + socket.id + ' created room ' + room);      socket.emit('created', room, socket.id);    } else if (numClients === 1) {      log('Client ID ' + socket.id + ' joined room ' + room);      io.sockets.in(room).emit('join', room);      socket.join(room);      socket.emit('joined', room, socket.id);      io.sockets.in(room).emit('ready');    } else { // 最多两个客户端      socket.emit('full', room);    }  });  socket.on('ipaddr', function() {    var ifaces = os.networkInterfaces();    for (var dev in ifaces) {      ifaces[dev].forEach(function(details) {        if (details.family === 'IPv4' && details.address !== '127.0.0.1') {          socket.emit('ipaddr', details.address);        }      });    }  });});

利用WebRTC相关技术有很多可以创新的点,比如业界已有创业团队在做Web P2P,核心技术就是WebRTC + DASH协议,共享空闲资源,基于此可以做雾CDN,节点都在用户侧,去中心化,这里还是有很多挖掘空间的。

参考资料

https://webrtc.org/

https://developer.mozilla.org/zh-CN/docs/Web/API/WebRTC_API

https://hpbn.co/webrtc/

https://webrtchacks.com/https://codelabs.developers.google.com/codelabs/webrtc-web/#0

原文发布于微信公众号 - QQ音乐前端团队(QQMusicFE)

原文发表时间:2018-07-01

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏lestat's blog

SoapClient的一点总结

近期在开发一个小型的酒店订房系统 ---- 应用场景:由于是在公司之前一个订房系统基础上进行修改,因此工作量不算大,但需要在系统中多个位置和酒店方提供的另一个...

2624
来自专栏安智客

TrustZone是如何保证硬件安全的?

从技术角度来说,一讲到TEE就会提到TrustZone,这是因为虽然TEE OS实现有多种多样,主芯片厂商也有好几种,但是大部分都是基于Arm的TrustZon...

1512
来自专栏FreeBuf

挖洞经验 | 看我如何利用上传漏洞在PayPal服务器上实现RCE执行

当你看到这篇文章标题时,是不是很吃惊,PayPal服务器的RCE漏洞?Dafaq?WTF?真的吗?这当然是真的,很幸运,我通过枚举和域名查找方法发现了该漏洞。 ...

3605
来自专栏小詹同学

Python爬虫系列之一——我有100万?

高中生都开始写爬虫了,可见爬虫有多热门,一个某某985高校的研究生不学习学习爬虫实在是有些落伍啦~ ? 一、网络爬虫和url ...

3486
来自专栏互联网杂技

react+redux+webpack教程1

经历了EXT、YUI时代,又经历了Angular时代,我们正在步入React时代。 前端开发框架凭借JS极其灵活强大的特性模样越来越“怪异”,正是这怪异让我们对...

3245
来自专栏京东技术

多级缓存设计详解 | 给数据库减负,刻不容缓!

物流研发部架构师,GIS技术部负责人,2012年加入京东,多年一线团队大促备战经验,负责物流研发一些部门的架构工作,专注于低延迟系统设计与海量数据处理。曾负责青...

1335
来自专栏IT技术精选文摘

58同城数据库架构设计思路

(1)可用性设计 解决思路:复制+冗余 副作用:复制+冗余一定会引发一致性问题 保证“读”高可用的方法:复制从库,冗余数据,如下图 ? 带来的问题:主从不一致 ...

2037
来自专栏Ryan Miao

Dubbo学习1-Hello world

前言 互联网技术到今天已经非常成熟和稳定了,其中为了解决高并发、大规模的服务请求,出现了微服务、RPC这样的分布式架构。今天就从头开始学习RPC框架dubbo。...

4096
来自专栏web前端教室

定位+思路+方法,三步教你快速敲定前端JS结构

闲话少说, --定位 就是说你现在要写的是什么东西?是一个页面?还是一个组件?还是一个function方法?不同的东西它的定位不同,结构就不同。 如果是页面,那...

19610
来自专栏Java进阶架构师

dubbo源码解析-详解cluster

今天是小长假的倒数第二天,本来国庆是要加班四天的,后来因为要有事要回家才得以幸免,但是后天上班之后都要搬砖搬到手脱皮是必须的了.但是再忙每周一篇源码解析的承诺都...

701

扫码关注云+社区