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 服务器信息。xconst 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才可建立链接。
xxxxxxxxxx
// 添加ice
pc.addIceCandidate(new RTCIceCandidate(candidate))
交换offer SDP
Offer SDP 是用来向另一端描述期望格式(视频, 格式, 解编码, 加密, 解析度, 尺寸 等等)的元数据。
一次信息的交换需要从一端拿到 offer,然后另外一端接受这个 offer 然后返回一个 answer。
xxxxxxxxxx
// 创建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实例中
xxxxxxxxxx
//获取媒体流 如果只想使用默认配置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;
}
为客户端交换offer、交换ice的中转站
使用nodejs创建一个简单的WebSocket服务,如果想要在公网上必须要用wss,因为客户端用的是https协议无法与ws协议连接,下面是如何创建wss:
xxxxxxxxxx
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 => {})
})
客户端在局域网环境下想要与外网的机器通讯,需要NAT网络穿越。
coturn是谷歌开源的中继服务器应用,集合了sturn 和 turn。
NAT两种类型,对称NAT与非对称NAT:
非对称NAT,每次请求对应的IP端口是不对应的(海王) 使用sturn就可以完成打洞穿越。
对称NAT,每次请求对应的IP端口是对应的(5201314),打洞穿越难度大 需要使用turn服务器中继。
通过一个场景解释:客户端向sturn服务器发请求,获得自己的对外ip和端口,此时客户端是对称NAT,这个对外暴露的端口只能给服务器使用,被叫客户端无法使用主叫暴露的端口导致无法连接。如果是非对称的,sturn服务器获得主叫对外暴露的IP和端口,告诉其他客户端,其他客户端就可以拿着这个ip和端口进行连接。当然非对称也有限制,具体分为三种,阅读这篇文章对称与非对称NAT。
sturn和turn区别:
sturn帮助客户端寻址,客户端请求sturn服务器,返回给客户端对外暴露的ip和端口,使客户端可以相互寻址,并p2p连接
turn 采用中继的方式,占用资源大
建议在客户端strun和turn都要配置。
下载 安装coturn
下载libevent-2.0 里面有coturn依赖的库
xxxxxxxxxx
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
xxxxxxxxxx
git clone https://github.com/coturn/coturn
cd coturn
./configure
make
make install
配置
coturn有多种配置方式:命令行、conf文件,数据库。我们使用配置文件的方式。进入/usr/local/etc/目录下有turnserver.conf.default,复制为turnserver.conf
此外还需要OpenSSL
xxxxxxxxxx
yum install openssl
使用OpenSSL生成cert和key
xxxxxxxxxx
openssl req -x509 -newkey rsa:2048 -keyout /etc/turn_server_pkey.pem -out /etc/turn_server_cert.pem -days 99999 -nodes
获取网卡信息ifconfig,记下内网地址
进入配置文件并修改
xxxxxxxxxx
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
xxxxxxxxxx
turnserver -o -a -f -user=user:123456 -r cwtlab
测试
可以使用自带的工具进行测试也可以通谷歌提供的测试sturn/turn网站
使用自动工具
xxxxxxxxxx
# 测试 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目录下