nodejs-websocket介绍

作者: taomas | 来源:发表于2018-09-18 17:20 被阅读1129次

    websocket 是一种网络通信协议,一般用来进行实时通信会使用到

    为什么要用 websocket

    websocket 协议和 http 协议类似,http 协议有一个缺陷,只能由客户方端发起请求,服务端根据请求 url 和传过去的参数返回对应结果

    websocket 是双向通信的,只要 websocket 连接建立起来,可以由客户端给服务端发送数据,也可以由服务端主动给客户端发送数据

    websocket 适用场景:聊天室

    简介

    websocket 相关简介,可以看阮老师的文章

    用法

    服务端nodejs-websocket

    nodejs 可以通过nodejs-websocket来实现创建一个 websocket 的服务

    // websocket.js
    const ws = require('nodejs-websocket')
    
    const createServer = () => {
      let server = ws.createServer(connection => {
        connection.on('text', function(result) {
          console.log('发送消息', result)
        })
        connection.on('connect', function(code) {
          console.log('开启连接', code)
        })
        connection.on('close', function(code) {
          console.log('关闭连接', code)
        })
        connection.on('error', function(code) {
          console.log('异常关闭', code)
        })
      })
      return server
    }
    
    module.exports = createServer()
    

    nodejs-websocket用法

    文档地址:https://www.npmjs.com/package/nodejs-websocket

    node 创建的 websocket 服务,主要包含三个概念

    WS: 引入nodejs-websocket后的主要对象

    • ws.createServer([options], [callback]):创建一个 server 对象
    • ws.connect(URL, [options], [callback]):创建一个 connect 对象,一般由客户端链接服务端 websocket 服务时创建
    • ws.setBinaryFragmentation(bytes):设置传输二进制文件的最小尺寸,默认 512kb
    • setMaxBufferLength:设置传输二进制文件的最大尺寸,默认 2M

    Server:通过 ws.createServer 创建

    Function

    • server.listen(port, [host], [callback]): 传入端口和主机地址后,开启一个 websocket 服务
    • server.close([callback]): 关闭 websocket 服务
    • server.connections: 返回包含所有 connection 的数组,可以用来广播所有消息
    // 服务端广播
    function broadcast(server, msg) {
      server.connections.forEach(function(conn) {
        conn.sendText(msg)
      })
    }
    

    Event

    可以通过server.on('event', (res) => {console.log(res)})调用

    • Event: 'listening()':调用server.listen会触发当前事件
    • Event: 'close()': 当服务关闭时触发该事件,如果有任何一个 connection 保持链接,都不会触发该事件
    • Event: 'error(errObj)':发生错误时触发,此事件后会直接调用 close 事件
    • Event: 'connection(conn)':建立新链接(完成握手后)触发,conn 是连接的实例对象

    Connection:每一个客户端创建连接时的实例

    Function

    • connection.sendText(str, [callback]):发送字符串给另一侧,可以由服务端发送字符串数据给客户端
    • connection.beginBinary():要求连接开始传输二进制,返回一个WritableStream
    • connection.sendBinary(data, [callback]): 发送一个二进制块,类似connection.beginBinary().end(data)
    • connection.send(data, [callback]): 发送一个字符串或者二进制内容到客户端,如果发送的是文本,类似于sendText(),如果发送的是二进制,类似于sendBinary(),
      callback将监听发送完成的回调
    • connection.close([code, [reason]]):开始关闭握手(发送一个关闭指令)
    • connection.server:如果服务是 nodejs 启动,这里会保留 server 的引用
    • connection.readyState:一个常量,表示连接的当前状态

    connection.CONNECTING:值为 0,表示正在连接
    connection.OPEN:值为 1,表示连接成功,可以通信了
    connection.CLOSING:值为 2,表示连接正在关闭。
    connection.CLOSED:值为 3,表示连接已经关闭,或者打开连接失败。

    • connection.outStream: 存储connection.beginBinary()返回的OutStream对象,没有则返回 null
    • connection.path:表示建立连接的路径
    • connection.headers:只读请求头的 name 的 value 对应的 object 对象
    • connection.protocols:客户端请求的协议数组,没有则返回空数组
    • connection.protocol:同意连接的协议,如果有这个协议,它会包含在connection.protocols数组里面

    Event

    • Event: 'close(code, reason)': 连接关闭时触发
    • Event: 'error(err)':发生错误时触发,如果握手无效,也会发出响应
    • Event: 'text(str)':收到文本时触发,str 时收到的文本字符串
    • Event: 'binary(inStream)':收到二进制内容时触发,inStream时一个ReadableStream
    var server = ws
      .createServer(function(conn) {
        console.log('New connection')
        conn.on('binary', function(inStream) {
          // 创建空的buffer对象,收集二进制数据
          var data = new Buffer(0)
          // 读取二进制数据的内容并且添加到buffer中
          inStream.on('readable', function() {
            var newData = inStream.read()
            if (newData)
              data = Buffer.concat([data, newData], data.length + newData.length)
          })
          inStream.on('end', function() {
            // 读取完成二进制数据后,处理二进制数据
            process_my_data(data)
          })
        })
        conn.on('close', function(code, reason) {
          console.log('Connection closed')
        })
      })
      .listen(8001)
    
    • Event: 'connect()':连接完全建立后发出

    具体代码实现

    const ws = require('nodejs-websocket')
    // 可以通过不同的code可以表示要后端实现的不同逻辑
    const {
      RECEIEVE_MESSAGE,
      SAVE_USER_INFO,
      CLOSE_CONNECTION
    } = require('../constants/config')
    
    // 当前聊天室的用户
    let chatUsers = []
    
    // 广播通知
    const broadcast = (server, info) => {
      console.log('broadcast', info)
      server.connections.forEach(function(conn) {
        conn.sendText(JSON.stringify(info))
      })
    }
    
    // 服务端获取到某个用户的信息通知到所有用户
    const broadcastInfo = (server, info) => {
      let count = server.connections.length
      let result = {
        code: RECEIEVE_MESSAGE,
        count: count,
        ...info
      }
      broadcast(server, result)
    }
    
    // 返回当前剩余的在线用户
    const sendChatUsers = (server, user) => {
      let chatIds = chatUsers.map(item => item.chatId)
      if (chatIds.indexOf(user.chatId) === -1) {
        chatUsers.push(user)
      }
      let result = {
        code: SAVE_USER_INFO,
        count: chatUsers.length,
        chatUsers: chatUsers
      }
      broadcast(server, result)
    }
    
    // 触发关闭连接,在离开页面或者关闭页面时,需要主动触发关闭连接
    const handleCloseConnect = (server, user) => {
      chatUsers = chatUsers.filter(item => item.chatId !== user.chatId)
      let result = {
        code: CLOSE_CONNECTION,
        count: chatUsers.length,
        chatUsers: chatUsers
      }
      console.log('handleCloseConnect', user)
      broadcast(server, result)
    }
    
    // 创建websocket服务
    const createServer = () => {
      let server = ws.createServer(connection => {
        connection.on('text', function(result) {
          let info = JSON.parse(result)
          let code = info.code
          if (code === CLOSE_CONNECTION) {
            handleCloseConnect(server, info)
            // 某些情况如果客户端多次触发连接关闭,会导致connection.close()出现异常,这里try/catch一下
            try {
              connection.close()
            } catch (error) {
              console.log('close异常', error)
            }
          } else if (code === SAVE_USER_INFO) {
            sendChatUsers(server, info)
          } else {
            broadcastInfo(server, info)
          }
        })
        connection.on('connect', function(code) {
          console.log('开启连接', code)
        })
        connection.on('close', function(code) {
          console.log('关闭连接', code)
        })
        connection.on('error', function(code) {
          // 某些情况如果客户端多次触发连接关闭,会导致connection.close()出现异常,这里try/catch一下
          try {
            connection.close()
          } catch (error) {
            console.log('close异常', error)
          }
          console.log('异常关闭', code)
        })
      })
      // 所有连接释放时,清空聊天室用户
      server.on('close', () => {
        chatUsers = []
      })
      return server
    }
    
    const server = createServer()
    
    module.exports = server
    

    部分前端代码

    前端主要是创建WebSocket连接后,在onopen事件触发时,初始化用户的一些信息,比如每个用户包含唯一的chatId之类的,以及保持用户昵称,用户头像啥的
    再就是监听onmessage事件,通过后端返回的 message 信息执行对应的操作,建议前后端约定一些 code 来表示某一种类似的 message 信息
    然后就是监听页面的一些触发事件,将信息通过send方法发送给服务端

    let websocket = new WebSocket(wsConfig.WS_ROOT_PATH)
    websocket.onopen = () => {
      console.log('websocket连接开启...')
      if (!this.chatId) {
        this.initChatId()
      }
      this.sendUserName()
    }
    websocket.onmessage = event => {
      let data = event.data
      let result = JSON.parse(data)
      let code = result.code
      let count = result.count
      this.updateChatCount(count)
      if (code === RECEIEVE_MESSAGE) {
        this.pushMessage(result)
        this.onMessageScroll()
      } else if (code === SAVE_USER_INFO || code === CLOSE_CONNECTION) {
        this.updateChatUser(result.chatUsers)
      }
      console.log('数据已接收...', code, result)
    }
    websocket.onclose = this.onWebsocketClose
    websocket.onerror = this.onWebsocketError
    
    // 发送message
    sendMessage(info) {
      if (this.websocket && typeof this.websocket.send === 'function') {
        this.websocket.send(JSON.stringify(info))
      }
    }
    

    问题

    如果浏览器进入其它页面或者关闭浏览器,链接会异常关闭,经常会导致后端出现异常报错

    // 前端代码监听页面关闭或者刷新
    window.onunload = () => {
      this.closeConnect()
    }
    // vue里跳转到其它页面
    beforeRouteLeave(to, from, next) {
      this.closeConnect()
      next()
    }
    

    总结

    这次使用 websocket 实现一个基本的聊天室功能,个人感觉还比较简单,只是中间会出现一些由于链接异常断开,导致后端服务抛出异常挂掉的情况
    记住前端关闭页面或者刷新页面时,先把连接关掉,每次进入页面时创建连接,然后后端将由于异常关闭导致的出错 try/catch 一下,避免抛出异常,阻塞进程

    websocket 对于实现聊天室这样的功能,真的很方便,其实还能扩展到多人合作或者网络游戏等功能

    相关文章

      网友评论

        本文标题:nodejs-websocket介绍

        本文链接:https://www.haomeiwen.com/subject/lcmpnftx.html