前段時間在知識星球中有同窗讓我空閒的時候能不能分享一下 WebSocket,若是不考慮協議層的底層細節,那麼基本上一兩句話就能夠說清楚:php
WebSocket 是創建在傳輸層 TCP 之上,而且依賴於 HTTP 的應用層協議,它的出現主要是爲了彌補 HTTP 協議中,服務器端沒法主動推送消息到客戶端的缺陷html
但是光是這麼回答,我覺着對該同窗的幫助也不大,不如就付諸行動,實打實的構建一個實例前端
實例描述:手機能夠經過掃描電腦二維碼(其實也不必定是手機控制電腦只要是端對端就能夠),跟電腦創建一個關聯,而後在手機中點擊方格,能夠同步控制電腦上的方格
實例體驗:傳送門node
結合 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
創建鏈接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
鏈接打開以後,從 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 共建計劃,誠邀您的加入,一塊兒牛逼一塊兒飛