webrtc的RTCPeerConnection使用

背景:

平时我们很少会需要使用到点对点单独的通讯,即p2p,一般都是点对服务端通讯,但p2p也有自己的好处,即通讯不经过服务端,从服务端角度这个省了带宽和压力,从客户端角度,通讯是安全,且快速的,当然有些情况下可能速度并不一定快。那么如何实现p2p呢?

解决办法:

webrtc的RTCPeerConnection就实现了p2p的功能,使用RTCPeerConnection需要理解一些概念,什么是信令,信令交换的过程,信令服务器。

信令

2个设备需要通讯,就需要知道对方的在互联网上的公开地址,一般情况下2个设备都是不会直接拥有一个公网的ip地址,所以他们之间的通讯,就需要如何在公网找到自己的方式,路由信息告诉对方,通常这个信息都是临时的,并非永久,当对方获取到这个信息后,就可以通过网络找到彼此的实际路由路径,从而进行通讯,这个信息就是信令(位置信息)。

信令的交换过程:

假设2个设备,p1要和p2进行通讯

1.p1发起邀请阶段

javascript 复制代码
const offer = await p1.createOffer();//创建邀请信令
await p1.setLocalDescription(offer);//设置为本地信令

send(JSON.stringify(offer));//把邀请信令发送给对方,至于怎么发送,一般是需要一个第3方的信令服务器来转发这个信息

2.p2收到邀请阶段

当收到p1发起的有邀请信令offer后

javascript 复制代码
await p2.setRemoteDescription(new RTCSessionDescription(JSON.parse(offer)));//设置为远端的信令

const answer = await p2.createAnswer();//创新一个应答信令,告诉p1我的位置
await pc.setLocalDescription(answer);//设置我的位置

send(JSON.stringify(answer ));将位置信息发送给p1

3.p1收到应答信息阶段

javascript 复制代码
await p2.setRemoteDescription(new RTCSessionDescription(JSON.parse(answer )));//设置为远端的信令
4.处理onicecandidate事件,确认要不要通讯
javascript 复制代码
 await p2.addIceCandidate(new RTCIceCandidate(JSON.parse(candidate)));

完成上述几个阶段,正常来说就能开始通讯了

数据通讯DataChannel的使用

发送端

javascript 复制代码
// 创建PeerConnection对象
const pc = new RTCPeerConnection();

// 创建DataChannel
const dataChannel = pc.createDataChannel('myDataChannel');

// 监听DataChannel的open事件
dataChannel.onopen = () => {
  console.log('DataChannel已打开');
};

// 监听DataChannel的error事件
dataChannel.onerror = (error) => {
  console.error('DataChannel错误:', error);
};

// 监听DataChannel的close事件
dataChannel.onclose = () => {
  console.log('DataChannel已关闭');
};

// 发送文本消息
function sendMessage(message) {
  dataChannel.send(message);
}

// 发起PeerConnection连接
// ...

// 在某个事件触发时调用sendMessage()发送消息
// sendMessage('Hello, world!');

接收端:

javascript 复制代码
// 创建PeerConnection对象
const pc = new RTCPeerConnection();

// 监听DataChannel的open事件
pc.ondatachannel = (event) => {
  const dataChannel = event.channel;

  // 监听DataChannel的message事件
  dataChannel.onmessage = (event) => {
    const message = event.data;
    console.log('接收到消息:', message);
  };

  // 监听DataChannel的error事件
  dataChannel.onerror = (error) => {
    console.error('DataChannel错误:', error);
  };

  // 监听DataChannel的close事件
  dataChannel.onclose = () => {
    console.log('DataChannel已关闭');
  };
};

datachannel的用法发送端和接收端用法是一样的,只是接收端,需要通过 onicecandidate的事件才能获取到。

单页面完整demo

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>WebRTC Demo</title>
</head>
<body>
    <h1>WebRTC Demo</h1>
    <button onclick="start()">Start</button>
    <button onclick="call()">Call</button>
    <button onclick="hangup()">Hang Up</button>
	<button onclick="sendMessage()">send</button>
    <br><br>
    <textarea id="localDesc"></textarea>
    <br><br>
    <textarea id="remoteDesc"></textarea>
    <br><br>
    <textarea id="message"></textarea>
    <br><br>
    <textarea id="received"></textarea>

    <script>
        let localConnection, remoteConnection,dataChannel,receiveChannel;

        function start() {
            localConnection = new RTCPeerConnection();
            remoteConnection = new RTCPeerConnection();

            localConnection.onicecandidate = e => {
                if (e.candidate) {
					console.log("localConnection.onicecandidate")
                    remoteConnection.addIceCandidate(e.candidate);
                }
            };

            remoteConnection.onicecandidate = e => {
                if (e.candidate) {
					console.log("remoteConnection.onicecandidate")
                    localConnection.addIceCandidate(e.candidate);
                }
            };

            localConnection.oniceconnectionstatechange = e => {
                console.log('Local ICE connection state change:', localConnection.iceConnectionState);
            };

            remoteConnection.oniceconnectionstatechange = e => {
                console.log('Remote ICE connection state change:', remoteConnection.iceConnectionState);
            };

            remoteConnection.ondatachannel = e => {
				console.log("ondatachannel",e)
                receiveChannel = e.channel;
                receiveChannel.onmessage = e => {
					console.log("onmessage",e.data)
                    document.getElementById('received').value += e.data + '\n';
                };
            };

            dataChannel = localConnection.createDataChannel('dataChannel');
            dataChannel.onopen = e => {
				console.log("onopen")
                console.log('Data channel opened');
            };
            dataChannel.onclose = e => {
				console.log("onclose")
                console.log('Data channel closed');
            };
			
			dataChannel.onmessage = event => {
				console.log("onmessage",event.data)
            };
}
        async function call() {
			console.log("createOffer")
            const offer = await localConnection.createOffer();
            await localConnection.setLocalDescription(offer);
            await remoteConnection.setRemoteDescription(offer);
			console.log("createAnswer")
            const answer = await remoteConnection.createAnswer();
            await remoteConnection.setLocalDescription(answer);
            await localConnection.setRemoteDescription(answer);

            document.getElementById('localDesc').value = localConnection.localDescription.sdp;
            document.getElementById('remoteDesc').value = remoteConnection.localDescription.sdp;
        }

        async function hangup() {
            await localConnection.close();
            await remoteConnection.close();
            localConnection = null;
            remoteConnection = null;
        }

        function sendMessage() {
            const message = document.getElementById('message').value;
            //const dataChannel = localConnection.createDataChannel('dataChannel');
            dataChannel.send(message);
			console.log("send",message)
        }
    </script>
</body>
</html>

不同页面demo,信令交换过程手动操作

javascript 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>WebRTC 文本消息发送</title>
</head>
<body>

	<textarea id="xx"></textarea>
	<textarea id="xx2"></textarea>
	<textarea id="xx3"></textarea>
    <div>
        <label for="message">消息:</label>
        <input type="text" id="message" />
        <button onclick="sendMessage()">发送</button>
		 <button onclick="sendOffer()">邀请</button>
		 <button onclick="handleAnswer()">接收远程信令</button>
		 <button onclick="handleCandidate()">接收candidate</button>
		 <br>
		 <button onclick="handleOffer()">接收邀请</button>
    </div>
    <div id="chat"></div>

    <script>
        let pc;
        let dataChannel;

        // 创建本地PeerConnection对象
        function createPeerConnection() {
            pc = new RTCPeerConnection();

            // 创建数据通道
            dataChannel = pc.createDataChannel('chat');

            // 监听收到消息事件
            dataChannel.onmessage = event => {
				console.log(event.data)
                const message = event.data;
                displayMessage(message);
            };

            // 监听连接状态变化事件
            dataChannel.onopen = () => {
                displayMessage('连接已建立');
            };

            dataChannel.onclose = () => {
                displayMessage('连接已关闭');
            };
			
			pc.ondatachannel = (e)=>{
				console.log("ondatachannel",e)
			};

            // 监听ICE候选事件
            pc.onicecandidate = e => {
                if (e.candidate) {
                    document.getElementById("xx3").value = JSON.stringify(e.candidate);
                }
            };
			pc.oniceconnectionstatechange = e => {
                console.log('Local ICE connection state change:', pc.iceConnectionState);
            };
        }
		 createPeerConnection()

        
        // 处理信令
        function handleSignal(signal) {
            switch (signal.type) {
                case 'offer':
                    handleOffer(signal.offer);
                    break;
                case 'answer':
                    handleAnswer(signal.answer);
                    break;
                case 'candidate':
                    handleCandidate(signal.candidate);
                    break;
            }
        }
		async function sendOffer(){
			let desc = await pc.createOffer()
			pc.setLocalDescription(desc); 
			
			document.getElementById("xx").value = JSON.stringify(desc)
            console.log(desc)
		}

        // 处理Offer信令
        async function handleOffer() {
            await pc.setRemoteDescription(new RTCSessionDescription(JSON.parse(document.getElementById("xx2").value)));
			let answer = await pc.createAnswer()
			await pc.setLocalDescription(answer);
			document.getElementById("xx").value = JSON.stringify(answer)
        }

        // 处理Answer信令
        async function handleAnswer() {
            // 设置远端描述
			let answer = new RTCSessionDescription(JSON.parse(document.getElementById("xx2").value))
            await pc.setRemoteDescription(answer);
        }

        // 处理ICE候选信令
        async function handleCandidate() {
            try {
                await pc.addIceCandidate(new RTCIceCandidate(JSON.parse(document.getElementById("xx2").value)));
            } catch (error) {
                console.error('添加ICE候选失败:', error);
            }
        }

        // 发送消息
        function sendMessage() {
            const messageInput = document.getElementById('message');
            const message = messageInput.value;
            dataChannel.send(message);
            displayMessage('我:' + message);
            messageInput.value = '';
        }

        // 显示消息
        function displayMessage(message) {
            const chatDiv = document.getElementById('chat');
            const messageP = document.createElement('p');
            messageP.textContent = message;
            chatDiv.appendChild(messageP);
        }
    </script>
</body>
</html>
相关推荐
Avan_菜菜14 小时前
FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理
运维·nginx·https
SelectDB2 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
zzzzzz3103 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
XIAOHEZIcode3 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220704 天前
如何搭建本地yum源(上)
运维
大树887 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠7 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质7 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz7 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工7 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信