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

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


deviceInfo返回结果

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


        

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容