Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/backbone.js/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 在没有TURN服务器的情况下,如何使用WebRTC绕过NAT?_Javascript_Mobile_Webrtc_P2p_Stun - Fatal编程技术网

Javascript 在没有TURN服务器的情况下,如何使用WebRTC绕过NAT?

Javascript 在没有TURN服务器的情况下,如何使用WebRTC绕过NAT?,javascript,mobile,webrtc,p2p,stun,Javascript,Mobile,Webrtc,P2p,Stun,我正在尝试制作一个可以在移动浏览器上玩的点对点Javascript游戏 我已经成功地在本地WiFi网络中的两部手机之间建立了p2p连接。 我无法通过移动网络连接两部手机,或者一部在WiFi上,另一部在移动网络上。 我试图关闭Windows防火墙,但无法通过移动网络将电脑连接到手机。 我试着让两个同龄人建立他们自己的数据通道并协商设置。 我读到80%到90%的设备能够通过WebRTC连接,而无需打开服务器,因此我完全不知道下一步该怎么做 桌面:谷歌Chrome 79.0.3945.130官方构建6

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

我已经成功地在本地WiFi网络中的两部手机之间建立了p2p连接。 我无法通过移动网络连接两部手机,或者一部在WiFi上,另一部在移动网络上。 我试图关闭Windows防火墙,但无法通过移动网络将电脑连接到手机。 我试着让两个同龄人建立他们自己的数据通道并协商设置。 我读到80%到90%的设备能够通过WebRTC连接,而无需打开服务器,因此我完全不知道下一步该怎么做

桌面:谷歌Chrome 79.0.3945.130官方构建64位队列:稳定

移动像素3/Android 10:Google Chrome 79.0.3945.116

移动网络

无线网络

对等代码


这里可能有一些不同的因素在起作用

两侧的NAT类型 IP系列IPv4或IPv6 UDP协议是允许的吗? 我会确定你在每一边的NAT类型,你可以在这里阅读更多。如果两个网络都在对称NAT后面,则需要一个TURN服务器

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

我还会在收集候选人时检查IPv4/IPv6是否存在交叉点。一些电话供应商只提供IPv6


UDP可能根本不被允许。这并不常见,但有可能。在这种情况下,您将被迫使用TURN。通过TCP进行NAT穿越是可能的,但WebRTC AFAIK不支持。

这里可能有一些不同的因素

两侧的NAT类型 IP系列IPv4或IPv6 UDP协议是允许的吗? 我会确定你在每一边的NAT类型,你可以在这里阅读更多。如果两个网络都在对称NAT后面,则需要一个TURN服务器

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

我还会在收集候选人时检查IPv4/IPv6是否存在交叉点。一些电话供应商只提供IPv6


UDP可能根本不被允许。这并不常见,但有可能。在这种情况下,您将被迫使用TURN。通过TCP进行NAT穿越是可能的,但WebRTC AFAIK不支持。

TURN服务器是解决该问题的一种方法。如果有不需要它的变通方法,没有人会使用它。这里一个常见的误解是,如果向系统中添加一个TURN服务器,它将中继所有流量。但事实并非如此,它仅用作无法以其他方式建立的连接的备用连接。与通过websocket服务器路由所有游戏消息相比,这仍将为您节省80%以上的流量


下一步是安装TURN服务器。广泛使用,并有合理的文件记录。它足够稳定,一旦安装,所需的维护量非常低。

TURN服务器就是解决此问题的方法。如果有不需要它的变通方法,没有人会使用它。这里一个常见的误解是,如果向系统中添加一个TURN服务器,它将中继所有流量。但事实并非如此,它仅用作无法以其他方式建立的连接的备用连接。与通过websocket服务器路由所有游戏消息相比,这仍将为您节省80%以上的流量


下一步是安装TURN服务器。广泛使用,并有合理的文件记录。它足够稳定,一旦安装,所需的维护量就非常低。

谢谢您的提示,我将研究这些。交叉点是指一个使用IPv6的对等点和一个使用IPv4的对等点吗?没错!人们将使用TURN服务器作为IPv4 IPv6I检查了ICE候选服务器,它显示了我的手机在移动网络上的IPv4 IP地址,以及我的计算机在局域网上的IPv4 IP地址。我的手机位于对称NAT后面,而我的台式机则不是。这难道不意味着他们应该能够与STUN连接吗?我尝试过,两台设备都通过了数据吞吐量,但都有以下连接消息:[WARN]无法使用自反候选连接,可能是由于网络环境/配置的原因。感谢提供的提示,我将研究这些。交叉点是指一个使用IPv6的对等点和一个使用IPv4的对等点吗?没错!人们将使用TURN服务器作为IPv4 IPv6I检查了ICE候选服务器,它显示了我的手机在移动网络上的IPv4 IP地址,以及我的计算机在局域网上的IPv4 IP地址。我的手机位于对称NAT后面,而我的台式机则不是。这难道不意味着他们应该能够与STUN连接吗?我尝试过,两台设备都通过了数据吞吐量,但有以下连接消息:[WARN]无法使用自反候选连接,可能是由于网络环境/配置。我正在查看托管c
oturn或付费Twilio,但我想确认我所做的工作对哪怕一个客户端都有效。我最终设置了一个coturn服务器。我正在考虑托管coturn或付费Twilio,但我想确认我所做的工作对哪怕一个客户端都有效。我最终设置了一个coturn服务器。
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
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);
    }
}