WebRTC对象

yuhuo2023-03-15JavaScriptJavaScript对象
参考链接

概念

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 服务器:coturnopen in new window

测试 STUN/TURN 可用性工具:Trickle ICE (webrtc.github.io)open in new window

免费的 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)open in new window

建立点对点连接,点对点传输音视频数据

<!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)open in new window

在已建立点对点连接的情况下,点对点传输任意数据

Last Updated 2024/3/14 09:51:53