WebRTC对象
参考链接
概念
NAT
NAT(Network Address Translation)网络地址转换,指的是路由器等网络设备,在传输数据的过程中,改变数据中的 IP 地址的一种技术。
原理
内网设备发起请求时,NAT 将其内网设备的内网 IP 和内部端口,转成当前 NAT 下的公网 IP 和外部端口,并且保存映射表。当 NAT 接收到响应时,根据响应报文中的目标端口(即外部端口),便能找到内网 IP 和内部端口,并将响应转发给它。
- 内部端口:用来匹配内网设备中的某个应用;
- 外部端口:用来匹配 NAT 中的某个内网设备;
优点
- 同个局域网共用一个公网 IP,节省了公网 IP 的使用;
- 外部设备无法主动发起对内网设备的连接,起到防火墙的作用,提高了安全性;
缺点
只能由内网设备先发起请求,NAT 中才有映射表,才能接收响应。即使有映射表,外部设备也无法得知内部设备的公网 IP 和外部端口,所以无法主动发起对内网设备的连接。(可通过 STUN 和 TURN 解决)
类型
- 完全圆锥形 NAT(Full cone NAT):只匹配外部端口;
- 受限圆锥形 NAT(Restricted cone NAT):映射表中还保存内网设备请求时目的 IP,响应时必须匹配外部端口和目的 IP;
- 端口受限圆锥形 NAT(Port-Restricted cone NAT):在受限圆锥形 NAT 的基础上,还要匹配目的端口;
- 对称 NAT(Symmetric NAT):如果目的 IP 和目的端口变化,外部端口也会不同,会生成一条新的映射表项;
STUN
STUN(Session Traversal Utilities for NAT)会话穿透应用程序,又称 NAT 打洞。
对应完全圆锥形 NAT,内网设备请求 STUN 服务器,服务器获取内网设备的公网 IP 和对外端口,并告诉一个外部设备,该外部设备就能直接发送消息到内网设备了。
受限圆锥形 NAT 和 端口受限圆锥形 NAT,在上面的基础上,需要内网设备再发个请求到外部设备,以在映射表中保存外部设备的IP和端口。
对称 NAT 无法打洞。
TURN
TURN(Traversal Using Relay NAT) 服务器中转穿透。
STUN 的数据传输还是点对点,但 TURN 的数据需要经过服务器中转,消耗服务器流量,仅在 STUN 不可用时使用。
常见的开源 STUN/TURN 服务器:coturn
测试 STUN/TURN 可用性工具:Trickle ICE (webrtc.github.io)
免费的 STUN 服务器:stun:stun.l.google.com:19302
ICE
ICE(Interactive Connectivity Establishment)交互式连通性建立。
收集候选地址,决定要 NAT 穿透的方式。
候选者类型(优先级从高到低):
- 主机候选者:设备自身 IP 和内部端口
- 反射候选者:经过 NAT 之后的公网 IP 和外部端口(STUN)
- 中继候选者:TURN 服务的转发地址和端口(TURN)
RTCPeerConnection 类
RTCPeerConnection - Web API 接口参考 | MDN (mozilla.org)
建立点对点连接,点对点传输音视频数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.remote_video {
width: 500px;
border: 1px solid gray;
}
.local_video {
position: absolute;
left: 398px;
top: 18px;
width: 100px;
}
</style>
</head>
<body>
<div>
<video class="remote_video"></video>
<video class="local_video"></video>
</div>
<br />
<label>本地信令:</label>
<input type="text" class="local_des" readonly placeholder="本地信令" />
<button onclick="createLocalDes('offer')">生成本地信令</button>
<br /><br />
<label>远端信令:</label>
<input type="text" class="remote_des" placeholder="远端信令" />
<button onclick="saveRemoteDes()">保存远端信令</button>
<br /><br />
<button onclick="closeConnect()">关闭连接</button>
<script>
let remoteVedio = document.querySelector(".remote_video");
let localVedio = document.querySelector(".local_video");
let remoteDes = document.querySelector(".remote_des");
let localDes = document.querySelector(".local_des");
let pc;
// 初始化连接
function initPc() {
pc = new RTCPeerConnection({
iceServers: [
{ urls: "stun:stun.voipbuster.com" },
{ urls: "stun:stun.l.google.com:19302" },
],
});
// 监听远程媒体流事件
pc.ontrack = function (evt) {
if (!remoteVedio.srcObject) {
remoteVedio.srcObject = evt.streams[0];
remoteVedio.play();
}
};
// 监听ice服务器返回新候选者事件
pc.onicecandidate = (event) => {
if (event.candidate) {
localDes.value = JSON.stringify(pc.localDescription);
}
};
// 监听连接断开事件
pc.onconnectionstatechange = (event) => {
if (pc.connectionState == "disconnected") {
closeConnect();
}
};
}
// 生成本地信令
async function createLocalDes(type) {
if (type == "offer" && localDes.value) {
alert("本地信令已存在");
return;
}
if (!pc) {
initPc();
}
// 采集本地媒体流
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
// 设置本地媒体流
localVedio.srcObject = localStream;
localVedio.play();
// 本地媒体流的轨道都添加到 RTCPeerConnection 中
localStream.getTracks().forEach((track) => {
pc.addTrack(track, localStream);
});
// 生成本地信令
pc.setLocalDescription(
await (type == "answer" ? pc.createAnswer() : pc.createOffer())
);
}
// 保存远端信令
async function saveRemoteDes() {
if (!remoteDes.value) {
alert("请先填写远端信令");
return;
}
if (!pc) {
initPc();
}
const des = JSON.parse(remoteDes.value);
await pc.setRemoteDescription(des);
// 来自远端的呼叫,则还需要创建本地信令去响应
if (des.type == "offer") {
createLocalDes("answer");
}
}
// 关闭连接
function closeConnect() {
// 关闭视频
localVedio.srcObject = null;
remoteVedio.srcObject = null;
// 清空输入框
localDes.value = "";
remoteDes.value = "";
// 关闭连接对象
pc.close();
pc = null;
alert("连接已关闭");
}
</script>
</body>
</html>
RTCDataChannel 类
RTCDataChannel - Web API 接口参考 | MDN (mozilla.org)
在已建立点对点连接的情况下,点对点传输任意数据