WebRTC连接本地连接,但通过Internet进行故障转移

时间:2015-12-11 06:35:18

标签: javascript webrtc

我正在运行一些测试代码,我正在尝试学习WebRTC的基础知识。即使我使用TURN服务器(一侧显示“检查”状态而另一侧显示“失败”状态),此测试代码也可以在局域网上运行,但不能通过互联网运行。我可以看到SDP中有冰候选者,所以我不需要明确发送它们(对吧?)。

这会将大量调试信息写入控制台,因此我可以告诉我的信令服务器正在运行。我被困 - 我的代码需要做些什么才能让它在互联网上工作?

顺便说一下,我在我的测试计算机之间运行了其他WebRTC演示脚本,它们确实有效(如opentokrtc.ocom)

<html>
<head>
<title>test</title>
<script type="text/javascript">
var curInvite = null;
//create an invitation to connect and post to signalling server
function CreateInvite(){
    //function to run upon receiving a response
    var postRespFunc = function(txt){
        console.log("Posted offer and received " + txt);
        var invite = txt;
        curInvite = invite;
        document.getElementById("inviteId").innerHTML = invite;
        //then poll for answer...
        var pollFunc = function(){
            GetRequest("answered?"+invite,function(txt){
                if(txt){
                    //assume it's the answer
                    handleAnswer(txt);
                }else{
                    //poll more
                    setTimeout(pollFunc,1000);
                }
            });
        };
        //start polling for answer
        setTimeout(pollFunc,1000);
    };
    //function to run after creating the WebRTC offer
    var postFunc = function(offer){
        PostRequest('offer','offer='+encodeURIComponent(offer), postRespFunc);
    }
    //create the offer
    createLocalOffer(postFunc);
}
function AnswerInvite(){
    var invite = document.getElementById("invitation").value;
    //can we create our local description BEFORE we get the remote desc?
    //reduce to one ajax call?
    GetRequest("accept?"+invite,function(txt){
        var answerPostedCallback = function(txt){
            console.log("answerPostedCallback",txt);
        }
        var answerCallback = function(answer){
            PostRequest("answer?"+invite,'answer='+encodeURIComponent(answer), answerPostedCallback);
        }
        handleOffer(txt, answerCallback);
        //then we're waiting for a data channel to be open...
    });
}

function PostRequest(postUrl, reqStr, callback){
    var req=new XMLHttpRequest();
    req.onload = function(){
        var strResp = req.responseText;
        if(callback) callback(strResp);
    }
    //var namevalue=encodeURIComponent(document.getElementById("test").value);
    //var parameters="name="+namevalue;
    req.open("POST", postUrl, true);
    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.send(reqStr);
}
function GetRequest(getUrl, callback){
    var req=new XMLHttpRequest();
    req.onload = function(){
        var strResp = req.responseText;
        if(callback) callback(strResp);
    }
    //var namevalue=encodeURIComponent(document.getElementById("test").value);
    //var parameters="name="+namevalue;
    req.open("GET", getUrl, true);
    req.send();
}

/************ WebRTC stuff ****************/
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || 
                       window.webkitRTCPeerConnection || window.msRTCPeerConnection;
var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription ||
                       window.webkitRTCSessionDescription || window.msRTCSessionDescription;

navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia ||
                       navigator.webkitGetUserMedia || navigator.msGetUserMedia;
//SEE http://olegh.ftp.sh/public-stun.txt                       
var cfg = {"iceServers":[
{url:'stun:stun.12connect.com:3478'},
{url:'stun:stun.12voip.com:3478'}
]};

cfg.iceServers = [{
url: 'turn:numb.viagenie.ca',
    credential: 'muazkh',
    username: 'webrtc@live.com'
}]

var con = { 'optional': [{'DtlsSrtpKeyAgreement': true}] };

var peerConnection = new RTCPeerConnection(cfg,con);
var dataChannel = null;

function initDataChannel(){
    dataChannel.onerror = function (error) {
            console.log("Data Channel Error:", error);
        };

        dataChannel.onmessage = function (event) {
            console.log("Got Data Channel Message:", event.data);
            var data = JSON.parse(event.data);
            document.getElementById("chat").innerHTML+= "RECD: " + data + "<br />";
        };

        dataChannel.onopen = function () {
            console.log('data channel open');
            alert("data channel open, ready to connect!");
        };

        dataChannel.onclose = function () {
            console.log("The Data Channel is Closed");
            peerConnection.close();
            alert("Disconnected!");
        };
}

//used when peerConnection is an answerer
peerConnection.ondatachannel = function (e) {
    dataChannel = e.channel || e; // Chrome sends event, FF sends raw channel
    initDataChannel();
    console.log("Received datachannel", arguments);
}
//to initiate a connection
function createLocalOffer(callback) {
        //create datachannel
        try {
        dataChannel = peerConnection.createDataChannel('test', {reliable:true});
        initDataChannel();
        console.log("Created datachannel (peerConnection)");
    } catch (e) { console.warn("No data channel (peerConnection)", e); }
        //set event handler
        peerConnection.onicecandidate = function (e) {
                console.log("ICE candidate (peerConnection)", e);
                if (e.candidate == null) {
                        console.log("ice candidate",peerConnection.localDescription);
                        callback(JSON.stringify(peerConnection.localDescription));
                }
        };
    peerConnection.createOffer(function (desc) {
        peerConnection.setLocalDescription(desc);
        console.log("created local offer", desc);
    }, function () {console.warn("Couldn't create offer");});
}

peerConnection.onconnection = function(e){
    console.log("peerConnection connected",e);
};

function onsignalingstatechange(state) {
    console.info('signaling state change:', state);
}

function oniceconnectionstatechange(state) {
    console.info('ice connection state change:', state);
    console.info('iceConnectionState: ', peerConnection.iceConnectionState);
}

function onicegatheringstatechange(state) {
    console.info('ice gathering state change:', state);
}

peerConnection.onsignalingstatechange = onsignalingstatechange;
peerConnection.oniceconnectionstatechange = oniceconnectionstatechange;
peerConnection.onicegatheringstatechange = onicegatheringstatechange;

//local handles answer from remote
function handleAnswer(answerJson) {
        var obj = JSON.parse(answerJson);
        var answerDesc = new RTCSessionDescription(obj);
    peerConnection.setRemoteDescription(answerDesc);
}

/* functions for remote side */

//handle offer from the initiator
function handleOffer(offerJson, callback) {
        var obj = JSON.parse(offerJson);
        var offerDesc = new RTCSessionDescription(obj);
    peerConnection.setRemoteDescription(offerDesc);
    //set event handler
        peerConnection.onicecandidate = function (e) {
                console.log("ICE candidate (peerConnection)", e);
                if (e.candidate == null) {
                        console.log("ice candidate",peerConnection.localDescription);
                }
        };
    peerConnection.createAnswer(function (answerDesc) {
        console.log("Created local answer: ", answerDesc);
        peerConnection.setLocalDescription(answerDesc);
        callback(JSON.stringify(answerDesc));
    }, function () { console.warn("No create answer"); });
}

function sendMessage() {
            var msg = document.getElementById("msg").value;
            document.getElementById("msg").value = null;
            document.getElementById("chat").innerHTML+= "SENT: " + msg + "<br />";
            var obj = {message: msg};
            dataChannel.send(JSON.stringify(msg));
    return false;
};

</script>
</script>
</head>
<body>
<p>test</p>
<p>
<div id="createWrapper">
<h4>create an invitiation</h4>
<button type="button" onclick="CreateInvite();">create invitation</button>
<h3 id="inviteId"></h3>
</div>
<div id="acceptWrapper">
<h4>or accept an inviation</h4>
<input id="invitation" type="text" name="invitation" />
<button type="button" onclick="AnswerInvite()">answer invitation</button>
</div>
<p>Once the data channel is open type your messages below</p>
<input type="text" id="msg" /><button type="button" onclick="sendMessage()">send</button>
<div id="chat"></div>
</body>
</html>

[编辑:这是工作代码,如果它对任何人都有用。您仍然需要自己的信令服务器和工作STUN / TURN服务器,但这有助于我理解基础知识]

<html>
<head>
<title>test</title>
<script type="text/javascript">
var curInvite = null;
//create an invitation to connect and post to signalling server
function CreateInvite(){
  //function to run upon receiving a response
  var postRespFunc = function(txt){
    console.log("Posted offer and received " + txt);
    var invite = txt;
    curInvite = invite;
    document.getElementById("inviteId").innerHTML = invite;
    //then poll for answer...
    var pollFunc = function(){
      GetRequest("answered?"+invite,function(txt){
        if(txt){
          //assume it's the answer
          handleAnswer(txt);
        }else{
          //poll more
          setTimeout(pollFunc,1000);
        }
      });
    };
    //start polling for answer
    setTimeout(pollFunc,100);
  };
  //function to run after creating the WebRTC offer
  var postFunc = function(offer){
    PostRequest('offer','offer='+encodeURIComponent(offer), postRespFunc);
  }
  //create the offer
  createLocalOffer(postFunc);
}
function AnswerInvite(){
  var invite = document.getElementById("invitation").value;
  //can we create our local description BEFORE we get the remote desc?
  //reduce to one ajax call?
  GetRequest("accept?"+invite,function(txt){
    var answerPostedCallback = function(txt){
      console.log("answerPostedCallback",txt);
    }
    var answerCallback = function(answer){
      PostRequest("answer?"+invite,'answer='+encodeURIComponent(answer), answerPostedCallback);
    }
    handleOffer(txt, answerCallback);
    //then we're waiting for a data channel to be open...
  });
}

function PostRequest(postUrl, reqStr, callback){
  var req=new XMLHttpRequest();
  req.onload = function(){
    var strResp = req.responseText;
    if(callback) callback(strResp);
  }
  //var namevalue=encodeURIComponent(document.getElementById("test").value);
  //var parameters="name="+namevalue;
  req.open("POST", postUrl, true);
  req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  req.send(reqStr);
}
function GetRequest(getUrl, callback){
  var req=new XMLHttpRequest();
  req.onload = function(){
    var strResp = req.responseText;
    if(callback) callback(strResp);
  }
  //var namevalue=encodeURIComponent(document.getElementById("test").value);
  //var parameters="name="+namevalue;
  req.open("GET", getUrl, true);
  req.send();
}

/************ WebRTC stuff ****************/
var RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || 
                       window.webkitRTCPeerConnection || window.msRTCPeerConnection;
var RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription ||
                       window.webkitRTCSessionDescription || window.msRTCSessionDescription;

navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia ||
                       navigator.webkitGetUserMedia || navigator.msGetUserMedia;
//SEE http://olegh.ftp.sh/public-stun.txt                       
var cfg = {"iceServers":[
    {url: 'turn:numb.viagenie.ca',
        credential: 'muazkh',
        username: 'webrtc@live.com'
    }
]};

var con = { 'optional': [{'DtlsSrtpKeyAgreement': true}] };

var peerConnection = null;

function createPeer(){
  peerConnection = new RTCPeerConnection(cfg,con);

  //used when peerConnection is an answerer
  peerConnection.ondatachannel = function (e) {
      dataChannel = e.channel || e; // Chrome sends event, FF sends raw channel
      initDataChannel();
      console.log("Received datachannel", arguments);
  }

  peerConnection.onsignalingstatechange = onsignalingstatechange;
  peerConnection.oniceconnectionstatechange = oniceconnectionstatechange;
  peerConnection.onicegatheringstatechange = onicegatheringstatechange;

  peerConnection.onconnection = function(e){
    console.log("peerConnection connected",e);
  };
}

var dataChannel = null;

function initDataChannel(){
  dataChannel.onerror = function (error) {
      console.log("Data Channel Error:", error);
    };

    dataChannel.onmessage = function (event) {
      console.log("Got Data Channel Message:", event.data);
      var data = JSON.parse(event.data);
      document.getElementById("chat").innerHTML+= "RECD: " + data + "<br />";
    };

    dataChannel.onopen = function () {
      console.log('data channel open');
      alert("data channel open, ready to connect!");
    };

    dataChannel.onclose = function () {
      console.log("The Data Channel is Closed");
      peerConnection.close();
      alert("Disconnected!");
    };
}

//to initiate a connection
function createLocalOffer(callback) {
  createPeer();
    //create datachannel
    try {
        dataChannel = peerConnection.createDataChannel('test', {reliable:true});
        initDataChannel();
        console.log("Created datachannel (peerConnection)");
    } catch (e) { console.warn("No data channel (peerConnection)", e); }
    //set event handler
    peerConnection.onicecandidate = function (e) {
        console.log("ICE candidate (peerConnection)", e);
        if (e.candidate == null) {
            console.log("ice candidate",peerConnection.localDescription);
            callback(JSON.stringify(peerConnection.localDescription));
        }
    };
    peerConnection.createOffer(function (desc) {
        peerConnection.setLocalDescription(desc);
        console.log("created local offer", desc);
    }, function () {console.warn("Couldn't create offer");});
}


function onsignalingstatechange(state) {
    console.info('signaling state change:', state);
}

function oniceconnectionstatechange(state) {
    console.info('ice connection state change:', state);
    console.info('iceConnectionState: ', peerConnection.iceConnectionState);
}

function onicegatheringstatechange(state) {
    console.info('ice gathering state change:', state);
}

//local handles answer from remote
function handleAnswer(answerJson) {
    var obj = JSON.parse(answerJson);
    var answerDesc = new RTCSessionDescription(obj);
    peerConnection.setRemoteDescription(answerDesc);
}

/* functions for remote side */

//handle offer from the initiator
function handleOffer(offerJson, callback) {
    createPeer();
    var obj = JSON.parse(offerJson);
    var offerDesc = new RTCSessionDescription(obj);
    //set event handler
    peerConnection.onicecandidate = function (e) {
        console.log("ICE candidate (peerConnection)", e);
        if (e.candidate == null) {
          console.log("ice candidate",peerConnection.localDescription);
          callback(JSON.stringify(peerConnection.localDescription));
        }
    };
    peerConnection.setRemoteDescription(offerDesc);
    peerConnection.createAnswer(function (answerDesc) {
        console.log("Created local answer: ", answerDesc);
        peerConnection.setLocalDescription(answerDesc);
    }, function () { console.warn("No create answer"); });
}

function sendMessage() {
      var msg = document.getElementById("msg").value;
      document.getElementById("msg").value = null;
      document.getElementById("chat").innerHTML+= "SENT: " + msg + "<br />";
      var obj = {message: msg};
      dataChannel.send(JSON.stringify(msg));
    return false;
};

</script>
</script>
</head>
<body>
<p>test</p>
<p>
<div id="createWrapper">
<h4>create an invitiation</h4>
<button type="button" onclick="CreateInvite();">create invitation</button>
<h3 id="inviteId"></h3>
</div>
<div id="acceptWrapper">
<h4>or accept an inviation</h4>
<input id="invitation" type="text" name="invitation" />
<button type="button" onclick="AnswerInvite()">answer invitation</button>
</div>
<p>Once the data channel is open type your messages below</p>
<input type="text" id="msg" /><button type="button" onclick="sendMessage()">send</button>
<div id="chat"></div>
</body>
</html>

2 个答案:

答案 0 :(得分:3)

SDP将只包含截至该时间点收集的候选人,因此,除非您在null回调中等待pc.onicecandidate候选人,否则您不会以这种方式获得所有候选人(您好像在等待createLocalOffer,而不是handleOffer,我认为这是问题所在。)

也就是说,我不推荐这种方法,因为ICE代理耗尽所有路径可能需要20秒(例如在具有VPN的系统上发生很多)。相反,我强烈建议明确地将候选人送到另一方。即Trickle ICE。

答案 1 :(得分:1)

如果你没有看到任何候选人如继电器或服务器反身那么你可以先尝试使用wireshark来捕获tcpdump,看看你的STUN / TURN服务器是否有任何传出数据包,如果没有传出通过过滤为STUN,然后您的配置可能无法正常工作。如果您看到STUN / TURN流量,那么无论出现什么错误,您都可能从服务器获得一些可能有身份验证错误或其他错误的响应,但根据响应类型,您可以通过STUN / TRUN确定问题。如果您看到STUN / TURN的成功响应,那么您可以看到您是否正确地将候选人包括在SDP中或您的信号提供/回复消息中,以便将候选人宣传到另一端。