基于 Node、WebSocket 的手机控制电脑实例

本文介绍了一个基于 WebSocket 的实战案例,实现了手机通过扫描二维码控制电脑的功能。详细解释了技术原理及其实现过程。

一、背景

前段时间在知识星球中有同学让我空闲的时候能不能分享一下 WebSocket,如果不考虑协议层的底层细节,那么基本上一两句话就可以说清楚:

WebSocket 是建立在传输层 TCP 之上,并且依赖于 HTTP 的应用层协议,它的出现主要是为了弥补 HTTP 协议中,服务器端无法主动推送消息到客户端的缺陷

可是光是这么回答,我觉着对该同学的帮助也不大,不如就付诸行动,实打实的构建一个实例

实例效果

实例描述:手机可以通过扫描电脑二维码(其实也不一定是手机控制电脑只要是端对端就可以),跟电脑建立一个关联,然后在手机中点击方格,可以同步控制电脑上的方格
实例体验:传送门

二、实现思路

用例图
  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 端根据透传消息做相应的动作

三、代码实现

1)服务器端代码

结合 express 创建 WebSocket 服务

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 服务器实例。服务器建立成功之后,需要监听来自客户端的连接:

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 的时候,找到该实例的关联方,并且将消息透传到关联方

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

建立连接

   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 拼接控制方链接,并调用接口生成二维码。对于控制消息,解析之后,变换对应的方格颜色就可以了

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';
            });
        }
    }复制代码

四、总结

对于最终目标来说,这个实例还太过简单,我们还可以做更加炫酷的东西,例如:鲜花从 A 手机滑动到 B 手机,只有你想不到,没有什么我们不可以尝试~~

我们在菲麦前端知识星球发起了 WebSocket demos 共建计划,诚邀您的加入,一起牛逼一起飞

菲麦前端,一个让知识深入原理的星球
WebSocket客户端和服务端实例源码 WebSocket ws实例 HTML5 用java实现的服务端 Websocket与服务器的正常通信 众所周知,Web 应用的交互过程通常是客户端通过浏览器发出一个请求,服务器端接收请求后进行处理并返回结果给客户端,客户端浏览器将信息呈现,这种机制对于信息变化不是特别频繁的应用尚可,但对于实时要求高、海量并发的应用来说显得捉襟见肘,尤其在当前业界移动互联网蓬勃发展的趋势下,高并发与用户实时响应是 Web 应用经常面临的问题,比如金融证券的实时信息,Web 导航应用中的地理位置获取,社交网络的实时消息推送等。 传统的请求-响应模式的 Web 开发在处理此类业务场景时,通常采用实时通讯方案,常见的是: 轮询,原理简单易懂,就是客户端通过一定的时间间隔以频繁请求的方式向服务器发送请求,来保持客户端和服务器端的数据同步。问题很明显,当客户端以固定频率向服务器端发送请求时,服务器端的数据可能并没有更新,带来很多无谓请求,浪费带宽,效率低下。 基于 Flash,AdobeFlash 通过自己的 Socket 实现完成数据交换,再利用 Flash 暴露出相应的接口为 JavaScript 调用,从而达到实时传输目的。此方式比轮询要高效,且因为 Flash 安装率高,应用场景比较广泛,但在移动互联网终端上 Flash 的支持并不好。IOS 系统中没有 Flash 的存在,在 Android 中虽然有 Flash 的支持,但实际的使用效果差强人意,且对移动设备的硬件配置要求较高。2012 年 Adobe 官方宣布不再支持 Android4.1+系统,宣告了 Flash 在移动终端上的死亡。 从上文可以看出,传统 Web 模式在处理高并发及实时性需求的时候,会遇到难以逾越的瓶颈,我们需要一种高效节能的双向通信机制来保证数据的实时传输。在此背景下,基于 HTML5 规范的、有 Web TCP 之称的 WebSocket 应运而生。 早期 HTML5 并没有形成业界统一的规范,各个浏览器和应用服务器厂商有着各异的类似实现,如 IBM 的 MQTT,Comet 开源框架等,直到 2014 年,HTML5 在 IBM、微软、Google 等巨头的推动和协作下终于尘埃落地,正式从草案落实为实际标准规范,各个应用服务器及浏览器厂商逐步开始统一,在 JavaEE7 中也实现了 WebSocket 协议,从而无论是客户端还是服务端的 WebSocket 都已完备,读者可以查阅HTML5 规范,熟悉新的 HTML 协议规范及 WebSocket 支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值