简单来说,RTCDataChannel 就是在点对点连接中建立一个双向的数据通道,从而获得文本、文件等数据的点对点传输能力。它依赖于流控制传输协议(SCTP),SCTP 是一种传输协议,类似于 TCP 和 UDP,可以直接在 IP 协议之上运行。但是,在 WebRTC 的情况下,SCTP 通过安全的 DTLS 隧道进行隧道传输,该隧道本身在 UDP 之上运行
。
这个示例在Connect之后,可以输入文本后,使用send发送到另一端。注意: 尽管参与连接的两端都在同一页面,我们将启动连接的一端称为 "local" 端,另一端称为 "remote" 端。
localConnection = new RTCPeerConnection();
sendChannel = localConnection.createDataChannel("sendChannel");
sendChannel.onopen = handleSendChannelStatusChange;
sendChannel.onclose = handleSendChannelStatusChange;
第一步是建立该连接的 "local" 端,它是发起连接请求的一方。 下一步是通过调用RTCPeerConnection.createDataChannel() 来创建 RTCDataChannel 并设置事件侦听以监视该数据通道, 从而获知该通道的打开或关闭 (即获得该对等连接的通道打开或者关闭的时机)。
请务必记住该通道的每一端都拥有自己的 RTCDataChannel 对象。
createDataChannel方法允许传入第二个参数进行不同的配置
reliable 设置消息传递是否进行担保
ordered 用来设置消息的接受是否需要按照发送时的顺序
maxRetransmitTime 设置消息发送失败时,多久重新发送
maxRetransmits 设置消息发送失败时,最多重发次数
protocol 设置强制使用其他子协议,但当用户代理不支持该协议时会报错
negotiated 此选项用来设置开发人员是否有责任在两边创建数据通道,还是浏览器来自动完成这个步骤
id 这个用来设置通道的唯一标识,可以在多通道时进行区分
这些配置项很多,不过大部分只在高级应用中才会使用。主要使用的配置项是reliable和 ordered,当设置为true时数据通道表现得更像TCP,设置为False时表现得更像UDP。
negotiated 为解决两边用户同步创建数据通道而设置。用来处理ondatachannel事件触发时的不同行为。默认值是false,表示浏览器会在通道另一边自动触发这个事件,告诉它这个新通道。如果设置成True,开发者需要自己在通道两边创建相同ID的数据通道。
【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~
![]()
remoteConnection = new RTCPeerConnection();
remoteConnection.ondatachannel = receiveChannelCallback;
远程端的建立过程类似“local”端, 但它无需自己创建 RTCDataChannel , 因为我们将通过上面建立的渠道进行连接。 我们创建对 datachannel 的事件处理回调;数据通道打开时该逻辑将被执行, 该回调处理将接收到一个 RTCDataChannel
对象,此过程将在文章后面部分描述。
下一步为每个连接建立 ICE 候选侦听处理, 当连接的一方出现新的 ICE 候选时该侦听逻辑将被调用以告知连接的另一方此消息。
注意: 在现实场景,当参与连接的两节点运行于不同的上下文,建立连接的过程或稍微复杂些,每一次双方通过调用RTCPeerConnection.addIceCandidate(),提出连接方式的建议 (例如: UDP,、中继UDP 、 TCP之类的) , 双方来回往复直到达成一致。本文既然不涉及现实网络环境,因此我们假定双方接受首次连接建议。
localConnection.onicecandidate = e => !e.candidate|| remoteConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);
remoteConnection.onicecandidate = e => !e.candidate|| localConnection.addIceCandidate(e.candidate).catch(handleAddCandidateError);
我们配置每个 RTCPeerConnection 对于事件 icecandidate 建立事件处理。
建立节点连接的最后一项是创建一个连接offer.
localConnection.createOffer().then(offer => localConnection.setLocalDescription(offer)).then(() => remoteConnection.setRemoteDescription(localConnection.localDescription)).then(() => remoteConnection.createAnswer()).then(answer => remoteConnection.setLocalDescription(answer)).then(() => localConnection.setRemoteDescription(remoteConnection.localDescription)).catch(handleCreateDescriptionError);
RTCPeerConnection 一旦open, 事件datachannel 被发送到远端以完成打开数据通道的处理, 该事件触发 receiveChannelCallback()
方法,如下所示:
function receiveChannelCallback(event) {receiveChannel = event.channel;receiveChannel.onmessage = handleReceiveMessage;receiveChannel.onopen = handleReceiveChannelStatusChange;receiveChannel.onclose = handleReceiveChannelStatusChange;}
事件datachannel 在它的channel属性中包括了: 对代表remote节点的 channel的RTCDataChannel 的指向, 它保存了我们用以在该channel上对我们希望处理的事件建立的事件监听。 一旦侦听建立, 每当remote节点接收到数据 handleReceiveMessage()
方法将被调用, 每当通道的连接状态发生改变 handleReceiveChannelStatusChange()
方法将被调用, 因此通道完全打开或者关闭时我们都可以作出相应的相应。
local节点和remote节点采用同样的方法处理表示通道连接状态变更的事件。
当local节点遭遇open 或者 close 事件, handleSendChannelStatusChange()
方法被调用:
function handleSendChannelStatusChange(event) {if (sendChannel) {var state = sendChannel.readyState;
if (state === "open") {messageInputBox.disabled = false;messageInputBox.focus();sendButton.disabled = false;disconnectButton.disabled = false;connectButton.disabled = true;} else {messageInputBox.disabled = true;sendButton.disabled = true;connectButton.disabled = false;disconnectButton.disabled = true;}}}
如果通道状态已经变更为 "open", 意味着我们已经完成了在两对等节点之间建立连接。 相应地用户界面根据状态更新,许用并将输入光标聚焦在text 输入框,以便用户可以立即输入要发送给对方的文本消息, 同时界面许用 "Send" 和 "Disconnect" 按钮(既然它们已经准备好了),禁用"Connect"按钮,既然在已经建立连接的情况下用不着它。
当连接状态变更为 "closed"时,界面执行相反的操作: 禁用文本输入框和 "Send" 按钮 , 许用"Connect" 按钮, 以便用户在需要时可以打开新的连接,禁用"Disconnect" 按钮,既然没有连接时用不着它。
另一方面,作为我们例子的remote 节点, 则无视这些状态改变事件, 仅仅是在控制台输出它们:
function handleReceiveChannelStatusChange(event) {if (receiveChannel) {console.log("Receive channel's status has changed to " +receiveChannel.readyState);}}
当用户按下 "Send" 按钮,触发我们已建立的该按钮的 click事件处理逻辑,在处理逻辑中调用sendMessage() 方法。 该方法也足够简单:
function sendMessage() {var message = messageInputBox.value;sendChannel.send(message);
messageInputBox.value = "";messageInputBox.focus();}
send方法可以发送String,Blob,ArrayBuffer,ArrayBufferView。
当远程通道发生“message”事件时,我们的handleReceiveMessage()方法被调用来处理事件。
function handleReceiveMessage(event) {var el = document.createElement("p");var txtNode = document.createTextNode(event.data);el.appendChild(txtNode);receiveBox.appendChild(el);}
参考 【从头到脚】WebRTC + Canvas 实现一个双人协作的共享画板
// 有省略
constructor(canvas, {moveCallback}) {···this.moveCallback = moveCallback || function () {}; // 鼠标移动的回调
}
onmousemove(e) { // 鼠标移动this.isMoveCanvas = true;let endx = e.offsetX;let endy = e.offsetY;let width = endx - this.x;let height = endy - this.y;let now = [endx, endy]; // 当前移动到的位置switch (this.drawType) {case 'line' : {let params = [this.last, now, this.lineWidth, this.drawColor];this.moveCallback('line', ...params);this.line(...params);}...
moveCallback(...arr) { // 同步到对方this.send(arr);
},
send(arr) { // 发送消息if (arr[0] === 'text') {···} else { // 处理数据同步this.channel.send(JSON.stringify(arr));}
}
页面收到 Callback 通知以后,直接调用 send 方法,将数据传递给对方。接收到数据后,调用封装类相应方法进行绘制。
handleChannel(channel) { // 处理 channel···channel.onmessage = (e) => { // 收到消息 普通消息类型是 对象if (Array.isArray(JSON.parse(e.data))) { // 如果收到的是数组,进行结构let [type, ...arr] = JSON.parse(e.data);this.palette[type](...arr); // 调用相应方法} else {this.messageList.push(JSON.parse(e.data)); // 接收普通消息}// console.log('channel onmessage', e.data);};
}
```·
上一篇: 难忘的一件事英语作文(精简6篇)
下一篇: 安全的英语作文(优质6篇)