【问题标题】:Can't use WebRTC DataChannels with Chrome when Firefox initiates the connectionFirefox 启动连接时,无法将 WebRTC DataChannels 与 Chrome 一起使用
【发布时间】:2014-02-07 19:06:09
【问题描述】:

我正在尝试使用 WebRTC DataChannels 创建一个简单的网页,该网页在浏览器之间发送 pings/pongs。

Chrome 启动连接,然后 Chrome 连接时,它工作
Firefox 启动连接,然后 Firefox 连接时,它工作
Chrome 启动连接,然后 Firefox 连接时,它工作
但是当 Firefox 启动连接然后 Chrome 连接时,它不起作用。 Chrome 从不接收 Firefox 发送的数据。

我在 Archlinux 上使用 Firefox 26 和 Chromium 32。

这是我的 JavaScript 代码:

<!DOCTYPE html>
<html>
<head>
    <title>WebRTC test</title>
    <meta charset="utf-8">
</head>
<body>
    <button id="create" disabled>Create data channel</button>

    <script type="text/javascript">
        // DOM

        var create = document.getElementById('create');

        // Compatibility

        window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
        window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
        window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;

        // Create a WebRTC object

        var rtc = new RTCPeerConnection(null);

        // Create a data channel

        var sendChannel = rtc.createDataChannel('pingtest', {reliable: false});
        var myMsg = 'ping';

        function setRecvChannel(recvChannel) {
            recvChannel.onmessage = function(event) {
                if(event.data.indexOf('\x03\x00\x00\x00\x00\x00\x00\x00\x00') === 0) {
                    console.log('-> ' + window.btoa(event.data));
                    return; // Received channel's name, ignore
                }

                console.log('-> ' + event.data);
                window.setTimeout(function() {
                    console.log('<- ' + myMsg);
                    sendChannel.send(myMsg);
                }, 500);
            };
        }

        // Chrome and Firefox

        sendChannel.onopen = function(event) {
            setRecvChannel(sendChannel);

            if(myMsg === 'ping') {
                console.log('<- ' + myMsg);
                sendChannel.send(myMsg);
            }
        };

        // Firefox

        rtc.ondatachannel = function(event) {
            setRecvChannel(event.channel);
        };

        // ICE

        rtc.onicecandidate = function(event) {
            if(event.candidate) {
                console.log('<- ' + JSON.stringify(event.candidate));
                ws.send(JSON.stringify(event.candidate));
            }
        };

        // Signaling channel

        var ws = new WebSocket('ws://127.0.0.1:49300/');

        ws.onopen = function() {
            create.disabled = false;
        };

        ws.onmessage = function(event) {
            console.log('-> ' + event.data);
            var data = JSON.parse(event.data);

            if(data.sdp) {
                rtc.setRemoteDescription(new RTCSessionDescription(data));

                if(data.type === 'offer') {
                    myMsg = 'pong';

                    rtc.createAnswer(function(anwser) {
                        rtc.setLocalDescription(anwser, function () {
                            console.log('<- ' + JSON.stringify(anwser));
                            ws.send(JSON.stringify(anwser));
                        });
                    }, console.error);
                }
            }
            else {
                rtc.addIceCandidate(new RTCIceCandidate(data));
            }
        };

        ws.onclose = function() {
            create.disabled = true;
        };

        // Create an offer

        create.onclick = function() {
            rtc.createOffer(function(offer) {
                rtc.setLocalDescription(offer, function () {
                    offer.sdp = offer.sdp;
                    console.log(offer.sdp);
                    console.log('<- ' + JSON.stringify(offer));
                    ws.send(JSON.stringify(offer));
                });
            }, console.error);
        };
    </script>
</body>
</html>

这是我创建的基于 WebSocket 的信令服务器,仅用于测试目的,它只是侦听端口 49300 并将从客户端接收到的数据广播到其他客户端:

#!/usr/bin/python
#-*- encoding: Utf-8 -*-
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
from string import printable
from threading import Thread
from base64 import b64encode
from struct import unpack
from hashlib import sha1

PORT = 49300

activeSocks = []

def SignalingChannel(ip, port, sock):
    print 'Connection from %s:%s' % (ip, port)

    # Handling the HTTP request

    try:
        headers = sock.recv(8184)

        assert headers.upper().startswith('GET')
        assert headers.endswith('\r\n\r\n')

        data = headers.strip().replace('\r', '').split('\n')[1:]

        headers = {}
        for header in data:
            name, value = header.split(':', 1)
            headers[name.strip().lower()] = value.strip()

        assert headers['host']
        assert 'upgrade' in headers['connection'].lower()
        assert 'websocket' in headers['upgrade'].lower()
        assert headers['sec-websocket-version'] == '13'
        assert len(headers['sec-websocket-key']) == 24

        guid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
        accept = b64encode(sha1(headers['sec-websocket-key'] + guid).digest())

        sock.send('HTTP/1.1 101 Switching Protocols\r\n' +
        'Connection: Upgrade\r\n' +
        'Upgrade: websocket\r\n' +
        'Sec-WebSocket-Accept: %s\r\n' % accept +
        '\r\n')

    except:
        try:
            msg = 'This is a RFC 6455 WebSocket server.\n'

            sock.send('HTTP/1.1 400 Bad Request\r\n' +
            'Connection: Close\r\n' +
            'Content-Length: %d\r\n' % len(msg) +
            'Content-Type: text/plain; charset=us-ascii\r\n' +
            'Sec-WebSocket-Version: 13\r\n' +
            '\r\n' +
            msg)

        except:
            pass

        sock.close()
        print 'Disconnection from %s:%s' % (ip, port)
        return

    activeSocks.append(sock)

    try:
        data = sock.recv(2)
        while len(data) == 2:
            frame = data[0] + chr(ord(data[1]) & 0b01111111)
            opcode = ord(data[0]) & 0b00001111
            mask = ord(data[1]) & 0b10000000
            paylen = ord(data[1]) & 0b01111111

            if paylen == 126:
                data = sock.recv(2)
                frame += data
                paylen = unpack('>H', data)[0]
            elif paylen == 127:
                data = sock.recv(8)
                frame += data
                paylen = unpack('>Q', data)[0]

            if mask:
                mask = sock.recv(4)

            data = ''
            received = True
            while received and len(data) < paylen:
                received = sock.recv(paylen - len(data))
                data += received

            if mask:
                unmasked = ''
                for i in xrange(len(data)):
                    unmasked += chr(ord(data[i]) ^ ord(mask[i % 4]))
            else:
                unmasked = data

            frame += unmasked

            if opcode != 8:
                print '-- From port %d --' % port
                if all(ord(c) < 127 and c in printable for c in unmasked):
                    print unmasked
                else:
                    print repr(unmasked)
                for destSock in activeSocks:
                    if destSock != sock:
                        destSock.send(frame)
            else:
                break

            data = sock.recv(2)
    except:
        pass

    activeSocks.remove(sock)
    sock.close()
    print 'Disconnection from %s:%s' % (ip, port)

listenSock = socket(AF_INET, SOCK_STREAM)
listenSock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

listenSock.bind(('0.0.0.0', PORT))
listenSock.listen(20)

print 'Listening on port 49300...'

while True:
    clientSock, (ip, port) = listenSock.accept()
    Thread(target=SignalingChannel, args=(ip, port, clientSock)).start()

要运行代码,启动信号服务器,在两个浏览器选项卡中打开网页,单击“创建数据通道”按钮并查看 Web 控制台。

有什么想法吗?

【问题讨论】:

    标签: javascript html google-chrome interop webrtc


    【解决方案1】:

    查看 Chrome/Firefox 错误跟踪器,似乎已发现并解决了此问题,但仅限于 Chrome Canary 33.0.1715.0 或更高版本。

    如果您不愿意要求上述 Chrome Canary 版本,您可以检测到错误的对等组合,并让您的“报价”按钮向其他客户端发出报价信号。

    伪代码:

    socket.onMessage(msg) {
       if(msg == "request-offer"){
          doOffer();
       }
       ...
    }
    
    createDataChannelButton.onClick() {
       if(!canCreateChannelBasedOnBrowser){
          socket.send("request-offer");
       }
       else {
          doOffer();
       }
    }
    

    使用您的示例代码:

    <!DOCTYPE html>
    <html>
    <head>
       <title>WebRTC test</title>
       <meta charset="utf-8">
    </head>
    <body>
    <button id="create" disabled>Create data channel</button>
    
    <script type="text/javascript">
       // DOM
    
       // CHANGE: Add basic browser detection based on google's adapter.js file.
       var rtcBrowserVersion = 0;
       var rtcCanInitiateDataOffer = false;
       if (navigator.mozGetUserMedia) {
          rtcBrowserVersion = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
          rtcCanInitiateDataOffer = true;
       } else if (navigator.webkitGetUserMedia) {
          // Chrome Canary reports major version 35 for me.  Can't find a reliable resource to confirm
          // canary versions.
          rtcBrowserVersion = parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
          rtcCanInitiateDataOffer = rtcBrowserVersion >= 35;
       }
    
    
       var create = document.getElementById('create');
    
       // Compatibility
    
       window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
       window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
       window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
    
       // Create a WebRTC object
    
       var rtc = new RTCPeerConnection(null);
    
       // Create a data channel
    
       var sendChannel = rtc.createDataChannel('pingtest', {reliable: false});
       var myMsg = 'ping';
    
       function setRecvChannel(recvChannel) {
          recvChannel.onmessage = function(event) {
             if(event.data.indexOf('\x03\x00\x00\x00\x00\x00\x00\x00\x00') === 0) {
                console.log('-> ' + window.btoa(event.data));
                return; // Received channel's name, ignore
            }
    
            console.log('-> ' + event.data);
            window.setTimeout(function() {
               console.log('<- ' + myMsg);
               sendChannel.send(myMsg);
            }, 500);
         };
      }
    
      // Chrome and Firefox
    
      sendChannel.onopen = function(event) {
         setRecvChannel(sendChannel);
    
         if(myMsg === 'ping') {
            console.log('<- ' + myMsg);
            sendChannel.send(myMsg);
         }
      };
    
      // Firefox
    
      rtc.ondatachannel = function(event) {
         setRecvChannel(event.channel);
      };
    
      // ICE
    
      rtc.onicecandidate = function(event) {
         if(event.candidate) {
            console.log('<- ' + JSON.stringify(event.candidate));
            ws.send(JSON.stringify(event.candidate));
         }
      };
    
      // Signaling channel
    
      var ws = new WebSocket('ws://127.0.0.1:49300/');
    
      ws.onopen = function() {
         create.disabled = false;
      };
    
      ws.onmessage = function(event) {
         console.log('-> ' + event.data);
         var data = JSON.parse(event.data);
    
        if(data.sdp) {
           rtc.setRemoteDescription(new RTCSessionDescription(data));
    
           if(data.type === 'offer') {
              myMsg = 'pong';
    
              rtc.createAnswer(function(anwser) {
                 rtc.setLocalDescription(anwser, function () {
                    console.log('<- ' + JSON.stringify(anwser));
                    ws.send(JSON.stringify(anwser));
                 });
              }, console.error);
           }
        }
        // CHANGE: Chrome with offer bug asked to initiate the offer.
        else if(data.initiate === true){
           doOffer();
        }
        else {
           rtc.addIceCandidate(new RTCIceCandidate(data));
        }
     };
    
     ws.onclose = function() {
       create.disabled = true;
    };
    
    // Create an offer
    
    // CHANGE: Create function for offer, so that it may be called from ws.onmessage
    function doOffer(){
       rtc.createOffer(function(offer) {
          rtc.setLocalDescription(offer, function () {
             offer.sdp = offer.sdp;
             console.log(offer.sdp);
             console.log('<- ' + JSON.stringify(offer));
             ws.send(JSON.stringify(offer));
          });
       }, console.error);
    }
    
    create.onclick = function() {
       // CHANGE: If this client is not able to negotiate a data channel, send a
       // message to the peer asking them to offer the channel.
       if(rtcCanInitiateDataOffer){
          doOffer();
       }
       else {
          ws.send(JSON.stringify({initiate:true}));
       }
    };
    

    相关问题:

    还值得注意的是,当前有一个max message buffer size of 16KB 向 Chrome 对等体发送数据。如果您要发送大数据消息,则需要将它们分成 16KB 的块,然后再通过数据通道传输。

    【讨论】:

    • 谢谢,但这并不能解决问题。在 Firefox 和 Chrome 中建立 DTLS/SRTP 连接不再需要此参数,现在它看起来像是发生在 DTLS 套接字内部的协议互操作性问题。
    • 我使用你的测试代码重现了这个问题,并根据我的发现更新了我的答案。
    • @Marin 我发布了有关您遇到的问题的详细信息以及解决方案。您能否确认这是否解决了您的问题?
    • 确实,升级 Chrome 解决了这个问题,现在修复已经登陆稳定频道(版本 33.0.1750.146)。我还必须替换“sendChannel.send(myMsg);”通过“recvChannel.send(myMsg);”在 setRecvChannel 中行在 Chrome 启动连接时使示例工作。
    【解决方案2】:

    仅供参考,我进一步修改了 HTML 以尝试解决我与 C++ libjingle(Chrome 和 Firefox 都不是)交谈时遇到的问题;)

    此版本显示页面状态。不漂亮,但很有用。

      <!DOCTYPE html>
      <html>
      <head>
          <title>WebRTC test</title>
          <meta charset="utf-8">
      </head>
      <body>
          <button id="create" disabled>Create data channel</button>
    
      <div id="output1">output1</div>
      <div id="output2">output2</div>
      <div id="output3">output3</div>
      <div id="output4">output4</div>
      <div id="output5">output5</div>
      <div id="output6">output6</div>
      <div id="output7">output7</div>
      <div id="output8">output8</div>
    
    
      <script type="text/javascript">
         var myCounts = {
             output1: 0,
             output2: 0
         };
    
         var server1 = 'ws://127.0.0.1:49300/';
    
         // CHANGE: Add basic browser detection based on google's adapter.js file.
         var rtcBrowserVersion = 0;
         var rtcCanInitiateDataOffer = false;
         if (navigator.mozGetUserMedia) {
            rtcBrowserVersion = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
            rtcCanInitiateDataOffer = true;
         } else if (navigator.webkitGetUserMedia) {
            // Chrome Canary reports major version 35 for me.  Can't find a reliable resource to confirm
            // canary versions.
            rtcBrowserVersion = parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
            rtcCanInitiateDataOffer = rtcBrowserVersion >= 35;
         }
    
         // DOM
    
         var create = document.getElementById('create');
    
         // Compatibility
    
         window.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
         window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription;
         window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.webkitRTCIceCandidate;
    
         // Create a WebRTC object
    
         var rtc = new RTCPeerConnection(null);
    
         // Create a data channel
    
         var sendChannel = rtc.createDataChannel('PingTest', {reliable: false});
         var myMsg = 'ping';
    
         function setRecvChannel(recvChannel) {
            recvChannel.onmessage = function(event) {
               myCounts.output1++;
               document.getElementById("output1").innerHTML = myCounts.output1 + ": " + event.data;
    
               if(event.data.indexOf('\x03\x00\x00\x00\x00\x00\x00\x00\x00') === 0) {
                  console.log('-> ' + window.btoa(event.data));
                  return; // Received channel's name, ignore
               };
    
               console.log('-> ' + event.data);
               window.setTimeout(function() {
                   console.log('<- ' + myMsg);
                   sendChannel.send(myMsg);
                 }, 500);
            };
         }
    
         // Chrome and Firefox
    
         sendChannel.onopen = function(event) {
            setRecvChannel(sendChannel);
    
            if(myMsg === 'ping') {
               console.log('<- ' + myMsg);
               sendChannel.send(myMsg);
            }
         };
    
         // Firefox
    
         rtc.ondatachannel = function(event) {
             myCounts.output2++;
             document.getElementById("output2").innerHTML = myCounts.output2 + " channel: " + event.channel.label;
             setRecvChannel(event.channel);
         };
    
         // ICE
    
         rtc.onicecandidate = function(event) {
            if(event.candidate) {
               console.log('<- ' + JSON.stringify(event.candidate));
               ws.send(JSON.stringify(event.candidate));
            }
         };
    
         // Signaling channel
    
         var ws = new WebSocket(server1);
         document.getElementById("output3").innerHTML="created WebSocket (not connected) " + server1;
    
         ws.onopen = function() {
            create.disabled = false;
            document.getElementById("output3").innerHTML="onOpen WebSocket " + server1;
         };
    
         ws.onmessage = function(event) {
            document.getElementById("output3").innerHTML="onMessage WebSocket " + event.data;
            console.log('-> ' + event.data);
            var data = JSON.parse(event.data);
    
            if (data.sdp) {
               rtc.setRemoteDescription(new RTCSessionDescription(data));
    
               if (data.type === 'offer') {
                   document.getElementById("output4").innerHTML="received SessiionDescription offer";
                   myMsg = 'pong';
    
                  rtc.createAnswer(function(anwser) {
                    rtc.setLocalDescription(anwser, function () {
                        console.log('<- ' + JSON.stringify(anwser));
                        ws.send(JSON.stringify(anwser));
                    });
                  }, console.error);
               }
               else if (data.type == 'answer') {
                   document.getElementById("output6").innerHTML="received SessiionDescription answer";
               };
            }
            // CHANGE: Chrome with offer bug asked to initiate the offer.
            else if (data.reverseInitiate === true) {
               document.getElementById("output8").innerHTML="was asked to reverseInitiate, I doOffer";
               doOffer();
            }
            else {
               rtc.addIceCandidate(new RTCIceCandidate(data));
            }
         };
    
         ws.onclose = function() {
             create.disabled = true;
         };
    
         // Create an offer
    
         // CHANGE: Create function for offer, so that it may be called from ws.onmessage
         function doOffer(){
            rtc.createOffer(function(offer) {
               rtc.setLocalDescription(offer, function () {
                  offer.sdp = offer.sdp;
                  console.log(offer.sdp);
                  console.log('<- ' + JSON.stringify(offer));
                  ws.send(JSON.stringify(offer));
               });
            }, console.error);
         }
    
         create.onclick = function() {
            // CHANGE: If this client is not able to negotiate a data channel, send a
            // message to the peer asking them to offer the channel.
            if (rtcCanInitiateDataOffer){
               doOffer();
            }
            else {
               document.getElementById("output7").innerHTML="sending reverseInitiate";
               ws.send(JSON.stringify( {reverseInitiate:true} ));
            };
         };
      </script>
    
      </body>
      </html>
    

    【讨论】:

      猜你喜欢
      • 2018-05-07
      • 2014-10-08
      • 1970-01-01
      • 2017-04-26
      • 2023-04-10
      • 2016-11-03
      • 1970-01-01
      • 2013-01-01
      • 1970-01-01
      相关资源
      最近更新 更多