多人同时编辑一个文档,能够在不刷新页面的情况下实时地看到他人的编辑。典型例子有石墨文档、腾讯文档、Google Docs等。
主要需要解决的技术难点有:
本文旨在为敏捷开发中快速实现多人在线协同编辑的需求提供一种只使用websocket的简单方法,其特点可以总结为以下几点:
前端Vue+后端Flask+geventwebsocket+数据库MySql
文档编辑器采用mavon-editor
因为仅仅使用了websocket,因此思路十分明确:
在websocket请求中存储有用户连接信息,发送接收是同一地址,可以使用request.environ.get('wsgi.websocket')
获取连接信息存入userlist中,直接进行监听以及发送即可。
在文档内容之外如果需要开发出其他的实时更新的消息可以使用json进行传输,标注各个信息在后端进行处理后再传输。当然处理多了会降低响应性。
经过实际操作,websocket传输似乎只支持字符串,所以前后端传输的必须是json格式的字符串,在发送前与接收后都需要相应的处理。
JSON.stringify(jsonobj)
JSON.parse(data)
json.dumps(obj)
json.loads(str)
实际运行发现后端json对象转字符串存在格式问题,前端不可读,所以直接手写json字符串解决。反正东西少,手写不烦
实现了实时更新正在编辑的用户以及文本内容
并没有处理连接断开操作
每次传送的值包括:
“username”:当前用户用户名(数据库用户名唯一)
“content”:修改后的文档内容
function contains(arr, obj) {
var i = arr.length;
while (i--) {
if (arr[i].username=== obj) {
return true;
}
}
return false;
}
判断用户列表有没有收到的用户,如果进行修改的是新用户,加入正在编辑的用户列表
写在methods中
initWebSocket(){ //初始化weosocket
const wsuri = "ws://ip:端口/conn";
this.websock = new WebSocket(wsuri);
this.websock.onmessage = this.websocketonmessage;
this.websock.onopen = this.websocketonopen;
this.websock.onerror = this.websocketonerror;
this.websock.onclose = this.websocketclose;
},
websocketonopen(){ //连接建立之后执行send方法发送数据
console.log('连接成功!')
},
sendcontent(){
this.sendjson.content=this.content;/修改后文档内容
this.sendjson.username=localStorage.getItem('token')//当前用户用户名,存储在本地localStorage中
this.websocketsend(JSON.stringify(this.sendjson));//传json字符串
},
websocketonerror(){//连接建立失败重连
this.initWebSocket();//不断尝试重连
},
websocketonmessage(e){ //数据接收
var jsondata=JSON.parse(e.data.replace(/\n/g,"\\n").replace(/\r/g,"\\r"));//换行处理
this.content=jsondata.content;//覆盖
if(contains(this.userList,jsondata.username)){
console.log("用户已存在")
}
else {
var tmp={
"username":jsondata.username
}
this.userList.push(tmp);//添加新的用户
}
},
websocketsend(Data){//数据发送
this.websock.send(Data);
},
websocketclose(){ //关闭
//console.log('断开连接');
},
与methods同级
watch: {
content() {//content是绑定的文本编辑器中内容
this.sendcontent();
},
},
最后在mounted中执行this.initWebSocket();
即可
#!/usr/bin/python3
# -*- coding: utf8 -*-
from flask import Flask,request
import json
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
from geventwebsocket.websocket import WebSocket #这条做语法提示用
app = Flask(__name__)
users = set()
@app.route('/conn')
def index():
wsock = request.environ.get('wsgi.websocket')//获取连接信息
print(wsock)
users.add(wsock)//加入用户集合
while True:
message = wsock.receive()
if message:
obj=json.loads(message)
username=obj['username']
content=obj['content']
res="{\"username\":\""+username+"\",\"content\":\""+content+"\"}"
#手写json字符串
for user in users:
if user!=wsock:
try:
print(res)
user.send(res)//传送json字符串
except:
print("用户断开连接")
users.remove(wsock)
if __name__ == '__main__':
# app.run()
#在APP外封装websocket
http_serv = WSGIServer(("0.0.0.0",端口),app,handler_class=WebSocketHandler)
# 启动服务
http_serv.serve_forever()
放至服务器上独立运行,注意安装Flask环境与geventwebsocket环境
可以使用nohup保持程序运行
用户之间编辑必定会存在时间上的差别,经过测试,当两个用户编辑时差在0.2秒之内时,会造成websocket接受与传送的冲突。即能实现0.2秒之外的多人同时文档编辑,人越多这个时间要求会更长。
因为没上锁,也没有合并,所以两个用户修改内容不同的情况下,在后端响应性上限之外的同步操作必定会产生冲突。
假设当前文档内容只有一个"你"字,用户A输入了“好”,成为”你好“,同时用户B输入了”坏“,成为”你坏“。因为修改必定有时间差,所以我们假设A先修改,那么后端收到”你好“,将”你好“发送给B用户。然而此时”你好“没有覆盖掉B的”你“,B就输入了”坏“,将”你坏“发送给了后端,然后B这边接受到了”你好“,将”你好“覆盖掉了”你坏“。后端接受到”你坏“,发送给A,此时A的”你好“被覆盖成了”你坏“,再次监听到修改再次发送”你坏“给后端。
如此循环,A用户和B用户的文档内容将不断地在”你好“和”你坏“之间切换,造成了冲突,死循环。
data.replace(/\n/g,"\\n").replace(/\r/g,"\\r")
进行换行处理