nodejs-websocket介绍

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))
  }
}

问题

如果浏览器进入其它页面或者关闭浏览器,链接会异常关闭,经?;岬贾潞蠖顺鱿忠斐1ù?/h4>
// 前端代码监听页面关闭或者刷新
window.onunload = () => {
  this.closeConnect()
}
// vue里跳转到其它页面
beforeRouteLeave(to, from, next) {
  this.closeConnect()
  next()
}

总结

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

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

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

推荐阅读更多精彩内容