背景:
平时我们很少会需要使用到点对点单独的通讯,即p2p,一般都是点对服务端通讯,但p2p也有自己的好处,即通讯不经过服务端,从服务端角度这个省了带宽和压力,从客户端角度,通讯是安全,且快速的,当然有些情况下可能速度并不一定快。那么如何实现p2p呢?
解决办法:
webrtc的RTCPeerConnection就实现了p2p的功能,使用RTCPeerConnection需要理解一些概念,什么是信令,信令交换的过程,信令服务器。
信令
2个设备需要通讯,就需要知道对方的在互联网上的公开地址,一般情况下2个设备都是不会直接拥有一个公网的ip地址,所以他们之间的通讯,就需要如何在公网找到自己的方式,路由信息告诉对方,通常这个信息都是临时的,并非永久,当对方获取到这个信息后,就可以通过网络找到彼此的实际路由路径,从而进行通讯,这个信息就是信令(位置信息)。
信令的交换过程:
假设2个设备,p1要和p2进行通讯
1.p1发起邀请阶段
const offer = await p1.createOffer();//创建邀请信令
await p1.setLocalDescription(offer);//设置为本地信令
send(JSON.stringify(offer));//把邀请信令发送给对方,至于怎么发送,一般是需要一个第3方的信令服务器来转发这个信息
2.p2收到邀请阶段
当收到p1发起的有邀请信令offer后
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收到应答信息阶段
await p2.setRemoteDescription(new RTCSessionDescription(JSON.parse(answer )));//设置为远端的信令
4.处理onicecandidate事件,确认要不要通讯
await p2.addIceCandidate(new RTCIceCandidate(JSON.parse(candidate)));
完成上述几个阶段,正常来说就能开始通讯了
数据通讯DataChannel的使用
发送端
// 创建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!');
接收端:
// 创建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
WebRTC Demo
WebRTC Demo
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)
}
不同页面demo,信令交换过程手动操作
WebRTC 文本消息发送
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);
displayM服务器托管网essage('我:' + message);
messageInput.value = '';
}
// 显示消息
function displayMessage(message) {
const chatDiv = document.getElementById('chat');
const messageP = document.createElement('p');
messageP.textContent = message;
chatDiv.appendChild(messageP);
}
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
背景 继上次的if else优化也有段时间了,最近小猫又又又着道了,接手的那个项目又遇到了坑爹的地方,经常性的报死锁异常,经常性的主从延迟……通过报错信息按图索骥,发现代码是这样的。 这是一段商品发布的逻辑,我们可以看到参数校验、查询、最终的inser…