WebRTC教程及STUN/TURN服务器搭建附源码下载
日期:2019-11-23
来源:程序思维浏览:2955次
咱们先看看WebRTC交换媒体能力消息的流程:
在WebRTC中,媒体能力最终通过 SDP 呈现。在传输媒体数据之前,首先要进行媒体能力协商,看双方都支持那些编码方式,支持哪些分辨率等。协商的方法是通过信令服务器交换媒体能力信息。

WebRTC 媒体协商的过种如上图所示。
第一步,Amy 调用 createOffer 方法创建 offer 消息。offer 消息中的内容是 Amy 的 SDP 信息。
第二步,Amy 调用 setLocalDescription 方法,将本端的 SDP 信息保存起来。
第三步,Amy 将 offer 消息通过信令服务器传给 Bob。
第四步,Bob 收到 offer 消息后,调用 setRemoteDescription 方法将其存储起来。
第五步,Bob 调用 createAnswer 方法创建 answer 消息, 同样,answer 消息中的内容是 Bob 的 SDP 信息。
第六步,Bob 调用 setLocalDescription 方法,将本端的 SDP 信息保存起来。
第七步,Bob 将 anwser 消息通过信令服务器传给 Amy。
第八步,Amy 收到 anwser 消息后,调用 setRemoteDescription 方法,将其保存起来。
通过以上步骤就完成了通信双方媒体能力的交换。
上以就是信令服务器应该处理的所有消息,这些消息组成了信令服务器最基本的信令,每一个都必不可少,否则的话双方就无法进行最终的通信了。
下面看看源码展示:
index.html文件
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Untitled Document</title>
</head>
<body>
Local: <br>
<video id="localVideo" autoplay></video><br>
Remote: <br>
<video id="remoteVideo" autoplay></video>
<script>
// 仅仅用于控制哪一端的浏览器发起offer,#号后面有值的一方发起
var isCaller = window.location.href.split('#')[1];
// 与信令服务器的WebSocket连接
var socket = new WebSocket("ws://127.0.0.1:3000");
// stun和turn服务器(本地测试可以忽略)
var iceServer = {
"iceServers": [{
"url": "stun:stun.l.google.com:19302"
}, {
"url": "turn:numb.viagenie.ca",
"username": "webrtc@live.com",
"credential": "muazkh"
}]
};
// 创建PeerConnection实例 (参数为null则没有iceserver,即使没有stunserver和turnserver,仍可在局域网下通讯)
var pc = new webkitRTCPeerConnection(iceServer);
// 发送ICE候选到其他客户端
pc.onicecandidate = function(event){
if (event.candidate !== null) {
socket.send(JSON.stringify({
"event": "_ice_candidate",
"data": {
"candidate": event.candidate
}
}));
}
};
// 如果检测到媒体流连接到本地,将其绑定到一个video标签上输出
pc.onaddstream = function(event){
document.getElementById('remoteVideo').src = URL.createObjectURL(event.stream);
};
// 发送offer和answer的函数,发送本地session描述
var sendOfferFn = function(desc){
pc.setLocalDescription(desc);
socket.send(JSON.stringify({
"event": "_offer",
"data": {
"sdp": desc
}
}));
},
sendAnswerFn = function(desc){
pc.setLocalDescription(desc);
socket.send(JSON.stringify({
"event": "_answer",
"data": {
"sdp": desc
}
}));
};
// 获取本地音频和视频流
navigator.webkitGetUserMedia({
"audio": true,
"video": true
}, function(stream){
//绑定本地媒体流到video标签用于输出
document.getElementById('localVideo').src = URL.createObjectURL(stream);
//向PeerConnection中加入需要发送的流
pc.addStream(stream);
//如果是发起方则发送一个offer信令
if(isCaller){
pc.createOffer(sendOfferFn, function (error) {
console.log('Failure callback: ' + error);
});
}
}, function(error){
//处理媒体流创建失败错误
console.log('getUserMedia error: ' + error);
});
//处理到来的信令
socket.onmessage = function(event){
var json = JSON.parse(event.data);
console.log('onmessage: ', json);
//如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述
if( json.event === "_ice_candidate" ){
pc.addIceCandidate(new RTCIceCandidate(json.data.candidate));
} else {
pc.setRemoteDescription(new RTCSessionDescription(json.data.sdp));
// 如果是一个offer,那么需要回复一个answer
if(json.event === "_offer") {
pc.createAnswer(sendAnswerFn, function (error) {
console.log('Failure callback: ' + error);
});
}
}
};
</script>
</body>
</html>
server.js里面是node代码socket服务器
var express = require('express'),
app = express(),
server = require('http').createServer(app);
server.listen(3000);
app.get('/', function(req, res) {
res.sendfile(__dirname + '/webrtc.html');
});
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({server: server});
// 存储socket的数组,这里只能有2个socket,每次测试需要重启,否则会出错
var wsc = [],
index = 1;
// 有socket连入
wss.on('connection', function(ws) {
console.log('connection');
// 将socket存入数组
wsc.push(ws);
// 记下对方socket在数组中的下标,因为这个测试程序只允许2个socket
// 所以第一个连入的socket存入0,第二个连入的就是存入1
// otherIndex就反着来,第一个socket的otherIndex下标为1,第二个socket的otherIndex下标为0
var otherIndex = index--,
desc = null;
if (otherIndex == 1) {
desc = 'first socket';
} else {
desc = 'second socket';
}
// 转发收到的消息
ws.on('message', function(message) {
var json = JSON.parse(message);
console.log('received (' + desc + '): ', json);
wsc[otherIndex].send(message, function (error) {
if (error) {
console.log('Send message error (' + desc + '): ', error);
}
});
});
});
WebRTC源码下载
在WebRTC 通讯时,光有信令是远远不够的。因为 WebRTC真正要传输的是媒体数据,信令只不过是其中的一部分。在WebRTC中他会尽可能的通过P2P进行数据的传输,但在 P2P穿越不成功时怎么办呢?
那就需要通过媒体中继服务器进行媒体数据的转发,下面我们就来看一下如何搭建媒体中继服务器吧。
STUN/TURN服务器搭建
在公网搭建一套 STUN/TURN 服务并不难。首先要有一台云主机,云主机的获我就不做介绍了,大家去某个云厂商购买就好了。
目前比较流行的 STUN/TURN 服务器是 coturn,使用它搭建 STUN/TURN 服务非常的方便。
下面我们就来看一下它的基本步骤:
获取 coturn 源码
.git clone https://github.com/coturn/coturn.git
编译安装
1.cd coturn
2../configure --prefix=/usr/local/coturn
3.sudo make -j 4 && make install
配置 coturn
网上有很多关于 coturn 的配置文章,搞的很复杂。大多数人都是从网上拷贝转发的,其中有很多错误。其实只要使用 coturn 的默认设置就可以了,我这里整理了一份,如下:
1.listening-port=3478 #指定侦听的端口
2.external-ip=39.105.185.198 #指定云主机的公网IP地址
3.user=aaaaaa:bbbbbb #访问 stun/turn服务的用户名和密码
4.realm=stun.xxx.cn #域名,这个一定要设置
所以,只需将上面 4 行配置项写入到 /usr/local/coturn/etc/turnserver.conf 配置文件中,你的 stun/turn 服务就配置好了。
启动 stun/turn 服务
1.cd /usr/local/coturn/bin
2.turnserver -c ../etc/turnserver.conf
测试 stun/turn 服务
打开 trickle-ice ,按里面的要求输入 stun/turn 地址、用户和密码后就可以探测stun/turn服务是否正常了。
以我们的配置为例,输入的信息分别是:
1.STUN or TURN URI 的值为: turn:stun.xxx.cn
2.用户名为: aaaaaa
3.密码为: bbbbbb
测试的结果如下图所示:

从上图我们可以看到该服务提供了 stun(srflx)和turn(relay)两种服务。
STUN/TURN布署好后,我们就可以使用它进行多媒体数据的传输了,再也不怕因为 NAT 和防火墙的原因导致双方无法通信的问题了。
在WebRTC中,媒体能力最终通过 SDP 呈现。在传输媒体数据之前,首先要进行媒体能力协商,看双方都支持那些编码方式,支持哪些分辨率等。协商的方法是通过信令服务器交换媒体能力信息。

WebRTC 媒体协商的过种如上图所示。
第一步,Amy 调用 createOffer 方法创建 offer 消息。offer 消息中的内容是 Amy 的 SDP 信息。
第二步,Amy 调用 setLocalDescription 方法,将本端的 SDP 信息保存起来。
第三步,Amy 将 offer 消息通过信令服务器传给 Bob。
第四步,Bob 收到 offer 消息后,调用 setRemoteDescription 方法将其存储起来。
第五步,Bob 调用 createAnswer 方法创建 answer 消息, 同样,answer 消息中的内容是 Bob 的 SDP 信息。
第六步,Bob 调用 setLocalDescription 方法,将本端的 SDP 信息保存起来。
第七步,Bob 将 anwser 消息通过信令服务器传给 Amy。
第八步,Amy 收到 anwser 消息后,调用 setRemoteDescription 方法,将其保存起来。
通过以上步骤就完成了通信双方媒体能力的交换。
上以就是信令服务器应该处理的所有消息,这些消息组成了信令服务器最基本的信令,每一个都必不可少,否则的话双方就无法进行最终的通信了。
下面看看源码展示:
index.html文件
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Untitled Document</title>
</head>
<body>
Local: <br>
<video id="localVideo" autoplay></video><br>
Remote: <br>
<video id="remoteVideo" autoplay></video>
<script>
// 仅仅用于控制哪一端的浏览器发起offer,#号后面有值的一方发起
var isCaller = window.location.href.split('#')[1];
// 与信令服务器的WebSocket连接
var socket = new WebSocket("ws://127.0.0.1:3000");
// stun和turn服务器(本地测试可以忽略)
var iceServer = {
"iceServers": [{
"url": "stun:stun.l.google.com:19302"
}, {
"url": "turn:numb.viagenie.ca",
"username": "webrtc@live.com",
"credential": "muazkh"
}]
};
// 创建PeerConnection实例 (参数为null则没有iceserver,即使没有stunserver和turnserver,仍可在局域网下通讯)
var pc = new webkitRTCPeerConnection(iceServer);
// 发送ICE候选到其他客户端
pc.onicecandidate = function(event){
if (event.candidate !== null) {
socket.send(JSON.stringify({
"event": "_ice_candidate",
"data": {
"candidate": event.candidate
}
}));
}
};
// 如果检测到媒体流连接到本地,将其绑定到一个video标签上输出
pc.onaddstream = function(event){
document.getElementById('remoteVideo').src = URL.createObjectURL(event.stream);
};
// 发送offer和answer的函数,发送本地session描述
var sendOfferFn = function(desc){
pc.setLocalDescription(desc);
socket.send(JSON.stringify({
"event": "_offer",
"data": {
"sdp": desc
}
}));
},
sendAnswerFn = function(desc){
pc.setLocalDescription(desc);
socket.send(JSON.stringify({
"event": "_answer",
"data": {
"sdp": desc
}
}));
};
// 获取本地音频和视频流
navigator.webkitGetUserMedia({
"audio": true,
"video": true
}, function(stream){
//绑定本地媒体流到video标签用于输出
document.getElementById('localVideo').src = URL.createObjectURL(stream);
//向PeerConnection中加入需要发送的流
pc.addStream(stream);
//如果是发起方则发送一个offer信令
if(isCaller){
pc.createOffer(sendOfferFn, function (error) {
console.log('Failure callback: ' + error);
});
}
}, function(error){
//处理媒体流创建失败错误
console.log('getUserMedia error: ' + error);
});
//处理到来的信令
socket.onmessage = function(event){
var json = JSON.parse(event.data);
console.log('onmessage: ', json);
//如果是一个ICE的候选,则将其加入到PeerConnection中,否则设定对方的session描述为传递过来的描述
if( json.event === "_ice_candidate" ){
pc.addIceCandidate(new RTCIceCandidate(json.data.candidate));
} else {
pc.setRemoteDescription(new RTCSessionDescription(json.data.sdp));
// 如果是一个offer,那么需要回复一个answer
if(json.event === "_offer") {
pc.createAnswer(sendAnswerFn, function (error) {
console.log('Failure callback: ' + error);
});
}
}
};
</script>
</body>
</html>
server.js里面是node代码socket服务器
var express = require('express'),
app = express(),
server = require('http').createServer(app);
server.listen(3000);
app.get('/', function(req, res) {
res.sendfile(__dirname + '/webrtc.html');
});
var WebSocketServer = require('ws').Server,
wss = new WebSocketServer({server: server});
// 存储socket的数组,这里只能有2个socket,每次测试需要重启,否则会出错
var wsc = [],
index = 1;
// 有socket连入
wss.on('connection', function(ws) {
console.log('connection');
// 将socket存入数组
wsc.push(ws);
// 记下对方socket在数组中的下标,因为这个测试程序只允许2个socket
// 所以第一个连入的socket存入0,第二个连入的就是存入1
// otherIndex就反着来,第一个socket的otherIndex下标为1,第二个socket的otherIndex下标为0
var otherIndex = index--,
desc = null;
if (otherIndex == 1) {
desc = 'first socket';
} else {
desc = 'second socket';
}
// 转发收到的消息
ws.on('message', function(message) {
var json = JSON.parse(message);
console.log('received (' + desc + '): ', json);
wsc[otherIndex].send(message, function (error) {
if (error) {
console.log('Send message error (' + desc + '): ', error);
}
});
});
});
WebRTC源码下载
在WebRTC 通讯时,光有信令是远远不够的。因为 WebRTC真正要传输的是媒体数据,信令只不过是其中的一部分。在WebRTC中他会尽可能的通过P2P进行数据的传输,但在 P2P穿越不成功时怎么办呢?
那就需要通过媒体中继服务器进行媒体数据的转发,下面我们就来看一下如何搭建媒体中继服务器吧。
STUN/TURN服务器搭建
在公网搭建一套 STUN/TURN 服务并不难。首先要有一台云主机,云主机的获我就不做介绍了,大家去某个云厂商购买就好了。
目前比较流行的 STUN/TURN 服务器是 coturn,使用它搭建 STUN/TURN 服务非常的方便。
下面我们就来看一下它的基本步骤:
获取 coturn 源码
.git clone https://github.com/coturn/coturn.git
编译安装
1.cd coturn
2../configure --prefix=/usr/local/coturn
3.sudo make -j 4 && make install
配置 coturn
网上有很多关于 coturn 的配置文章,搞的很复杂。大多数人都是从网上拷贝转发的,其中有很多错误。其实只要使用 coturn 的默认设置就可以了,我这里整理了一份,如下:
1.listening-port=3478 #指定侦听的端口
2.external-ip=39.105.185.198 #指定云主机的公网IP地址
3.user=aaaaaa:bbbbbb #访问 stun/turn服务的用户名和密码
4.realm=stun.xxx.cn #域名,这个一定要设置
所以,只需将上面 4 行配置项写入到 /usr/local/coturn/etc/turnserver.conf 配置文件中,你的 stun/turn 服务就配置好了。
启动 stun/turn 服务
1.cd /usr/local/coturn/bin
2.turnserver -c ../etc/turnserver.conf
测试 stun/turn 服务
打开 trickle-ice ,按里面的要求输入 stun/turn 地址、用户和密码后就可以探测stun/turn服务是否正常了。
以我们的配置为例,输入的信息分别是:
1.STUN or TURN URI 的值为: turn:stun.xxx.cn
2.用户名为: aaaaaa
3.密码为: bbbbbb
测试的结果如下图所示:

从上图我们可以看到该服务提供了 stun(srflx)和turn(relay)两种服务。
STUN/TURN布署好后,我们就可以使用它进行多媒体数据的传输了,再也不怕因为 NAT 和防火墙的原因导致双方无法通信的问题了。
精品好课