[WebRTC] 透過 WebRTC 做到P2P對談(1) – Server篇

2017-03-02

接下來幾篇文章,我想實作一下WebRTC來做到P2P對談,基本上我是接到任務發現這真的是一個有點小複雜的東西,所以網路上看一些文章測試出不少方法,這是目前跑起來看起來比較容易懂的方法,介紹給大家讓大家少走一些冤枉路,首先這邊我們要用到的東西,分別為兩台電腦(分別在不同IP),然後一台Linux的機器,另一台我是用Windows ,為何要用Linux因為跑node.js 我主要都是用Linux ,不過你可以自己重組你自己的環境,這一篇主要是基本概念+Signaling Server 的建置.. HERE WE GO !!
image

1.什麼是WebRTC:  WebRTC,名稱源自網頁即時通訊(英語:Web Real-Time Communication)的縮寫,是一個支援網頁瀏覽器進行實時語音對話或影片對話的API。它於2011年6月1日開源並在GoogleMozillaOpera支援下被納入全球資訊網協會W3C推薦標準[1][2][3]。 (from : https://zh.wikipedia.org/wiki/WebRTC)

簡單的說一個技術用來做到瀏覽器之間來做到P2P的溝通。

2.為何還要架設Server ,因為茫茫人海中,你要如何讓兩個瀏覽器知道對方,所以需要一台Singaling Server來做到,目前網路上很多作法用firebase,但是這邊我希望就是用自己的server 所以我選用nodejs來架設websocket 做通訊的signaling server .

簡單的說就是讓兩個client彼此知道存在。

3.你會聽到一個名詞叫做ICE /STUN,網路上說 互動式連結建立 (Interactive Connectivity Establishment,ICE) 是可讓網路瀏覽器連上端點的框架。有很多原因會導致無法建立Peer A 與 Peer B 的直接連線。若連線遭遇到阻擋開放連線的防火牆,可透過 ICE 越過防火牆。在大多數情況下,你的裝置都沒有公用的 IP 位址,也能讓 ICE 提供專屬位址。如果你的路由器無法直接連上端點,同樣能藉由 ICE 讓中介伺服器轉發資料。
STUN :NAT 會話傳輸應用程序 (Session Traversal Utilities for NAT,STUN) (縮寫之內還有縮寫) 屬於一種通訊協定,可找到你的公用位址,並判斷你的路由器是否會限制直接連上端點。用戶端將送出請求到 STUN 伺服器,而 STUN 伺服器會回傳用戶端的公用位址,並藉以得知路由器 NAT 背後的用戶端是否連上公用網路。


(from : https://blog.mozilla.com.tw/posts/3261/webrtc-%E7%9B%B8%E9%97%9C%E7%B8%AE%E5%AF%AB%E5%90%8D%E8%A9%9E%E7%B0%A1%E4%BB%8B)

簡單的說 一種技術讓你在p2p通訊的時候對於許多限制被統一克服的解決方案。

在之後的範例裡面我們是用Google的機器 : stun:stun2.l.google.com:19302

4. 終於我們要開始動作了,首先,你得先安裝node.js 還有 npm 這邊我就不贅述

sudo apt-get update
sudo apt-get install nodejs
sudo apt-get install npm

5.接下來 就是給你一段code 你就直接存成server.js 並且存檔用node.js run 起來 
//require our websocket library 
var WebSocketServer = require('ws').Server;

//creating a websocket server at port 9090 
var wss = new WebSocketServer({ port: 9090 });

//all connected to the server users 
var users = {};

//when a user connects to our sever 
wss.on('connection', function (connection) {

    console.log("User connected");

    //when server gets a message from a connected user 
    connection.on('message', function (message) {

        var data;
        //accepting only JSON messages 
        try {
            data = JSON.parse(message);
        } catch (e) {
            console.log("Invalid JSON");
            data = {};
        }

        //switching type of the user message 
        switch (data.type) {
            //when a user tries to login 
            case "login":
                console.log("User logged", data.name);
                //if anyone is logged in with this username then refuse 
                if (users[data.name]) {
                    sendTo(connection, {
                        type: "login",
                        success: false,
			message: '重複登入'
                    });
                } else {
                    //save user connection on the server 
                    users[data.name] = connection;
                    connection.name = data.name;

                    sendTo(connection, {
                        type: "login",
                        success: true
                    });
                }
		console.log("users data: ", users);
                break;

            case "offer":
                //for ex. UserA wants to call UserB 
                console.log("Sending offer to: ", data.name);
		
                //if UserB exists then send him offer details 
                var conn = users[data.name];

                if (conn != null) {
                    //setting that UserA connected with UserB 
                    connection.otherName = data.name;

                    sendTo(conn, {
                        type: "offer",
                        offer: data.offer,
                        name: connection.name
                    });
                }
		

                break;

            case "answer":
                console.log("Sending answer to: ", data.name);
                //for ex. UserB answers UserA 
                var conn = users[data.name];

                if (conn != null) {
                    connection.otherName = data.name;
                    sendTo(conn, {
                        type: "answer",
                        answer: data.answer
                    });
                }

                break;
 	
	     

            case "candidate":
                console.log("Sending candidate to:", data.name);
                var conn = users[data.name];

                if (conn != null) {
                    sendTo(conn, {
                        type: "candidate",
                        candidate: data.candidate
                    });
                }

                break;

            case "leave":
                
                var conn = users[data.name];
		
		users[data.name]=undefined;
               
		//conn.otherName = null;

                //notify the other user so he can disconnect his peer connection 
                if (conn != null) {
                    sendTo(conn, {
                        type: "leave"
                    });
                }
		
		console.log("[leave] users data : ", users);

                break;

            default:
                sendTo(connection, {
                    type: "error",
                    message: "Command not found: " + data.type
                });

                break;

        }
    });

    //when user exits, for example closes a browser window 
    //this may help if we are still in "offer","answer" or "candidate" state 
    connection.on("close", function () {

        if (connection.name) {
            delete users[connection.name];

            if (connection.otherName) {
                console.log("Disconnecting from ", connection.otherName);
                var conn = users[connection.otherName];
		
                if (conn != null) {
		    conn.otherName = null;
                    sendTo(conn, {
                        type: "leave"
                    });
                }
            }
        }
    });

    connection.send(JSON.stringify({
                        type: "hello",
                        message: 'Welcome to connect Signaling Server'
                    }));

});

function sendTo(connection, message) {
    connection.send(JSON.stringify(message));
}

6.執行起來 node server.js ,如果你跑起來會出現Error: Cannot find module 'ws' ,你就記得安裝 ws 套件
npm install ws

unnamed

之後就是run 起來,這樣ws 版本的signaling server就建設好了
這邊有幾件事情要提一下,上面的code不完全是我寫的,是我改網路上看到的code ,因為他的code run 起來可能因為版本關係有些地方不太對,而且我加入些東西,如果你覺得我說的不夠清楚,請您移駕到這裡
https://www.tutorialspoint.com/webrtc/webrtc_text_demo.htm

接下來就是client 得部分


當麻許的超技八 2014 | Donma Hsu Design.