1、websocket
websocket是html5出的协议,它是基于TCP协议,利用http协议建立连接,实现了客户端和服务器端的双向通信,对http协议是个很好的补充。
具体的原理在这里不再赘述,详情可以参照知乎用户Ovear的回答,说的十分生动形象:WebSocket 是什么原理?为什么可以实现持久连接?
2、socket.io
socket.io实现了对websocket的封装,使用户可以专注于实现websocket的功能(双向通信),而不必理会底层的实现。由于不同版本的浏览器对websocket的支持不同,为了兼容不同版本、实现相关功能,socket.io底层实现根据浏览器的支持分别使用了http轮询、WebSocket还有其他的技术手段实现功能。
3、socket.io使用
3.1、socket.io客户(浏览器)端
socket.io包括客户端和服务端的api,创建客户端socket.io有两种方式,详细内容见w3c教程:
一种是传统的script标签引入:
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io('http://localhost');
</script>
一种是node环境下利用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 (字符串):连接的服务端地址,例如本例中的:ws://127.0.0.1:3500,默认的指向widnow.location;
option (Object):选项,常用参数有以下几种
---forceNew (布尔型)是否重用当前已存在的链接;
---path (字符串)自定义path,连接的路径;
---query(Object)携带查询选项;
Return Socket
在这里要注意,socket.io有几个概念来区分不同地址的链接:
1、url:目标地址,即服务器所在地址,
2、命名空间(namespace):一个服务器所在的地址可以有多个命名空间,例如客户端连接的地址为ws://127.0.0.1:3500/admin,命名空间为admin;当客户端连接的地址为ws://127.0.0.1:3500/customer,命名空间为customer,这样客户端根据命名空间的不同连接不同的服务端后台命名空间。
---在不声明新的命名空间情况下,系统会默认使用default namespace。
---不同命名空间下的socket是不能互相通信了,是处于隔离状态的。
3、房间(room):一个命名空间可以有多个房间,这在实际开发中很有用,例如根据id查询仪器信息:ws://127.0.0.1:3500/deviceInfo,可以将id为1的仪器信息连接到deviceInfo命名空间的1房间,将id为2的仪器信息连接到deviceInfo命名空间的2房间,这样访问两个房间的用户就可以准确的获取相关的信息。
---在不加入或指定room的情况下,socket.io 会默认分配一个default room
---同一room下的socket可以广播消息,不同room下的socket是不能互相通信了,是处于隔离状态的。
记住一点:一个socket可以有多个namespace,每个namespace可以有多个room,每个namespace和room之间是隔离的不能互相通信,room可以加入但是namespace在连接时就要指定。
客户端通过io新建的tcp连接只有一个,例如:
const socket = io();
const adminSocket = io('/admin');
const customerSocket = io('/customer');
注意:重用相同的命名空间将会创建两个连接:
const socket = io();
const socket2 = io();//两个不同的socket,和上面的socket不同
按照上面的内容,下例为实时获取后台发送的设备信息,将id作为参数发送到后台,后台根据id创建room进行前后端的双向通信(示例1):
// 设备信息
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服务器端
安装socket.io
$ npm install socket.io
socket.io的安装十分简单,就像普通的npm包一样直接安装就好,详细内容见w3c教程
我们使用koa框架来搭建服务器端,在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){})就能监听客户端setClientMsg方法发送的消息,io.emit('setServeMsg',serverData)就能想客户端发送的消息serverData。
如果我们服务器端只拥有这个几个websocket方法,这种创建方法十分简洁明了,但是实际工作中,往往使用websocket接口可能只有那么几个,其他的都是http接口,而且可能需要对于不同的用户的客户端需要建立不同的websocket的接口,那应该怎么使用呢(示例1)?
一、服务器端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()); //响应json化
app.use(cors()); //设置跨域cors
//静态文件
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服务传入函数
server.listen(3500);
console.log(`-----------服务运行成功,本地端口:3500----------`);
creatSocket.js
const IO = require('socket.io'),
{
getFulldate
} = require('../util');
function creatSocket(app) {
const io = IO(app);
//每个客户端socket连接时都会触发 connection 事件
io.on("connection", function(clientSocket) {
clientSocket.emit("receiveMsg", '连接整体的socket');
console.log('连接整体的socket');
});
//单独的命名空间
//命名空间:监听属性改变的,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
二、服务器端websocket处理动态接口
类似于http接口,将id等参数传递到服务器端,服务器后台根据handshake.query.id获取到id的取值,socket.io可以根据id等参数产生新的房间,然后与客户端连接。
详情看上面的creatSocket.js,利用jion加入房间,然后利用to(roomid).emit发送消息
clientSocket.join(deviceId); //加入房间
clientSocket.to(deviceId).emit(`deviceParam`, `deviceParam ${deviceId} time:` + time);
3.3、运行结果
点击websocket按钮,会调用GetDeviceParam,连接到后台ws://127.0.0.1:3500/deviceInfo,实现数据的实时刷新,由此完成了前后端基于websocket的双向实时通信。服务器端代码示例见github。