天天看點

webrtc 入門第四章 資料通道

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的資料通道基本知識和使用。本章在通道連接配接的基礎上進行了資料發送的功能。資料通道支援二級制資料,文本消息,文本等資料,對于主打音視訊傳輸的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協定

繼續閱讀