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協定