WebRTC背景
WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准。
客户端
创建RTCPeerConnection实例->发送candidate->创建offer->交换offer->添加媒体流
客户端主要工作简单来看就是三个交换,交换ice、交换offer、交换流。通过代码来看:
创建WebRTC实例
去除浏览器前缀转换通用变量
const RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection; const IceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate; const SessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription; navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
创建WebRTC peer实例,它接收一个配置选项
configuration
,其中配置了用来创建连接的 ICE 服务器信息。const configuration = { // 内网穿透使用sturn和turn服务信息,下面的服务器国内无法使用,需要自行搭建,见后文 iceServers: [ {urls: "stun:23.21.150.121"}, {urls: "stun:stun.l.google.com:19302"}, {urls: "turn:numb.viagenie.ca", credential: "webrtcdemo", username: "louis%40mozilla.com"} ] } const pc = new RTCPeerConnection(configuration); // 当 ICE 框架找到可以使用 Peer 创建连接的 “候选者”,会立即触发一个事件(onicecandidate)。ICE候选者信息通过信令服务器转发 pc.onicecandidate = function (e) { if (!e.candidate) return; // 通过信令服务器转发candidate 接收方通过addIceCandidate方法添加 send("icecandidate", JSON.stringify(e.candidate)); }; // ... function addIce(candidate){ // 信令服务器响应后 将远端的candidate加入本地ice pc.addIceCandidate(new RTCIceCandidate(candidate)); }
ICECandidate
ICE全称Interactive Connectivity Establishment:交互式连通建立方式。
ICE参照RFC5245建议实现,是一组基于offer/answer模式解决NAT穿越的协议集合。
它综合利用现有的STUN,TURN等协议,以更有效的方式来建立会话。客户端侧无需关心所处网络的位置以及NAT类型,并且能够动态的发现最优的传输路径。
简单来说 发起方创建RTCPeerConnection实例后要将自己的candidate发送给接收方,双方互换candidate才可建立链接。
// 添加ice pc.addIceCandidate(new RTCIceCandidate(candidate))
推荐博客 ICE
交换offer SDP
Offer SDP 是用来向另一端描述期望格式(视频, 格式, 解编码, 加密, 解析度, 尺寸 等等)的元数据。
一次信息的交换需要从一端拿到 offer,然后另外一端接受这个 offer 然后返回一个 answer。
// 创建offer await pc.createOffer({ // 是否接收音频 offerToReceiveAudio: true, // 是否接收视频 offerToReceiveVideo: true }); // 添加至本地描述 await pc.setLocalDescription(offer); // ... 将本地的offer通过信令服务器转发给接收方 // 接收方收到远端的offer 创建自己answer 并发送给主叫 await recvPc.setRemoteDescription(offer); await recvPc.setLocalDescription(await recvPc.createAnswer()); // ...将answer发送给主叫 // ...接收到被叫方返回的answer信息添加到setRemoteDescription pc.setRemoteDescription(answer);
添加流发送给对方
在接收到offer时我们就可以获取本地的媒体流,并添加到RTCPeerConnection实例中
//获取媒体流 如果只想使用默认配置video直接赋值为true window.navigator.mediaDevices.getUserMedia({video: { // 帧数 frameRate: { ideal: 10, max: 15 }, // 宽高 最大1280 最小800 ideal(应用最理想的)值时,这个值有着更高的权重,意味着浏览器会先尝试找到最接近指定的理想值的设定或者摄像头(如果设备拥有不止一个摄像头)。此外还要facingMode 前置或者后置摄像头 width: { max: 1280,ideal: 1024,min:800 }, height: { max: 720,ideal: 960,min:600 } },audio:true}) .then(mediaStream => { console.log('addStream'); // 添加流到RTCPeerConnection实例中 this.pc.addStream(mediaStream); // 把本地的流添加到video标签中播放 $('.my-video').srcObject = mediaStream; }); // 监听onaddstream pc.onaddstream=function(e){ // e.stream $('.your-best-friend-video').srcObject = e.stream; }
推荐阅读MDN教程
推荐博客 浅显易懂
获取媒体流 getUserMedia MDN教程
查看客户端代码
信令服务器
为客户端交换offer交换ice的中转站
使用nodejs创建一个简单的WebSocket服务,如果想要在公网上必须要用wss,因为客户端用的是https协议无法与ws协议连接,下面是如何创建wss:
const WebSocket = require('ws'); const https = require('https'); const options={ key:fs.readFileSync('./ssl/privkey.key','utf8'), cert:fs.readFileSync('./ssl/cert.crt','utf8') } // 添加证书及私钥 证书可以使用openSSL生成,或者申请免费的 const options={ key:fs.readFileSync('./ssl/privkey.key','utf8'), cert:fs.readFileSync('./ssl/cert.crt','utf8') } // 创建https服务 const httpsServer=https.createServer(options, function (req, res) { //要是单纯的https连接的话就会返回这个东西 res.writeHead(403);//403即可 res.end("This is a WebSockets server!\n"); }).listen(15065) // 创建wss服务 const wsServer = new WebSocket.Server({ server: httpsServer }); wsServer.on('connection', (client, request) => { // 发送信息 client.send('xxx') // 接收消息 client.on('message', msg => {}) // 关闭连接 client.on('close', msg => {}) })
查看信令服务服务器代码
STURN/TURN中继服务器
客户端在局域网环境下想要与外网的机器通讯,需要NAT网络穿越。
coturn是谷歌开源的中继服务器应用,集合了sturn 和 turn。
NAT两种类型,对称NAT与非对称NAT:
非对称NAT,每次请求对应的IP端口是不对应的(海王) 使用sturn就可以完成打洞穿越。 对称NAT,每次请求对应的IP端口是对应的(5201314),打洞穿越难度大 需要使用turn服务器中继。 通过一个场景解释:客户端向sturn服务器发请求,获得自己的对外ip和端口,此时客户端是对称NAT,这个对外暴露的端口只能给服务器使用,被叫客户端无法使用主叫暴露的端口导致无法连接。如果是非对称的,sturn服务器获得主叫对外暴露的IP和端口,告诉其他客户端,其他客户端就可以拿着这个ip和端口进行连接。当然非对称也有限制,具体分为三种,阅读这篇文章[对称与非对称NAT](https://blog.csdn.net/yifuteli_kevin/article/details/8911261?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control)。
sturn和turn区别:
sturn帮助客户端寻址,客户端请求sturn服务器,返回给客户端对外暴露的ip和端口,使客户端可以相互寻址,并p2p连接 turn 采用中继的方式,占用资源大
建议在客户端strun和turn都要配置。
下载 安装coturn
下载libevent-2.0 里面有coturn依赖的库
wget https://github.com/downloads/libevent/libevent/libevent-2.0.21-stable.tar.gz tar zxvf libevent-2.0.21-stable.tar.gz cd libevent-2.0.21-stable && ./configure make && make install
下载编译coturn
git clone https://github.com/coturn/coturn cd coturn ./configure make make install
配置
coturn有多种配置方式:命令行、conf文件,数据库。我们使用配置文件的方式。进入/usr/local/etc/目录下有turnserver.conf.default,复制为turnserver.conf
此外还需要OpenSSL
yum install openssl
使用OpenSSL生成cert和key
openssl req -x509 -newkey rsa:2048 -keyout /etc/turn_server_pkey.pem -out /etc/turn_server_cert.pem -days 99999 -nodes
获取网卡信息ifconfig,记下内网地址
进入配置文件并修改
relay-device=eth0 #与前ifconfig查到的网卡名称一致 listening-ip=172.18.77.60 #内网IP listening-port=3478 external-ip=47.107.110.xxx #公网IP relay-threads=50 cert=/etc/turn_server_cert.pem pkey=/etc/turn_server_pkey.pem min-port=49152 max-port=65535 user=user:123456 #用户名密码,创建IceServer时用 realm=xxxxxx
启动turnserver
turnserver -o -a -f -user=user:123456 -r cwtlab
测试
可以使用自带的工具进行测试也可以通谷歌提供的测试sturn/turn网站
使用自动工具
# 测试 STUN turnutils_stunclient 47.107.110.xxx # 测试 TURN turnutils_uclient -u user -w 123456 47.107.110.xxx
进入源码所根目录查看相关文档
README.turnserver 查看turnserver相关指令及介绍
README.turnadmin 查看turnadmin 相关指令及介绍
README.turnutils 查看turnutils 测试
相关程序位置bin目录下
coturn 下载安装 配置 博客
coturn 下载安装 配置二
对称与非对称NAT
谷歌提供的测试sturn/turn网站