使用websocket实现协同编辑
作者:互联网
1、协同编辑的意思是什么?
其实,协同编辑无非就是字面意思,多人同时编辑,并且能够同步看到对方问保存的数据,典型的例子可以参考石墨文档,腾讯文档。
2、技术解决
核心技术就是信息的实时通信
以及多人编辑时所产生的冲突
这里我采用websocket来进行实时通信,大家都知道他是一个全双工通信协议,经过时间的考证,还是非常好用的,多数流行语言都有与之响应封装好的软件包
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。详细的概念可以百度自行查找,比比皆是
编辑冲突的问题可以使用合并算法,上锁等技术(这里没有做过多的研究,所以我使用下面的方式,嘻嘻)
编辑冲突问题交给用户,当前用户实时地看到了别人正在编辑,那么当前用户就自觉性地停止编辑。
3、实现思路
1. 用户打开图像编辑页面,与后端建立长连接。
2. 后端将当前用户加入当前图像编辑列表。
3. 前端监听用户对于图像内容的修改,每一次修改将整个修改内容发送给后端。
4. 后端接收到信息,不做任何处理,直接将图像信息发送给图像编辑用户列表中其他的所有用户。
5. 前端收到后端的文本信息直接覆盖掉当前图像内容。
4、示例
废话就不多说了,直接上代码(复制粘贴即可使用呦)
这里是使用java语言编写的,采用的是原生注解方式,还有其他实现方式,在这里不一一介绍了(主要是上百度搜的没几个能用的,不是相关代码不全,就是长篇大论,最后还是不行,我实在是不会用啊!!!)
首先我们要使用websocket,肯定是要在pom里导入依赖包的(maven无法解析的,可以添加一下响应的版本号)
<!-- webscoket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
创建MyWebSocketConfig文件来注入bean
package com.example.javawebsocket; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; //@Configuration注解标识的类中声明了1个或者多个@Bean方法,Spring容器可以使用这些方法来注入Bean @Configuration public class MyWebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
接下来就是创建一个原生注解的文件
package cn.staitech.system.utils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import cn.staitech.common.security.utils.SecurityUtils; import com.alibaba.fastjson.parser.JSONToken; import io.swagger.models.auth.In; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Component; @Component //主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端 @ServerEndpoint(value = "/websocket/{imageid}") //此注解相当于设置访问URL public class WebSocketServer { /** * 与某个客户端的连接会话,需要通过它来给客户端发送数据 */ private Session session; private String userName; /** concurrent包的线程安全Set,用来存放每个客户端对应的CumWebSocket对象。*/ private static CopyOnWriteArraySet<WebSocketServer> webSockets =new CopyOnWriteArraySet<>(); /**为了保存在线用户信息,在方法中新建一个list存储一下【实际项目依据复杂度,可以存储到数据库或者缓存】**/ private static Map<Long,Session> sessionPool = new HashMap<Long,Session>(); private static Map<Integer, CopyOnWriteArraySet<WebSocketServer>> newwebSockets = new HashMap<Integer,CopyOnWriteArraySet<WebSocketServer>>(); /** * 建立连接 * @param session * @param userName */ @OnOpen public void onOpen(Session session,@PathParam(value = "imageid") Integer imageid) throws IOException { Long userid = SecurityUtils.getUserId(); this.session = session; webSockets.add(this); // 如果id不存在,创建一个新的用户存储池,格式为 图像id:[用户1,用户2] if(! newwebSockets.containsKey(imageid)){ CopyOnWriteArraySet<WebSocketServer> webSocketslist =new CopyOnWriteArraySet<>(); newwebSockets.put(imageid,webSocketslist); newwebSockets.get(imageid).add(this); }else{ newwebSockets.get(imageid).add(this); } sessionPool.put(userid, session); Session res = sessionPool.get(userid); System.out.println(imageid+"【websocket消息】有新的连接,总数为:"+newwebSockets.get(imageid).size()); } /** * 断开连接 */ @OnClose public void onClose(@PathParam(value = "imageid") Integer imageid) { webSockets.remove(this); newwebSockets.get(imageid).remove(this); System.out.println(imageid + "【websocket消息】连接断开,总数为:"+newwebSockets.get(imageid).size()); } /** * 发送错误 * @param session * @param error */ @OnError public void one rror(Session session, Throwable error) { System.out.println("[连接ID:{}] 错误原因:{}" + this.session + error.getMessage()); } /** * 收到信息 */ @OnMessage public String onMessage(String message) { System.out.println("【websocket消息】收到客户端消息:"+message); return message; } // 此为广播消息 public void sendAllMessage(String message,Integer imageid) { for(WebSocketServer webSocket : newwebSockets.get(imageid)) { System.out.println("【websocket消息】广播消息:"+message); try { webSocket.session.getAsyncRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } // 此为单点消息 public void sendOneMessage(String userName, String message) { System.out.println("【websocket消息】单点消息:"+message); System.out.println(sessionPool); Session session = sessionPool.get(userName); if (session != null) { try { session.getAsyncRemote().sendText(message); } catch (Exception e) { e.printStackTrace(); } } } }
我们我两个接口来调用一下上面两个接口
package com.example.javawebsocket; import com.example.javawebsocket.WebSocketServer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/annotation") public class Websocket { @Autowired private WebSocketServer webSocketServer; // @GetMapping("/sendAllWebSocket") // public String test() { // String text="你们好!这是websocket群体发送!"; // webSocketServer.sendAllMessage(text); // return text; // } @GetMapping("/sendOneWebSocket/{userName}") public String sendOneWebSocket(@PathVariable("userName") String userName) { String text=userName+" 你好! 这是websocket单人发送!"; webSocketServer.sendOneMessage(userName,text); return text; } @CrossOrigin @RequestMapping(value = "/newimage",method = RequestMethod.POST) public String newimage(@RequestBody socketvo req){ webSocketServer.sendAllMessage(req.getImagename(), req.getImageid()); System.out.println(req.getImagename()); return "ok"; } }
基本上大致就是酱紫了,可以根据不同需求来自行更改
标签:协同,websocket,String,imageid,编辑,session,import,public 来源: https://www.cnblogs.com/gengjiangtao/p/16635014.html