介绍
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,效果图如下:
github地址:
- 服务端 python: https://github.com/Lushenggang/websocket_python
- 前端 vue: https://github.com/Lushenggang/websocket_vue
前端
前端需要主动建立连接,同时实现收发消息功能。
1.我们封装一个websocket类,对类进行实例化就创建了一个socket连接:
文件路径:src/socket/socket.js
class CWebSocket {
constructor (url) {
this.socket = new Websocket(url)
}
}
- 发消息函数
- 因为收发的消息是字符串格式,所以需要用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)
}
}
- 收消息函数
- 收到消息的时候会触发指定的回调函数,所以我们定义一个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: