实时通讯 nodejs-websocket 模块使用介绍

发布于 2019-03-19 11:53:05 字数 8084 浏览 3025 评论 0

Socket 是一种网络通信协议,一般用来进行实时通信会使用到,而我们的聊天室就可以使用 Websocket 来进行通信,比起传统的轮询更加高效和节约资源。

nodejs-websocket 是一个 NodeJS 模块,用于创建 Websocket 的客户端和服务端。

使用方法

使用命令行安装模块

npm install nodejs-websocket

创建一个 websocket 的服务:

var ws = require("nodejs-websocket")
 
// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
  console.log("New connection")
  conn.on("text", function (str) {
    console.log("Received "+str)
    conn.sendText(str.toUpperCase()+"!!!")
  })
  conn.on("close", function (code, reason) {
    console.log("Connection closed")
  })
}).listen(8001)

文档地址: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()
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

0 文章
0 评论
84961 人气
更多

推荐作者

醉城メ夜风

文章 0 评论 0

远昼

文章 0 评论 0

平生欢

文章 0 评论 0

微凉

文章 0 评论 0

Honwey

文章 0 评论 0

qq_ikhFfg

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文