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>
相关推荐
小安运维日记12 分钟前
CKA认证 | Day3 K8s管理应用生命周期(上)
运维·云原生·容器·kubernetes·云计算·k8s
小han的日常38 分钟前
接口自动化环境搭建
运维·自动化
小扳41 分钟前
Docker 篇-Docker 详细安装、了解和使用 Docker 核心功能(数据卷、自定义镜像 Dockerfile、网络)
运维·spring boot·后端·mysql·spring cloud·docker·容器
运维小文1 小时前
服务器硬件介绍
运维·服务器·计算机网络·缓存·硬件架构
小周不摆烂1 小时前
丹摩征文活动 | 丹摩智算平台:服务器虚拟化的璀璨明珠与实战秘籍
大数据·服务器
中云DDoS CC防护蔡蔡1 小时前
为什么海外服务器IP会被封
服务器·经验分享
是安迪吖1 小时前
nfs服务器
运维·服务器
鱼骨不是鱼翅1 小时前
模拟回显服务器
运维·服务器
EasyCVR2 小时前
ISUP协议视频平台EasyCVR视频设备轨迹回放平台智慧农业视频远程监控管理方案
服务器·网络·数据库·音视频
Elastic 中国社区官方博客2 小时前
使用真实 Elasticsearch 进行更快的集成测试
大数据·运维·服务器·数据库·elasticsearch·搜索引擎·集成测试