天天看点

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协议

继续阅读