本文介绍了在没有TURN服务器的情况下,如何使用WebRTC解决NAT?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试制作一种可以在移动浏览器上玩的对等Javascript游戏.

I'm trying to make a peer to peer Javascript game that can be played on mobile browsers.

  • 我已经能够在本地WiFi网络中的两部电话之间成功建立p2p连接.
  • 我无法通过移动网络连接两部电话,或者无法通过WiFi连接一部电话,而无法通过移动网络连接一部电话.
  • 我尝试关闭Windows防火墙,但无法将PC连接到移动网络上的手机.
  • 我尝试让两个对等方建立自己的数据通道并进行协商.

我已经读到80%至90%的设备能够通过WebRTC进行连接而无需TURN服务器,因此我完全不知道下一步该怎么做.

I've read that 80% to 90% of devices are able to connect over WebRTC without TURN servers so I am at a complete loss of what to do next.

桌面:Google Chrome 79.0.3945.130(正式版本)(64位)(同类:稳定)

Desktop: Google Chrome 79.0.3945.130 (Official Build) (64-bit) (cohort: Stable)

移动版(Pixel 3/Android 10):Google Chrome 79.0.3945.116

Mobile (Pixel 3/Android 10): Google Chrome 79.0.3945.116

移动网络

Time    Event
1/24/2020, 11:58:17 PM  createLocalDataChannel
label: Test, reliable: true
1/24/2020, 11:58:17 PM  negotiationneeded
1/24/2020, 11:58:17 PM  createOffer
1/24/2020, 11:58:17 PM  createOfferOnSuccess
1/24/2020, 11:58:17 PM  setLocalDescription
1/24/2020, 11:58:17 PM  signalingstatechange
1/24/2020, 11:58:17 PM  setLocalDescriptionOnSuccess
1/24/2020, 11:58:17 PM  icegatheringstatechange
1/24/2020, 11:58:17 PM  icecandidate (host)
1/24/2020, 11:58:17 PM  icecandidate (srflx)
1/24/2020, 11:58:17 PM  setRemoteDescription
1/24/2020, 11:58:17 PM  addIceCandidate (host)
1/24/2020, 11:58:17 PM  signalingstatechange
1/24/2020, 11:58:17 PM  setRemoteDescriptionOnSuccess
1/24/2020, 11:58:17 PM  iceconnectionstatechange
1/24/2020, 11:58:17 PM  iceconnectionstatechange (legacy)
1/24/2020, 11:58:17 PM  connectionstatechange
1/24/2020, 11:58:18 PM  addIceCandidate (srflx)
1/24/2020, 11:58:33 PM  iceconnectionstatechange
disconnected
1/24/2020, 11:58:33 PM  iceconnectionstatechange (legacy)
failed
1/24/2020, 11:58:33 PM  connectionstatechange
failed

WIFI网络

Time    Event
1/25/2020, 12:02:45 AM
createLocalDataChannel
label: Test, reliable: true
1/25/2020, 12:02:45 AM  negotiationneeded
1/25/2020, 12:02:45 AM  createOffer
1/25/2020, 12:02:45 AM  createOfferOnSuccess
1/25/2020, 12:02:45 AM  setLocalDescription
1/25/2020, 12:02:45 AM  signalingstatechange
1/25/2020, 12:02:45 AM  setLocalDescriptionOnSuccess
1/25/2020, 12:02:45 AM  icegatheringstatechange
1/25/2020, 12:02:45 AM  icecandidate (host)
1/25/2020, 12:02:45 AM  icecandidate (srflx)
1/25/2020, 12:02:46 AM  setRemoteDescription
1/25/2020, 12:02:46 AM  signalingstatechange
1/25/2020, 12:02:46 AM  setRemoteDescriptionOnSuccess
1/25/2020, 12:02:46 AM  icegatheringstatechange
1/25/2020, 12:02:46 AM  addIceCandidate (host)
1/25/2020, 12:02:46 AM  iceconnectionstatechange
1/25/2020, 12:02:46 AM  iceconnectionstatechange (legacy)
1/25/2020, 12:02:46 AM  connectionstatechange
1/25/2020, 12:02:46 AM  addIceCandidate (srflx)
1/25/2020, 12:02:46 AM  iceconnectionstatechange
connected
1/25/2020, 12:02:46 AM  iceconnectionstatechange (legacy)
connected
1/25/2020, 12:02:46 AM  connectionstatechange
connected
1/25/2020, 12:02:46 AM  iceconnectionstatechange (legacy)
completed

对等代码

"use strict";

import { isAssetLoadingComplete } from '/game/assetManager.js';
import { playerInputHandler } from '/game/game.js';

const rtcPeerConnectionConfiguration = {
    // Server for negotiating traversing NATs when establishing peer-to-peer communication sessions
    iceServers: [{
        urls: [
            'stun:stun.l.google.com:19302'
        ]
    }]
};

let rtcPeerConn;
// For UDP semantics, set maxRetransmits to 0 and ordered to false
const dataChannelOptions = {
    // TODO: Set this to a unique number returned from joinRoomResponse
    //id: 1,
    // json for JSON and raw for binary
    protocol: "json",
    // If true both peers can call createDataChannel as long as they use the same ID
    negotiated: false,
    // TODO: Set to false so the messages are faster and less reliable
    ordered: true,
    // If maxRetransmits and maxPacketLifeTime aren't set then reliable mode will be on
    // TODO: Send multiple frames of player input every frame to avoid late/missing frames
    //maxRetransmits: 0,
    // The maximum number of milliseconds that attempts to transfer a message may take in unreliable mode.
    //maxPacketLifeTime: 30000
};

let dataChannel;

export let isConnectedToPeers = false;

export function createDataChannel(roomName, socket) {
    rtcPeerConn = new RTCPeerConnection(rtcPeerConnectionConfiguration);
    // Send any ice candidates to the other peer
    rtcPeerConn.onicecandidate = onIceCandidate(socket);
    // Let the 'negotiationneeded' event trigger offer generation
    rtcPeerConn.onnegotiationneeded = function () {
        console.log("Creating an offer")
        rtcPeerConn.createOffer(sendLocalDesc(socket), logError('createOffer'));
    };
    console.log("Creating a data channel");
    dataChannel = rtcPeerConn.createDataChannel(roomName, dataChannelOptions);
    dataChannel.onopen = dataChannelStateOpen;
    dataChannel.onmessage = receiveDataChannelMessage;
    dataChannel.onerror = logError('createAnswer');
    dataChannel.onclose = function(TODO) {
        console.log(`Data channel closed for scoket: ${socket}`, TODO)
    };
}

export function joinDataChannel(socket) {
    console.log("Joining a data channel");
    rtcPeerConn = new RTCPeerConnection(rtcPeerConnectionConfiguration);
    rtcPeerConn.ondatachannel = receiveDataChannel;
    // Send any ice candidates to the other peer
    rtcPeerConn.onicecandidate = onIceCandidate(socket);
}

function receiveDataChannel(rtcDataChannelEvent) {
    console.log("Receiving a data channel", rtcDataChannelEvent);
    dataChannel = rtcDataChannelEvent.channel;
    dataChannel.onopen = dataChannelStateOpen;
    dataChannel.onmessage = receiveDataChannelMessage;
    dataChannel.onerror = logError('createAnswer');
    dataChannel.onclose = function(TODO) {
        console.log(`Data channel closed for scoket: ${socket}`, TODO)
    };
}

function onIceCandidate(socket) {
    return function (event) {
        if (event.candidate) {
            console.log("Sending ice candidates to peer.");
            socket.emit('signalRequest', {
                signal: event.candidate
            });
        }
    }
}

function dataChannelStateOpen(event) {
    console.log("Data channel opened", event);
    isConnectedToPeers = true;

    if(!isAssetLoadingComplete) {
        document.getElementById("startGameButton").textContent = "Loading...";
    }
    else {
        document.getElementById('startGameButton').removeAttribute('disabled');
        document.getElementById("startGameButton").textContent = "Start Game";
    }
}

function receiveDataChannelMessage(messageEvent) {
    switch(dataChannel.protocol) {
        case "json":
            const data = JSON.parse(messageEvent.data)
            playerInputHandler(data);
            break;
        case "raw":
            break;
      }
}

export function signalHandler(socket) {
    return function (signal) {
        if (signal.sdp) {
            console.log("Setting remote description", signal);
            rtcPeerConn.setRemoteDescription(
                signal,
                function () {
                    // If we received an offer, we need to answer
                    if (rtcPeerConn.remoteDescription.type === 'offer') {
                        console.log("Offer received, sending answer")
                        rtcPeerConn.createAnswer(sendLocalDesc(socket), logError('createAnswer'));
                    }
                },
                logError('setRemoteDescription'));
        }
        else if (signal.candidate){
            console.log("Adding ice candidate ", signal)
            rtcPeerConn.addIceCandidate(new RTCIceCandidate(signal));
        }
    }
}

function sendLocalDesc(socket) {
    return function(description) {
        rtcPeerConn.setLocalDescription(
            description,
            function () {
                console.log("Setting local description", description);
                socket.emit('signalRequest', {
                    playerNumber: socket.id,
                    signal: description
                });
            },
            logError('setLocalDescription'));
    };
}

export function sendPlayerInput(playerInput){
    dataChannel.send(JSON.stringify(playerInput));
}

function logError(caller) {
    return function(error) {
        console.log('[' + caller + '] [' + error.name + '] ' + error.message);
    }
}

推荐答案

此处可能有一些不同的因素在起作用.

There are a few different factors that could at at play here.

  • 双方的NAT类型
  • IP系列(IPv4或IPv6)
  • 协议(完全允许UDP吗?)

我将确定您背后的NAT类型,您可以在这里进一步了解 https://webrtchacks.com/symmetric-nat .如果两个网络都位于对称NAT之后,则将需要TURN服务器.

I would determine what NAT type you are behind on each side, you can read more about that here https://webrtchacks.com/symmetric-nat. If both networks are behind symmetric NATs you are going to need a TURN server.

如果您没有浏览器,也可以使用 Pion TURN Go TURN客户端和服务器.

If you don't have a browser you could also use Pion TURN a Go TURN client and server.

在收集候选者时,我还将检查IPv4/IPv6上是否有交集.一些电话提供商仅提供IPv6.

I would also check when gathering candidates if there is an intersection on IPv4/IPv6. Some phone providers are only giving out IPv6.

可能根本不允许UDP.这并不常见,但有可能.在这种情况下,您将不得不使用TURN.可以通过TCP进行NAT遍历,但WebRTC AFAIK不支持.

UDP may not be allowed at all. This isn't common, but possible. In this case you will be forced to use TURN. NAT traversal via TCP is possible, but not supported by WebRTC AFAIK.

这篇关于在没有TURN服务器的情况下,如何使用WebRTC解决NAT?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-15 12:50