前端调试长链接

372 阅读5分钟

长链接这种业务的需求,在前端的应用场景还是比较多的,比如我上一家公司做的 IM 及时通讯, 这家公司需要用到的实时状态更新,今天就让我们来简单搞一个本地就能调试的前后端长链接服务好了!

iShot_2023-06-25_14.09.57.png 最终我们连接成功以后可以这样调用一下子, 本地通过 node 启动的服务来互相之间进行通信, 这里长链接的心跳检测原理就不解释了,已经有很多人解释的比较全面啦~ , 长链接的链接 和建立 从逻辑来讲还是很清晰的。

准备

  • 首先我们需要两个依赖包 "socket.io": "^4.5.4", "socket.io-client": "^4.5.4" ,
  • 目录结构如下,就是自己本地搭一下 简单的不行

iShot_2023-06-25_14.47.47.png 这里,后面我后端的代码,可以直接放在里面, 前端代码我进行了一层封装, 封装了一个公共类, 使用继承的方式来使用。
然后我们可以使用 node 文件名 启动后端node服务, 前端的我们可以把代码插入到我们任何一个项目当中,随便在一个项目的启动文件或者页面中都可以。

前端

  • 首先我们依赖的包,主要是 socket.io-client,
  • 公共类
/*
 * @Description:长链接包源头使用
 * @Autor: codeBo
 * @Date: 2023-06-21 10:21:18
 * @LastEditors: gjzxlihaibo@163.com
 * @LastEditTime: 2023-06-25 14:01:26
 */
import io from 'socket.io-client'
// 实例化 io 类
class socketIO {
  socket: any
  constructor(opt: { url: string; query: any }) {
    // 最新版直接使用 io 调用,不需要使用 io.connnet , 第一个参数是 url ,第二个参数是配置项 后端可通过 on connection 事件 参数中 解构出来 handshake 读取
    this.socket = io(opt.url, {
      reconnection: true, // 是否从新链接 默认 true
      transports: ['websocket'],
      query: opt.query || {},
    })
  }
  // 派发事件
  emit(event: string, param: any): void {
    console.log('=============send:', event, ':', param)
    this.socket.emit(event, param)
  }
  // 侦听事件, callback 是回调, 后端直接调用传参给前端
  addListener(event: string, callback: (res: any) => undefined) {
    this.socket.on(event, (data: any) => {
      callback(data)
    })
  }
  // 开启长链接
  open(): void {
    console.log('=============socket-open')
    this.socket.open()
  }
  // 链接
  connect(): void {
    this.socket.connect()
  }
  // 关闭
  close(): void {
    this.socket.close()
  }
}

export default socketIO
  • 使用公共类

下面的 socketIO 就是导入我们这个公共类, EventEmitter 是一个方法类, 是一个 事件bus,代码在这个之后

import EventEmitter from '../util/event'
import { MSG_EVENT_CONSTANT } from '../util/constant'
import socketIO from './socket'
import { INTERFACE_SOCKET_OPT_BASE } from './interface'
export class ProcessSocket extends EventEmitter {
  socket: any
  opt: INTERFACE_SOCKET_OPT_BASE
  constructor(opt: INTERFACE_SOCKET_OPT_BASE) {
    super()
    // opt socket 配置项, 可以根据需求自行更改, socketUrl 必填
    this.opt = Object.assign(
      {
        socketUrl: '',
        token: '',
        userId: '',
        loginType: '',
        userType: '',
      },
      opt,
    )
    this.socket = new socketIO({
      url: opt.socketUrl,
      query: {
        systemId: this.opt.userId,
        token: this.opt.token,
        userType: this.opt.userType,
        loginType: this.opt.loginType,
      },
    })
    this.bindEvent()
  }
  bindEvent = (): void => {
    // 连接成功
    this.socket.addListener('connect', () => {
      console.log('=============connected:')
      this.emit(MSG_EVENT_CONSTANT.CONNECT_CHANGE, 1)
      // 用户信息校验
      const param = {
        cmd: 'register',
        userId: this.opt.userId,
        role: this.opt.userRole || 'default',
        deviceVersion: 'web service system',
        token: this.opt.token,
      }
      this.socket.emit('my_event', param)
    })
    // 连接断开
    this.socket.addListener('disconnect', (reason: string) => {
      console.log('=============discounnect:', reason)
      // if (reason === "io server disconnect") {
      //     this.socket.connect();
      // }
      this.emit(MSG_EVENT_CONSTANT.CONNECT_CHANGE, 0)
      //this.socket.connect();
    })
    // 连接错误
    this.socket.addListener('error', (reason: string) => {
      console.log('=============error连接错误:', reason)
      this.emit(MSG_EVENT_CONSTANT.CONNECT_CHANGE, 0)
    })
    // 连接错误
    this.socket.addListener('connect_error', (reason: string) => {
      console.log('=============connect_error连接错误', reason)
      this.emit(MSG_EVENT_CONSTANT.CONNECT_CHANGE, 0)
    })
    // 接收消息
    this.socket.addListener('connect_msg', (reason: string) => {
      console.log('=============接收消息', reason)
    })
  }
  // 登录
  login(): void {
    this.socket.open()
  }
  // 发送消息  我们可以使用这个方法 来调试动态消息的发送与接受,页面中可以加一个按钮,然后调用实体类的这个方法
  sendMessage(dataObj: any): void {
    const sendData = {
      cmd: 'chat',
      message: dataObj,
    }
    this.socket.emit('my_event', sendData)
  }
}

  • 事件类
export default class EventEmitter {
  _eventMap: Map<string, any[]>
  constructor() {
    this._eventMap = new Map()
  }

  emit(type: string, ...args: any) {
    if (this._eventMap.has(type)) {
      const cbs: any = this._eventMap.get(type)
      for (const fn of cbs) {
        fn.apply(this, args)
      }
      return true
    } else {
      return false
    }
  }

  off(type: string, fn: any) {
    if (type === undefined) {
      this._eventMap.clear()
    } else if (this._eventMap.has(type)) {
      if (fn === undefined) {
        this._eventMap.delete(type)
      } else {
        const cbs: any[] | undefined = this._eventMap.get(type)
        if (cbs && cbs.length > 1) {
          cbs.splice(cbs.indexOf(fn), 1)
        } else {
          this._eventMap.delete(type)
        }
      }
    }
    return this
  }

  on(type: string, fn: any) {
    if (this._eventMap.has(type)) {
      const cbs: any[] | undefined = this._eventMap.get(type)
      cbs && cbs.push(fn)
    } else {
      this._eventMap.set(type, [fn])
    }
    return this
  }

  once(type: string, fn: any) {
    this.on(type, (...args: any) => {
      this.off(type, fn)
      fn.apply(this, args)
    })
    return this
  }
}

在业务代码中使用我们上面的方法类

  • 侦听我们 node 服务启动的对应端口号 socketHandle 可以放在我们按钮的点击事件上
// 调试长链接服务, 请 本地启动 src/sdk/server/io.js 服务进行调试, 感谢
const socket = new ProcessSocket({ socketUrl: 'http://127.0.0.1:23456' })
const socketHandle = () => {
  socket.sendMessage('传输给后端的消息')
}

后端

  • 我们前端可以自己本地启动一个服务, 使用 socket.io, 使用 http 启动一个端口号的服务, 期间要解决跨域问题, 使用 io 建立链接, 其回调函数中有一个参数是 socket , 我们就是用这个类 来侦听 和派发事件。 这里面 on my_event 就是侦听前端 sendMessage 事件中的消息啦~
  • 上代码
import { createServer } from 'http'
import { Server } from 'socket.io'
// 此文件如无法 在项目中 cd 进去使用的时候, 可自己搞一个
const connectList = ['']

const testRoomList = []

const httpServer = createServer()

const io = new Server(httpServer, {
  cors: {
    //解决跨域问题
    origin: '*',
    methods: ['GET', 'POST'],
  },
})
let numUsers = 0
connectList.length = 0
testRoomList.length = 0
io.on('connection', (socket) => {
  // ...
  console.log('cconnection---->' + socket.id)

  socket.on('disconnect', () => {
    // ...
    console.log('断开连接---->')
    let index = connectList.indexOf(socket.id)
    connectList.splice(index, index + 1)

    let index1 = testRoomList.indexOf(socket.id)
    connectList.splice(index1, index1 + 1)
  })
  // 收到消息 , 参数就是前端传过来了, 1,2,3 这样排列,回调函数可以放在任意位置, cb 消息
  socket.on('my_event', (res) => {
    console.log('收到消息', res)
  })
  socket.on('leave', (arg) => {
    // console.log(`socket ${arg.id} has leave room ${arg.room}`);
    console.log('leave-room    --->' + arg) // world
    socket.leave(arg)
    io.to(arg).emit(
      'leavecall',
      `socket ${socket.username} 离开了房间: ${arg}`,
    )
  })

  socket.on('hello', (arg) => {
    console.log('on  hello    --->' + arg) // world
    socket.emit('server-back', {
      message: 'server give you a msg',
    })
  })

  // when the client emits 'add user', this listens and executes
  socket.on('do login', (user_name) => {
    let index = connectList.indexOf(socket.id)
    console.log('index---->' + index)
    if (index > -1) {
      let str = user_name + '已经登录---->'
      console.log(str)
      socket.emit('login', {
        isLogin: true,
        numUsers: numUsers,
        username: user_name,
        message: str,
      })
    } else {
      console.log('登陆成功---->')
      connectList.push(socket.id)
      socket.username = user_name
      ++numUsers
      socket.emit('login', {
        isLogin: true,
        numUsers: numUsers,
        username: user_name,
        message: '登陆成功',
      })
    }
  })

  // when the client emits 'add user', this listens and executes
  socket.on('join test room', (user_name) => {
    socket.username = user_name
    let index = testRoomList.indexOf(socket.id)
    console.log('index---->' + index)
    if (index > -1) {
      let str = user_name + '已经加入房间---->'
      console.log(str)
      socket.emit('user joined', str)
    } else {
      let str = '欢迎 ' + user_name + '加入---->'
      console.log(str)
      testRoomList.push(socket.id)
      socket.join('testroom')
      io.to('testroom').emit('user joined', str)
      if (user_name === '王二麻子') {
        socket.join('test1')
        socket.join('test12')
      } else {
        socket.join('test3')
        socket.join('test4')
      }
    }
  })

  socket.on('obj message', (arg, callback) => {
    console.log('obj message    --->' + JSON.stringify(arg)) // world
    callback('1111111111111111111111111111111')
  })
  socket.on('obj callback', (arg, callback) => {
    console.log('obj callback    --->' + JSON.stringify(arg)) // world
    callback({
      name: 'server',
      data: 'with params callback',
    })
  })

  socket.on('new message', (arg) => {
    console.log('new message    --->' + JSON.stringify(arg)) // world
    socket.emit('new message', {
      username: socket.username,
      message: arg + '的服务器回复',
    })
    if (testRoomList.length > 1) {
      socket
        .to(testRoomList[0])
        .emit('private message', socket.username, arg + '的服务器回复')
    }
  })
  socket.on('get message', (arg, callback) => {
    console.log('get message    --->' + arg) // world
    // io.to("testroom").emit("msg", {
    //   message: arg + "的服务器回复"
    // });
    callback(arg + '的服务器回复')
  })

  socket.emit('send', 1, '2', { 3: '4', 5: Buffer.from([6]) })

  io.sockets.emit('broadcast', {
    message: 'this is a broadcast msg',
  })
  socket.on('compress', (arg) => {
    console.log('compress    --->' + arg) // world
    socket.compress(true).emit('compress', 'this is a compress msg')
  })
  socket.on('my-event', (arg, callback) => {
    console.log('my-event    --->' + arg) // world
    setTimeout(() => {
      callback({
        message: 'this is a timeout msg',
      })
    }, 1000)
    // socket.emit('my-event-back','this is a timeout msg')
  })
  socket.on('update item', (arg1, arg2, callback) => {
    console.log(arg1) // 1
    console.log(arg2) // { name: "updated" }
    callback({
      status: 'ok',
    })
  })

  io.local.emit('localmsg', {
    message: 'this is local msg',
  })
  socket.emit('new message', '测试删除所以')

  socket.emit('msg', '测试单独string回复')
  setTimeout(() => {
    socket.emit('connect_msg', { msg: '发送消息给客户端111' })
  }, 20000)
})

httpServer.listen(23456, function () {
  console.log('client listening.....')
})

大家有问题可以留言,工作日24h以内就会回复的。