webrtc 入门第四章 数据通道
一、介绍
在webrtc再实现了端到端的连接过程中除了传输媒体流以外,还可以传输文字,文件,图片等数据,再IM的场景中同样实用,并且不需要第三方消息服务如websocket,透传消息等。
数据通道的创建依赖RTCPeerConnection 的连接,其连接流程在第三章可以查看,当两端建立了连接后,就可以进行发送数据。
二、实践
1、数据对象 RTCDataChannel
that.localConnection.createDataChannel('webrtc-datachannel')
通过RTCPeerConnection 连接可以创建一个RTCDataChannel对象,创建时需要传入通道名称
方法名称 | 参数 | 说明 | 类型 |
pc.createDataChannel() | 通道名称 | 该方法创建一个发送任何数据的通道 | 方法 |
pc.ondatachannel() | 为RTCDataChannelEvent事件,返回的参数为接收端数据通道 event.channel为通道 | 事件 | |
RTCDataChannel | 两个RTCPeerConnection 连接之间的双向通道 | 对象 | |
RTCDataChannel.send() | data发送的数据 | 发送数据方法,数据参数可以时string,byte,ArrayBuffer,Blob等 | 方法 |
RTCDataChannel.close() | 关闭通道 | 方法 | |
RTCDataChannel.onmessage() | 通道接收到数据后的回调函数,event.data为收到的数据 | 事件 |
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
},
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>
在上述示例中实现了简单的发送和接收的功能。
三、总结
本章介绍了关于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更注重数据得实时性,因为视频或者音频等数据的丢失相对来说是有一定容忍性和误差的。可以通过算法弥补数据丢失。
3.构造Websocket需要一个url和服务器进行连接,创建一个唯一的SocketSessionId。DataChannel的连接依赖与一个RTCPeerConnection对象,当RTCPeerConnection建立以后,可以包含一个和多个RTCDataChannel。
4.Websocket允许浏览器和web服务之间的全双工通信。基于TCP协议