基於 Node、WebSocket 的手機控制電腦實例

1、背景

前段時間在知識星球中有同窗讓我空閒的時候能不能分享一下 WebSocket,若是不考慮協議層的底層細節,那麼基本上一兩句話就能夠說清楚:php

WebSocket 是創建在傳輸層 TCP 之上,而且依賴於 HTTP 的應用層協議,它的出現主要是爲了彌補 HTTP 協議中,服務器端沒法主動推送消息到客戶端的缺陷html

但是光是這麼回答,我覺着對該同窗的幫助也不大,不如就付諸行動,實打實的構建一個實例前端

實例效果
實例效果

實例描述:手機能夠經過掃描電腦二維碼(其實也不必定是手機控制電腦只要是端對端就能夠),跟電腦創建一個關聯,而後在手機中點擊方格,能夠同步控制電腦上的方格
實例體驗:傳送門node

2、實現思路

用例圖
用例圖

  1. 首先 PC 端先要跟服務器端創建一個鏈接,鏈接創建以後,服務器爲鏈接的實例建立一個惟一的 id,並返回到客戶端。同時維護一個 Map,以鏈接 id 爲 key 值保存鏈接實例
  2. PC 端拿到鏈接 id,以 id 做爲參數拼接一個控制方頁面 url,而且將 url 生成爲二維碼,方便手機掃描
  3. 手機掃碼訪問 PC 端拼接好的 url,從 url 參數中獲取關聯方 id,向服務器發起鏈接,當鏈接創建成功以後,向服務發送關聯 id,服務器收到關聯消息,維護一個 Map 創建新實例 id 和 關聯方 id 的關聯關係
  4. 當手機端進行了點擊方格的操做,發送一個消息到服務器,服務器找到關聯方實例,將消息透傳到 PC 端
  5. PC 端根據透傳消息作相應的動做

3、代碼實現

1)服務器端代碼

結合 express 建立 WebSocket 服務web

const app = express();

// 建立應用服務器
const server = http.createServer(app);
// 啓動 HTTP 服務
server.listen(port, '0.0.0.0', function onStart(err) {
    if (err) {
        console.log(err);
    }
    console.log('啓動成功');
});

// 經過 ws 模塊創建 Websocket 服務器
const WebSocketServer = require('ws').Server;
const wss = new WebSocketServer( { server : server } );

// 鏈接實例 Map
process.wsMap = {}
// 鏈接實例關聯關係 Map
process.wsRelaMap = {}
// 鏈接監聽
require('./src/socket/conn.js')(wss)複製代碼

爲了方便,這裏使用了一個專門處理 WebSocket 的 node 模塊 ws,前面提到過,WebSocket 要依賴於 HTTP,因此在創建 WebSocket 服務器的時候須要傳入一個 HTTP 服務器實例。服務器創建成功以後,須要監聽來自客戶端的鏈接:express

wss.on('connection', function( ws ) {
        // 鏈接實例 id
        const id = ws._ultron.id;
        ws.on('message', function( data, flags ) {
            const dataStr = data;
            data = JSON.parse(data);
            /**
            * 初始鏈接,而且傳入了須要關聯的 id
            */
            if (data.type === '1' && data.relaId) {
                wsRelaMap[id] = data.relaId;
            } else if (data.type === '2') { // 發送消息到關聯方
                const rela = wsMap[wsRelaMap[id]];
                if (rela) {
                    rela.send(dataStr);
                }
            }
        });
        // 鏈接關閉,從 Map 中移除,不然長期佔據內存
        ws.on('close', function() {
            console.log('stopping client');
            delete wsMap[id]
        });

       // 保持鏈接實例
        wsMap[id] = ws;
       // 發送 id 到客戶端
        ws.send(message.buildConnectMessage(id));
    });複製代碼

根據 type 連區分消息類型,type 爲 1 爲初始鏈接消息,假若傳入了關聯方 id,這創建一個關聯關係。當 type 爲 2 的時候,找到該實例的關聯方,而且將消息透傳到關聯方api

2)PC 端代碼(被控制方)

創建鏈接bash

var domain = '192.168.1.102:5001/';
   var wsServer = 'ws://' + domain;
   var websocket = new WebSocket(wsServer);複製代碼

接收消息服務器

function onMessage (evt) {
        // console.log(evt.data)
        // document.getElementById('message').innerText = evt.data
        var msg = JSON.parse(evt.data);
        var qrcodeImg = document.getElementById('qrcodeImg');
        console.log(msg);
        console.log(msg.id);
        // 消息類型爲1,初始化鏈接的時候,服務器端返回鏈接 id
        if (msg.type === '1') {
            // 拼接控制方鏈接,並調用接口生成二維碼
            qrcodeImg.src = 'http://qr.liantu.com/api.php?text=http://' + domain + 'handler.html?id=' + msg.id
        } else {
            // 其它類型的消息爲控制消息,根據消息作相應的變換
            qrcodeImg.style.display = 'none';
            document.getElementById('show').style.display = 'block';
            if (msg.selected) {
                var items = document.getElementsByClassName('item');
                for (var i=0; i <items.length; i++) {
                    items[i].style.backgroundColor = '#ccc'
                }
                document.getElementById(msg.selected).style.backgroundColor = 'red'
            }
        }
    }複製代碼

初始鏈接的時候,服務器端會返回鏈接實例 id(根據 type 字段來區分消息類型),前端根據 id 拼接控制方連接,並調用接口生成二維碼。對於控制消息,解析以後,變換對應的方格顏色就能夠了websocket

3)前端控制方

鏈接打開以後,從 url 獲取關聯 id,發送到服務器端創建關聯,而且監聽方格點擊,隨時向服務器發起控制消息

function onOpen () {
       // 獲取關聯 id
        var relaId = getQueryString('id') || 1
        var message = {
            type: '1',
            relaId: relaId
        };
      // 發起關聯消息
        websocket.send(JSON.stringify(message));
        var conMsg = {
            type: '2',
            message: 'connected'
        };
        websocket.send(JSON.stringify(conMsg));

        // 監聽點擊,改變方格顏色,併發起控制消息
        var items = document.getElementsByClassName('item');
        for (var i=0; i <items.length; i++) {
            items[i].addEventListener('click', function (e) {
               var msg = {
                    type: '2',
                    selected: this.id
                };
                websocket.send(JSON.stringify(msg));
                for (var i=0; i <items.length; i++) {
                    items[i].style.backgroundColor = '#ccc';
                }
                this.style.backgroundColor = 'red';
            });
        }
    }複製代碼

4、總結

對於最終目標來講,這個實例還太過簡單,咱們還能夠作更加炫酷的東西,例如:鮮花從 A 手機滑動到 B 手機,只有你想不到,沒有什麼咱們不能夠嘗試~~

咱們在菲麥前端知識星球發起了 WebSocket demos 共建計劃,誠邀您的加入,一塊兒牛逼一塊兒飛

菲麥前端,一個讓知識深刻原理的星球
菲麥前端,一個讓知識深刻原理的星球
相關文章
相關標籤/搜索