使用信令实现的WebRTC数据通道示例——工作中的Hello World

11

本意是将此内容成为一个社区Wiki帖子,以便开发人员能够获取简单而实用的示例,实现浏览器之间(p2p)JSON消息通信,同时保持最新状态。

WebRTC DataChannels是实验性技术,并且仍处于草案阶段。目前看来,网络上存在大量过时的WebRTC示例,尤其是对于试图学习RTCDataChannel API的开发者而言,情况更加复杂。

简单而功能齐全的1页示例,可在支持WebRTC的兼容浏览器上运行,似乎很难找到。例如,一些示例省略了信令实现,另一些仅适用于单个浏览器(如Chrome-Chrome),许多由于API最近的更改已过时,其他则过于复杂,从而创建了一个启动障碍。
请发布符合以下条件的示例(如果未满足某些条件,请指明):
  1. 客户端代码是单页的(200行或更少)
  2. 服务器端代码是单页的,技术被引用(例如:node.js, php, python等)
  3. 信令机制已实现,协议技术被引用(例如:WebSockets, 长轮询, GCM等)
  4. 可跨浏览器运行的工作代码(Chrome、Firefox、Opera和/或Bowser
  5. 最小化选项、错误处理、抽象等--意图是提供一个基础示例
1个回答

6

这里有一个使用HTML5 WebSockets进行信令和node.js后端的工作示例

信令技术:WebSockets
客户端:纯html/javascript
服务器:node.jsws
最近测试于:Firefox 40.0.2Chrome 44.0.2403.157 mOpera 31.0.1889.174


客户端代码:
<html>
<head>
</head>
<body>
    <p id='msg'>Click the following in different browser windows</p>
    <button type='button' onclick='init(false)'>I AM Answerer Peer (click first)</button>
    <button type='button' onclick='init(true)'>I AM Offerer Peer</button>

<script>
    (function() {   
        var offererId = 'Gandalf',   // note: client id conflicts can happen
            answererId = 'Saruman',  //       no websocket cleanup code exists
            ourId, peerId,
            RTC_IS_MOZILLA = !!window.mozRTCPeerConnection,
            RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection,
            RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.msRTCSessionDescription,
            RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.msRTCIceCandidate,
            rtcpeerconn = new RTCPeerConnection(
                    {iceServers: [{ 'url': 'stun:stun.services.mozilla.com'}, {'url': 'stun:stun.l.google.com:19302'}]}, 
                    {optional: [{RtpDataChannels: false}]}
                ),
            rtcdatachannel, 
            websocket = new WebSocket('ws://' + window.location.hostname + ':8000'),
            comready, onerror;

        window.init = function(weAreOfferer) {
            ourId = weAreOfferer ? offererId : answererId;
            peerId = weAreOfferer ? answererId : offererId;

            websocket.send(JSON.stringify({
                inst: 'init', 
                id: ourId
            }));

            if(weAreOfferer) {

                rtcdatachannel = rtcpeerconn.createDataChannel(offererId+answererId);
                rtcdatachannel.onopen = comready;
                rtcdatachannel.onerror = onerror;

                rtcpeerconn.createOffer(function(offer) {
                    rtcpeerconn.setLocalDescription(offer, function() {
                        var output = offer.toJSON();
                        if(typeof output === 'string') output = JSON.parse(output); // normalize: RTCSessionDescription.toJSON returns a json str in FF, but json obj in Chrome

                        websocket.send(JSON.stringify({
                            inst: 'send', 
                            peerId: peerId, 
                            message: output
                        }));
                    }, onerror);
                }, onerror);
            }
        };

        rtcpeerconn.ondatachannel = function(event) {
            rtcdatachannel = event.channel;
            rtcdatachannel.onopen = comready;
            rtcdatachannel.onerror = onerror;
        };

        websocket.onmessage = function(input) {
            var message = JSON.parse(input.data);

            if(message.type && message.type === 'offer') {
                var offer = new RTCSessionDescription(message);

                rtcpeerconn.setRemoteDescription(offer, function() {
                    rtcpeerconn.createAnswer(function(answer) {
                        rtcpeerconn.setLocalDescription(answer, function() {
                            var output = answer.toJSON();
                            if(typeof output === 'string') output = JSON.parse(output); // normalize: RTCSessionDescription.toJSON returns a json str in FF, but json obj in Chrome

                            websocket.send(JSON.stringify({
                                inst: 'send',
                                peerId: peerId,
                                message: output
                            }));
                        }, onerror);
                    }, onerror);                
                }, onerror);
            } else if(message.type && message.type === 'answer') {              
                var answer = new RTCSessionDescription(message);
                rtcpeerconn.setRemoteDescription(answer, function() {/* handler required but we have nothing to do */}, onerror);
            } else if(rtcpeerconn.remoteDescription) {
                // ignore ice candidates until remote description is set
                rtcpeerconn.addIceCandidate(new RTCIceCandidate(message.candidate));
            }
        };

        rtcpeerconn.onicecandidate = function (event) {
            if (!event || !event.candidate) return;
            websocket.send(JSON.stringify({
                inst: 'send',
                peerId: peerId,
                message: {candidate: event.candidate}
            }));
        };

        /** called when RTC signaling is complete and RTCDataChannel is ready */
        comready = function() {
            rtcdatachannel.send('hello world!');
            rtcdatachannel.onmessage = function(event) {
                document.getElementById('msg').innerHTML = 'RTCDataChannel peer ' + peerId + ' says: ' + event.data;    
            }
        };

        /** global error function */
        onerror = websocket.onerror = function(e) {
            console.log('====== WEBRTC ERROR ======', arguments);
            document.getElementById('msg').innerHTML = '====== WEBRTC ERROR ======<br>' + e;
            throw new Error(e);
        };
    })();
</script>
</body>
</html>

服务器端代码:

var server = require('http').createServer(), 
    express = require('express'),    
    app = express(),
    WebSocketServer = require('ws').Server,
    wss = new WebSocketServer({ server: server, port: 8000 });

app.use(express.static(__dirname + '/static')); // client code goes in static directory

var clientMap = {};

wss.on('connection', function (ws) {
    ws.on('message', function (inputStr) {
        var input = JSON.parse(inputStr);
        if(input.inst == 'init') {
            clientMap[input.id] = ws;
        } else if(input.inst == 'send') {
            clientMap[input.peerId].send(JSON.stringify(input.message));
        }
    });
});

server.on('request', app);
server.listen(80, YOUR_HOSTNAME_OR_IP_HERE, function () { console.log('Listening on ' + server.address().port) });

获取错误WebSocket已处于CLOSING或CLOSED状态。 - Wajihurrehman
在这行代码中,为什么会出现“WebSocket已处于CLOSING或CLOSED状态”的错误,请问您能告诉我原因吗?我是WebSocket的新手,使用wrtc创建了两个文件,一个用于服务器,另一个用于客户端的HTML文件,并在两个不同的浏览器中打开。 - Wajihurrehman
1
阅读了您的回答后,我比阅读规范和大量标题更好地理解了WebRTC,谢谢<3。 - Иван Никулин

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接