1、介绍
在webrtc再实现了端到端的连接过程中除了传输媒体流以外,还可以传输文字,文件,图片等数据,再IM的场景中同样实用,并且不需要第三方消息服务如websocket,透传消息等。
数据通道的创建依赖RTCPeerConnection 的连接,其连接流程在第三章可以查看,当两端建立了连接后,就可以进行发送数据。
2、实践
2.1数据对象 RTCDataChannel
that.localConnection.createDataChannel('webrtc-datachannel')
通过RTCPeerConnection 连接可以创建一个RTCDataChannel对象,创建时需要传入通道名称
2.2数据发送流程
数据通道的最基本引用场景是发文字,使用RTCDataChannel的send方法创建一个发送数据的通道,当发送端和接收端建立连接后就可以发送数据
1、创建本地(localConnection)、远端连接(remoteConnection),发送(sendChannel)和接收数据通道(reviceChannel)
2、发创建连接,发起提议协商和会话,此处参考第三章节内容
3、创建本地连接的数据通道
that.sendChannel = that.localConnection.createDataChannel('webrtc-datachannel')
4、设置远端连接的ondatachannel事件,event参数里的channel 就是远端的数据通道
receiveChannelCallBack: function (event) { let that = this; that.receiveChannel = event.channel that.receiveChannel.onmessage = that.onReceiveMessageCallBack; that.receiveChannel.onopen = that.onReceiveChannelStateChange; console.log("Receive channel callback", that.receiveChannel) }, that.remoteConnection.ondatachannel = that.receiveChannelCallBack;
5、本地和远端设置发送和接收回调方法,接收回调方法的event.data是获取到的管道里的数据
onReceiveMessageCallBack: function (event) { console.log("接受到数据:" + event.data) this.receiveData = event.data },
2.3示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>RTCPeerConnection 连接测试</title> </head> <body> <div class="app"> <div> <h3> 发送 </h3> <div style="margin-left: 70px;margin-top: -39px;margin-bottom: 10px;">状态:{[sendChannelState]} </div> <div> <textarea style="width: 400px;height:100px;" placeholder="请输入要发送得文本...." v-model="sendData"></textarea> </div> </div> <div> <h3> 接收</h3> <div style=" margin-left: 70px;margin-top: -39px;margin-bottom: 10px;">状态:{[receiveChannelState]}</div> <div> <textarea style="width: 400px;height:100px;">{[receiveData]}</textarea> </div> </div> <input type="button" title="呼叫" value="呼叫" v-on:click="call"/> <input type="button" title="发送" value="发送" v-on:click="send"/> <input type="button" title="挂断" value="挂断" v-on:click="stop"/> <hr> </div> </body> <script src="/static/js/Vue.2.5.3.js"></script> <script type="text/javascript"> navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; let vm = new Vue({ el: ".app", delimiters: ['{[', ']}'], data: { // 本地连接 localConnection: null, // 远端视频流 remoteConnection: null, // 本地通道 sendChannel: null, sendChannelState: "", // 远端通道 receiveChannel: null, receiveChannelState: "", // 远端接受到得数据 receiveData: "", // 本地发送数据 sendData: "", // ICE service地址 configuration: { "iceServers": [ { "urls": "stun:49.232.162.254:2478", "username": "admin", "credential": "admin" } ] }, }, methods: { stop: function () { let that = this that.remoteConnection.close() that.localConnection.close() that.localConnection = null that.remoteConnection = null console.log("关闭会话") } , send: async function () { let that = this; if (that.sendData == "") { return } if (that.sendChannel == null) { return } console.log(that.sendChannel.readyState) that.sendChannel.send(that.sendData) console.log("发送数据:", that.sendData) } , call: async function () { let that = this; console.log("开始呼叫") // 监听返回icecandidate 信息 that.localConnection = new RTCPeerConnection() that.localConnection.addEventListener("icecandidate", that.onIceCandidateA) // 实例化发送通道 that.sendChannel = that.localConnection.createDataChannel('webrtc-datachannel') that.sendChannel.onopen = that.onSendChannelStateChange that.sendChannel.onclose = that.onSendChannelStateChange that.remoteConnection = new RTCPeerConnection(that.configuration) that.remoteConnection.addEventListener("icecandidate", that.onIceCandidateB) // 远端数据到达监听事件 that.remoteConnection.ondatachannel = that.receiveChannelCallBack; // 监听ICE状态变化 that.localConnection.addEventListener("iceconnectionstatechange", that.onIceStateChangeA) that.remoteConnection.addEventListener("iceconnectionstatechange", that.onIceStateChangeB) // 创建通话offer try { console.log("localConnection 创建offer会话开始") const offer = await that.localConnection.createOffer() await that.onCreateOfferSuccess(offer) } catch (e) { console.log("创建会话描述SD失败:", e.toString()) } } , receiveChannelCallBack: function (event) { let that = this; that.receiveChannel = event.channel that.receiveChannel.onmessage = that.onReceiveMessageCallBack; that.receiveChannel.onopen = that.onReceiveChannelStateChange; console.log("Receive channel callback", that.receiveChannel) }, onReceiveChannelStateChange: function () { this.receiveChannelState = this.receiveChannel.readyState console.log("接受通道状态:" + this.receiveChannel.readyState) }, onReceiveMessageCallBack: function (event) { console.log("接受到数据:" + event.data) this.receiveData = event.data }, onSendChannelStateChange: function () { this.sendChannelState = this.sendChannel.readyState console.log("send channel state change", this.sendChannel.readyState) }, // 创建提议offer成功 onCreateOfferSuccess: async function (event) { let that = this // 设置连接描述 console.log("localConnection 创建offer返回得SDP信息", event.sdp) console.log("设置localConnection得本地描述start...") try { await that.localConnection.setLocalDescription(event) console.log("设置localConnection得本地描述成功") } catch (e) { console.log("设置localConnection得本地描述错误:", e.toString()) } console.log("设置remoteConnection得远端描述 start") try { await that.remoteConnection.setRemoteDescription(event) console.log("设置remoteConnection得远端描述成功") } catch (e) { console.log("设置remoteConnection得远端描述错误:", e.toString()) } // 开始应答 console.log("remoteConnection创建应答 answer start") try { const answer = await that.remoteConnection.createAnswer() console.log("remoteConnection创建应答成功") await that.onCreateAnswerSuccess(answer) } catch (e) { console.log("remoteConnection创建应答错误:", e.toString()) } } , // 创建answer应答成功 onCreateAnswerSuccess: async function (answer) { let that = this console.log("remoteConnection创建应答answer数据:", answer) console.log("localConnection与remoteConnection交换应答answer信息 start") try { await that.remoteConnection.setLocalDescription(answer) console.log("设置remoteConnection得本地answer 应答远端描述成功") } catch (e) { console.log("设置remoteConnection得本地answer应答描述错误:", e.toString()) } try { await that.localConnection.setRemoteDescription(answer) console.log("设置localConnection得远端answer应答描述成功") } catch (e) { console.log("设置localConnection得远端answer应答描述错误:", e.toString()) } } , // 监听ICE状态变化事件回调方法 onIceStateChangeA: function (event) { console.log("监听 localConnection ICE状态", this.localConnection.iceConnectionState) console.log(event) } , // 监听ICE状态变化事件回调方法 onIceStateChangeB: async function (event) { console.log("监听 remoteConnection ICE状态", this.remoteConnection.iceConnectionState) console.log(event) } , onIceCandidateA: async function (event) { let that = this try { if (event.candidate) { // 直接交换candidate数据,就不需要通过信令服务器传送 await that.remoteConnection.addIceCandidate(event.candidate) console.log("remoteConnection IceCandidate----------") console.log(event) that.onAddIceCandidateSuccess(that.remoteConnection) } } catch (e) { that.onAddIceCandidateError(that.remoteConnection, e) } console.log("onIceCandidateA data:" + event.candidate) } , onIceCandidateB: async function (event) { let that = this try { if (event.candidate) { await that.localConnection.addIceCandidate(event.candidate) console.log("localConnection IceCandidate----------") console.log(event) that.onAddIceCandidateSuccess(that.localConnection) } } catch (e) { that.onAddIceCandidateError(that.localConnection, e) } console.log("onIceCandidateB data:" + event.candidate) }, // onAddIceCandidateSuccess: function (pc) { console.log("添加" + this.getPcName(pc) + " IceCandidate 成功") }, onAddIceCandidateError: function (pc, err) { console.log("添加" + this.getPcName(pc) + " IceCandidate 失败" + err.toString()) }, getPcName: function (pc) { return (pc === this.localConnection) ? "localConnection" : "remoteConnection" }, } }) </script> </html>
在上述示例中实现了简单的发送和接收的功能。
3、总结
本章介绍了关于WebRtc的数据通道基本知识和使用。本章在通道连接的基础上进行了数据发送的功能。数据通道支持二级制数据,文本消息,文本等数据,对于主打音视频传输的WebRtc来说很实用,这样不需要借助第三方的服务来实现。
数据通道功能和Websocket实现的功能相似,都是具有onmessage 和send方法。但是两者的区别还是有的
1.Websocket需要后台服务做数据中转,比较麻烦,但是造性比较高,基于tcp协议传输,数据更加安全,比较在意数据安全可靠性
2.RTC实现点对点得传输数据更快,使用ICE server穿透nat,相对于数据可靠性来说。有的场景下可能会多一层TURN服务器转发。WebRtc更注重数据得实时性,因为视频或者音频等数据的丢失相对来说是有一定容忍性和误差的。可以通过算法弥补数据丢失。
3.构造Websocket需要一个url和服务器进行连接,创建一个唯一的SocketSessionId。DataChannel的连接依赖与一个RTCPeerConnection对象,当RTCPeerConnection建立以后,可以包含一个和多个RTCDataChannel。
4.Websocket允许浏览器和web服务之间的全双工通信。基于TCP协议
5.WebRtc的PeerConnection允许两个浏览器之间的全双工通信,基于SCTP协议
对点得传输数据更快,使用ICE server穿透nat,相对于数据可靠性来说。有的场景下可能会多一层TURN服务器转发。WebRtc更注重数据得实时性,因为视频或者音频等数据的丢失相对来说是有一定容忍性和误差的。可以通过算法弥补数据丢失。
原文链接:webrtc 入门第四章 数据通道
★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。
见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓