基于koa的前后端分离的socket.io使用

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标签引入:

        一种是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接口并存

        工程目录:

基于koa的前后端分离的socket.io使用_第1张图片

                            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、运行结果


基于koa的前后端分离的socket.io使用_第2张图片
deviceInfo返回结果

点击websocket按钮,会调用GetDeviceParam,连接到后台ws://127.0.0.1:3500/deviceInfo,实现数据的实时刷新,由此完成了前后端基于websocket的双向实时通信。服务器端代码示例见github。


        

你可能感兴趣的