1、websocket
????????websocket是html5出的協(xié)議,它是基于TCP協(xié)議,利用http協(xié)議建立連接,實現(xiàn)了客戶端和服務(wù)器端的雙向通信,對http協(xié)議是個很好的補充。
????????具體的原理在這里不再贅述,詳情可以參照知乎用戶Ovear的回答,說的十分生動形象:WebSocket 是什么原理?為什么可以實現(xiàn)持久連接?
2、socket.io
????????socket.io實現(xiàn)了對websocket的封裝,使用戶可以專注于實現(xiàn)websocket的功能(雙向通信),而不必理會底層的實現(xiàn)。由于不同版本的瀏覽器對websocket的支持不同,為了兼容不同版本、實現(xiàn)相關(guān)功能,socket.io底層實現(xiàn)根據(jù)瀏覽器的支持分別使用了http輪詢、WebSocket還有其他的技術(shù)手段實現(xiàn)功能。
3、socket.io使用
3.1、socket.io客戶(瀏覽器)端
socket.io包括客戶端和服務(wù)端的api,創(chuàng)建客戶端socket.io有兩種方式,詳細內(nèi)容見w3c教程:
????????一種是傳統(tǒng)的script標簽引入:
<script src="/socket.io/socket.io.js"></script>
<script>
? const socket = io('http://localhost');
</script>
????????一種是node環(huán)境下利用import引入模塊:
const io = require('socket.io-client');
// or with import syntax
import io from 'socket.io-client';
然后就可以初始化一個socket:io(url[, options]),例如:const socket = io('ws://127.0.0.1:3500/deviceInfo', {query: { id: 1}})
????????url (字符串):連接的服務(wù)端地址,例如本例中的:ws://127.0.0.1:3500,默認的指向widnow.location;
????????option (Object):選項,常用參數(shù)有以下幾種
? ? ? ? ? ? ? ? ---forceNew (布爾型)是否重用當前已存在的鏈接;
? ? ? ? ? ? ? ? ---path (字符串)自定義path,連接的路徑;
? ? ? ? ? ? ? ? ---query(Object)攜帶查詢選項;
? ? ? ? Return Socket
在這里要注意,socket.io有幾個概念來區(qū)分不同地址的鏈接:
????????1、url:目標地址,即服務(wù)器所在地址,
? ? ? ? 2、命名空間(namespace):一個服務(wù)器所在的地址可以有多個命名空間,例如客戶端連接的地址為ws://127.0.0.1:3500/admin,命名空間為admin;當客戶端連接的地址為ws://127.0.0.1:3500/customer,命名空間為customer,這樣客戶端根據(jù)命名空間的不同連接不同的服務(wù)端后臺命名空間。
????????????????---在不聲明新的命名空間情況下,系統(tǒng)會默認使用default namespace。
????????????????---不同命名空間下的socket是不能互相通信了,是處于隔離狀態(tài)的。
? ? ? ? 3、房間(room):一個命名空間可以有多個房間,這在實際開發(fā)中很有用,例如根據(jù)id查詢儀器信息:ws://127.0.0.1:3500/deviceInfo,可以將id為1的儀器信息連接到deviceInfo命名空間的1房間,將id為2的儀器信息連接到deviceInfo命名空間的2房間,這樣訪問兩個房間的用戶就可以準確的獲取相關(guān)的信息。
????????????????---在不加入或指定room的情況下,socket.io 會默認分配一個default room
????????????????---同一room下的socket可以廣播消息,不同room下的socket是不能互相通信了,是處于隔離狀態(tài)的。
? ? ? ? ? ? ? ?記住一點:一個socket可以有多個namespace,每個namespace可以有多個room,每個namespace和room之間是隔離的不能互相通信,room可以加入但是namespace在連接時就要指定。
客戶端通過io新建的tcp連接只有一個,例如:
const socket = io();
const adminSocket = io('/admin');
const customerSocket = io('/customer');
注意:重用相同的命名空間將會創(chuàng)建兩個連接:
const socket = io();
const socket2 = io();//兩個不同的socket,和上面的socket不同
按照上面的內(nèi)容,下例為實時獲取后臺發(fā)送的設(shè)備信息,將id作為參數(shù)發(fā)送到后臺,后臺根據(jù)id創(chuàng)建room進行前后端的雙向通信(示例1):
// 設(shè)備信息
export function GetDeviceParam(_id = 0) {
? const socket = io('ws://127.0.0.1:3500/deviceInfo', {
? ? query: { id: _id }
? })
? socket.on('deviceParam', (d) => {
? ? console.log('deviceParam:', d)
? })
? socket.on('receiveMsg', (d) => {
? ? console.log('receiveMsg:', d)
? })
3.2、socket.io服務(wù)器端
?安裝socket.io
????????????$ npm install socket.io
????????socket.io的安裝十分簡單,就像普通的npm包一樣直接安裝就好,詳細內(nèi)容見w3c教程
?????????我們使用koa框架來搭建服務(wù)器端,在koa中使用socket.io:
? ?????????????const ?app = new koa(),
? ? ? ? ?????????????????IO = require('socket.io')
????????????????????????server = require('http').createServer(app.callback());
? ? ? ? ? ? ? ??const io = IO(app);
????????這樣就新建了一個socket的io,利用io.on('setClientMsg',function(sockect){})就能監(jiān)聽客戶端setClientMsg方法發(fā)送的消息,io.emit('setServeMsg',serverData)就能想客戶端發(fā)送的消息serverData。
????????如果我們服務(wù)器端只擁有這個幾個websocket方法,這種創(chuàng)建方法十分簡潔明了,但是實際工作中,往往使用websocket接口可能只有那么幾個,其他的都是http接口,而且可能需要對于不同的用戶的客戶端需要建立不同的websocket的接口,那應(yīng)該怎么使用呢(示例1)?
一、服務(wù)器端http接口和websocket接口并存
????????工程目錄:

? ??????????????????????????const koa = require('koa'),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? koaBody = require('koa-body'),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? logger = require('koa-logger'),
? ????????????????????????????????????json = require('koa-json'),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? cors = require('@koa/cors'),
? ????????????????????????????????????static = require('koa-static'),
? ????????????????????????????????????koaViews = require('koa-views'),
? ????????????????????????????????????koaNunjucks = require('koa-nunjucks-2'),
? ????????????????????????????????????path = require('path'),
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? router = require('./api'),
? ????????????????????????????????????app = new koa(),
? ????????????????????????????????????server = require('http').createServer(app.callback()),
????????????????????????????????????? creatSocket = require('./socket');
????????????????????????????????//io = require('socket.io')(app);
????????????????????????????????// 使用各種中間件
????????????????????????????????app.use(logger()); //控制臺日志
????????????????????????????????app.use(koaBody({
????????????????????????????????????? multipart: true, // 支持文件上傳
????????????????????????????????????? encoding: 'gzip',
????????????????????????????????????????? formidable: {
? ????????????????????????????????????????????? uploadDir: path.join(__dirname, 'uploads')
? ????????????????????????????????????????????????}
????????????????????????????????????????}));
????????????????????????????????????app.use(json()); //響應(yīng)json化
????????????????????????????????????app.use(cors()); //設(shè)置跨域cors
????????????????????????????????????//靜態(tài)文件
????????????????????????????????????app.use(static(
????????????????????????????????????????? path.join(__dirname, './')
????????????????????????????????????????));
????????????????????????????????????//模板渲染
????????????????????????????????????// app.use(koaViews(path.join(__dirname, './views'), {
????????????????????????????????????????????????//? extension: 'ejs'
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // }));//ejs模板
????????????????????????????????????????app.use(koaNunjucks({
? ????????????????????????????????????????????ext: 'njk', //njk,html
????????????????????????????????????????????? path: path.join(__dirname, './views'),
? ????????????????????????????????????????????????nunjucksConfig: {
? ????????????????????????????????????????????????????????? trimBlocks: true
????????????????????????????????????????????????????? }
????????????????????????????????????????????????})); //Nunjucks模板
????????????????????????????????????????????????//使用路由
????????????????????????????????????????????????app.use(router.routes());
????????????????????????????????????????????????//websocket
????????????????????????????????????????????????creatSocket(server);//將新建的socket服務(wù)傳入函數(shù)
????????????????????????????????????????????????server.listen(3500);
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? console.log(`-----------服務(wù)運行成功,本地端口:3500----------`);????????????????????????????
????????creatSocket.js
const IO = require('socket.io'),
? {
? ? getFulldate
? } = require('../util');
function creatSocket(app) {
? const io = IO(app);
? //每個客戶端socket連接時都會觸發(fā) connection 事件
? io.on("connection", function(clientSocket) {
? ? clientSocket.emit("receiveMsg", '連接整體的socket');
? ? console.log('連接整體的socket');
? });
? //單獨的命名空間
? //命名空間:監(jiān)聽屬性改變的,deviceInfo
? const deviceIo = io.of('/deviceInfo');
? let deviceId = '';
? deviceIo.on("connection", function(clientSocket) {
? ? //console.log('clientSocket.handshake.query.id:', clientSocket.handshake.query.id)
? ? deviceId = clientSocket.handshake.query.id;
? ? clientSocket.emit("receiveMsg", '連接deviceInfo的socket');
? ? console.log('連接deviceInfo的socket');
? ? clientSocket.join(deviceId); //加入房間
? ? //deviceInfo下的room
? ? setInterval(function() {
? ? ? let time = getFulldate();
? ? ? clientSocket.to(deviceId).emit(`deviceParam`, `deviceParam ${deviceId} time:` + time);
? ? }, 5000)
? });
}
module.exports = creatSocket?
? ? ? ?其中creatSocket是將websocket的api封裝在另外的文件夾中,具體代碼可見github
二、服務(wù)器端websocket處理動態(tài)接口
? ? ? ? 類似于http接口,將id等參數(shù)傳遞到服務(wù)器端,服務(wù)器后臺根據(jù)handshake.query.id獲取到id的取值,socket.io可以根據(jù)id等參數(shù)產(chǎn)生新的房間,然后與客戶端連接。
詳情看上面的creatSocket.js,利用jion加入房間,然后利用to(roomid).emit發(fā)送消息
clientSocket.join(deviceId); //加入房間
clientSocket.to(deviceId).emit(`deviceParam`, `deviceParam ${deviceId} time:` + time);
3.3、運行結(jié)果

點擊websocket按鈕,會調(diào)用GetDeviceParam,連接到后臺ws://127.0.0.1:3500/deviceInfo,實現(xiàn)數(shù)據(jù)的實時刷新,由此完成了前后端基于websocket的雙向?qū)崟r通信。服務(wù)器端代碼示例見github。
? ??????