前端项目中使用websocket来实现即时通讯-以聊天室为例

时间:2024-03-21 21:56:42

介绍

websocket可以在用户的浏览器和服务器之间打开交互式通信会话,使用websocket可以向服务器发送消息并接收事件驱动的响应,而无需通过轮询服务器的方式以获得响应。

本文通过构建一个简易的websocket聊天室,简单介绍如何使用websocket在服务端和浏览器端进行通信。

首先介绍一下前端websocket一些基本接口。

Websocket API

  • 实例化
let socket = new Websocket(url[, protocols])
// 实例化直接建立连接,连接完成可以开始通讯了
  • 发送消息
socket.send(msg)
  • 关闭
socket.close()

Websocket 回调函数

  • onopen
    连接建立成功触发,可以开始通讯了。
  • onmessage(event)
    接收消息函数,服务器返回的数据在event.data中,接收到的数据是字符串的格式。
  • onclose
    socket关闭时被调用。
    -onerror
    用于指定连接失败后的回调函数。
    指定回调函数的示例:
socket.onmessage = (event) => {
  console.log(event.data)
}

Websocket 状态码

实例化后,socket的可能的状态码可以通过访问socket对象的常量属性获得。
可能的状态码:

- socket.CONNECTING - 0 # 正在链接,实例化至此时onopen触发之间
- socket.OPEN - 1	# 触发onopen,之后一直保持该状态直到断开连接
- socket.CLOSING - 2 # 关闭连接挥手阶段 
- socket.CLOSED - 3 # 连接已关闭或者连接未建立。 当服务器关闭或者调用close断开连接并且挥手结束后

socket当前的状态码可以通过readyState属性进行访问:

if (socket.readyState == socket.OPEN) { // 判断是否在正常连接状态
  xxx
} 

Websockt聊天室实现

只是一个非常简易的demo,效果图如下:
前端项目中使用websocket来实现即时通讯-以聊天室为例
github地址:

前端

前端需要主动建立连接,同时实现收发消息功能。
1.我们封装一个websocket类,对类进行实例化就创建了一个socket连接:
文件路径:src/socket/socket.js

class CWebSocket {
  constructor (url) {
    this.socket = new Websocket(url)
  }
}
  1. 发消息函数
  • 因为收发的消息是字符串格式,所以需要用JSON.stringify转为字符串。
  • 实例化后就可能会调用发送消息,但是此时连接可能未建立完成,所以用一个待发送消息列表将未发送的消息保存下来,等连接成功后发送。
class CWebSocket {
  constructor (url) {
    this.socket = new Websocket(url)
  	this.waitingList = []
  	this.socket.open = (...args) => {
  		this.checkWaitingList()
  	}
  }
  checkWaitingList () {
  	this.waitingList.forEach(this.C2SMessage)
  },
  C2SMessage (data) {
  	if (this.socket.readyState !== this.socket.OPEN) {
  		this.waitingList.push(data)
  		return
	}
  	let msg = JSON.stringify(data)
  	this.socket.send(msg)
  }
}
  1. 收消息函数
  • 收到消息的时候会触发指定的回调函数,所以我们定义一个S2CMessage函数,并赋值到onmessage上。
  • 为了在外部方便地监听socket收到的消息,需要给类添加监听函数addListener、移除监听函数addListener, 并将添加的监听函数存放到一个列表里:
class CWebSocket {
	constructor (url) {
		// ...
		this.listenerList = []
		this.socket.onmessage = (...args) => {
	      this.S2CMessage(...args)
	    }
	}
	addListener (func) {
		this.listenerList.push(func)
	}
	removeListener (func) {
		let index = this,listenerList.indexOf(func)
		index != -1 && this.listenerList.splice(index, 1)
	}
	S2CMessage (event) {
		let msg = JSON.parse(event.data)
		this.listenerList.forEach(func => func(msg))
	}
}

至此,就实现websocket的绝大部分功能。
4. 销毁实例,有类实例创建、初始化就应该有对应的销毁函数:

class CWebSocket {
	// ...
	close () {
	    if (this.socket) {
	      this.listenerList = []
	      this.socket.onopen = null
	      this.socket.onmessage = null
	      this.socket.close()
	      this.socket = null
	    }
	}
}

同时导出这个类,方便外部引用:

export class CWebSocket {
	xxx
}

简易但是完整的scoket封装类就完成了,接下来就是使用了。为每个用户生成一个uuid作为ID,组件创建和销毁的时候分别分别创建和销毁socket对象,
简单写一个页面:

<template>
  <div id="app">
    <div class="title">简易websocket聊天室({{ userId }})</div>
    <div class="message-list">
      <div class="message" v-for="(data, idx) of messageList" :key="idx">
        <div class="user-id">{{ data.userId }}<template v-if="data.userId == userId">(我)</template>:</div>
        <div class="msg">{{ data.msg }}</div>
      </div>
    </div>
    <div class="msg-send">
      <textarea placeholder="输入消息开始聊天吧" @keypress.ctrl.enter.stop="C2SMessage" v-model="message" cols="30" rows="5"></textarea>
      <button @click.stop="C2SMessage">发送</button>
    </div>
  </div>
</template>

<script>
import uuidv4 from 'uuid/v4'
import { CWebSocket } from '@/socket/socket.js'

export default {
	data () {
		messageList: [],
		message: '',
		userId: uuidv4(),
		socket: null
	}
	created () {
		this.socket = new CWebSocket()
		this.socket.addListener((...args) => {
			this.S2CMessage(...args)
		})
	},
	beforeDestroy () {
		this.socket.close()
	},
	methods: {
		S2CMessage (data) {
	      this.messageList.push(data)
	    },
	    C2SMessage () {
	      if (!this.message) return
	      this.socket.C2SMessage({
	        msg: this.message,
	        userId: this.userId
	      })
	      this.message = ''
	    }
	}
}
</script>

<style>
body {
  background: #FAFBFC
}
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  box-shadow: 0 0 3px 0 #3361d8;
  width: 50rem;
  max-width: 100%;
  background: #ECF5FD;
  position: fixed;
  border-radius: 4px;
  transform: translate(-50%, 0);
  left: 50%;
  display: flex;
  flex-direction: column;
  top: 5rem;
  bottom: 5rem;
}
.title {
  flex-shrink: 0;
  padding: .5rem;
}
.message-list {
  flex: auto;
  background: white;
  overflow: auto
}

.message-list .message {
  text-align: left;
  padding: .5rem;
}
.message-list .message .user-id {
  color: blue;
}

.message-list .message .msg {
  margin: 0 1rem;
  padding: 8px;
  background: #ECF2FC;
  border-radius: 4px;
}

.msg-send {
  flex-shrink: 0;
  display: flex;
  padding: 4px;
}
.msg-send textarea {
  flex: auto;
  border-radius: 4px;
  resize: none;
  padding: 8px 4px;
}
.msg-send button {
  float: right;
  margin: 4px;
  align-self: flex-end;
  border-radius: 4px;
  border: none;
  background: #57a3f3;
  color: white;
  height: 2rem;
  padding: 0 1rem;
  line-height: 2rem;
}

</style>

服务端代码

使用python的web框架bottle,代码非常简单:

from bottle import get, run
from bottle.ext.websocket import GeventWebSocketServer
from bottle.ext.websocket import websocket

users = set()
@get('/websocket/', apply=[websocket])
def chat(ws):
  users.add(ws)
  while True:
    msg = ws.receive()  # 接客户端的消息
    if msg:
      print(msg)
      for user in users:
        user.send(msg) # 广播消息
      else:
        break
  print('退出聊天')
  users.remove(ws)

run(host='127.0.0.1', port=8000, server=GeventWebSocketServer)

一个简易的聊天室就完成了,详细代码可查看github: