基于 Node、WebSocket 的手机控制电脑实例
一、背景
前段时间在知识星球中有同学让我空闲的时候能不能分享一下 WebSocket,如果不考虑协议层的底层细节,那么基本上一两句话就可以说清楚:
WebSocket 是建立在传输层 TCP 之上,并且依赖于 HTTP 的应用层协议,它的出现主要是为了弥补 HTTP 协议中,服务器端无法主动推送消息到客户端的缺陷
可是光是这么回答,我觉着对该同学的帮助也不大,不如就付诸行动,实打实的构建一个实例
实例效果实例描述:手机可以通过扫描电脑二维码(其实也不一定是手机控制电脑只要是端对端就可以),跟电脑建立一个关联,然后在手机中点击方格,可以同步控制电脑上的方格
实例体验:传送门
二、实现思路
用例图- 首先 PC 端先要跟服务器端建立一个连接,连接建立之后,服务器为连接的实例创建一个唯一的 id,并返回到客户端。同时维护一个 Map,以连接 id 为 key 值保存连接实例
- PC 端拿到连接 id,以 id 作为参数拼接一个控制方页面 url,并且将 url 生成为二维码,方便手机扫描
- 手机扫码访问 PC 端拼接好的 url,从 url 参数中获取关联方 id,向服务器发起连接,当连接建立成功之后,向服务发送关联 id,服务器收到关联消息,维护一个 Map 建立新实例 id 和 关联方 id 的关联关系
- 当手机端进行了点击方格的操作,发送一个消息到服务器,服务器找到关联方实例,将消息透传到 PC 端
- 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 共建计划,诚邀您的加入,一起牛逼一起飞
菲麦前端,一个让知识深入原理的星球