在现代应用中,实时协作已经成为了非常重要的功能,尤其是在文档编辑、聊天系统和在线编程等场景中。通过实时共享文档,多个用户可以同时对同一份文档进行编辑,并能看到其他人的编辑内容。这种功能广泛应用于 Google Docs、Notion 等产品中。
在本文中,我们将实现一个简单的共享文本框,使用 WebSocket 技术来实现多人实时编辑同一份文本。通过 WebSocket 协议,客户端和服务器可以保持一个持续的连接,使得文档的内容能够实时同步到所有参与者。
(引流:https://juejin.cn/post/7445187277558628387)
效果图如下:
1. 什么是 WebSocket?
WebSocket 是一种网络协议,它提供了一个全双工的通信通道,允许客户端和服务器之间进行实时、双向的数据传输。与传统的 HTTP 协议不同,WebSocket 连接在建立后会保持打开状态,不需要频繁的建立连接,从而大大提高了数据交换的效率。
WebSocket 协议通常用于实时聊天、在线游戏、金融行情推送等场景。在本文中,我们将利用 WebSocket 来实现一个共享文本框。
2. 项目需求
我们的目标是实现一个简单的共享文本框功能,要求如下:
- 多个用户可以同时连接到同一个文档并进行编辑。
- 每次用户编辑文本时,修改内容会即时同步到其他用户的浏览器。
- 实现基本的文本框功能,包括输入和显示。
3. 技术栈
- 前端:HTML、CSS、JavaScript(使用 WebSocket API)
- 后端:SpringBoot
- 通信协议:WebSocket
4. 实现步骤
4.1 搭建 WebSocket 服务端
首先,我们需要创建一个 WebSocket 服务器来处理客户端连接。具体步骤如下:
- 是maven依赖中引入websocket的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 对WebSocket进行一些配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @ Description: 开启WebSocket支持
* 用于在Spring框架的应用中配置和启用WebSocket功能。
* 通过相关注解和方法的定义,使得应用能够正确地处理WebSocket连接和通信。
*/
@Configuration
public class WebSocketConfig {
//Bean生命周期的初始化
// 用于将方法返回的ServerEndpointExporter对象作为一个Bean注册到Spring的容器中
@Bean
public ServerEndpointExporter serverEndpointExporter() {
//创建并返回一个ServerEndpointExporter对象。
// ServerEndpointExporter主要作用是扫描带有@ServerEndpoint注解的WebSocket端点类,并将它们注册到Servlet容器中,
// 从而使得应用能够正确地处理WebSocket连接请求,实现WebSocket的通信功能。
return new ServerEndpointExporter();
}
}
- WebSocket服务器实现
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.stereotype.Service;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@Service
@ServerEndpoint("/api/websocket/sharedText/{sid}")
public class WebSocketServer {
// 每个连接的 Session
private Session session;
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
private static int onlineCount = 0;
// 存储每个连接的 sid
private String sid = "";
// 存储每个连接的内容
private static String content = "";
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
this.session = session;
// 使用 URL 中的 sid 参数为当前连接设置 sid
this.sid = sid;
webSocketSet.add(this); // 将当前连接添加到 WebSocket 客户端集合中
addOnlineCount(); // 增加在线连接数
try {
sendMessage(this.content); // 向当前客户端发送连接成功消息
System.out.println("有新窗口开始监听:" + sid + ", 当前在线人数为:" + getOnlineCount());
} catch (IOException e) {
System.out.println("websocket IO Exception");
}
}
@OnClose
public void onClose() {
webSocketSet.remove(this); // 从 WebSocket 客户端集合中移除当前连接
subOnlineCount(); // 减少在线连接数
System.out.println("释放的 sid 为:" + sid);
System.out.println("有一连接关闭!当前在线人数为 " + getOnlineCount());
}
@OnMessage
public void onMessage(String message, Session session) throws JsonProcessingException {
this.content = message;
// 打印来自某个 sid 的消息内容
System.out.println("收到来自窗口 " + sid + " 的信息: " + message);
// 群发消息给所有已连接的客户端
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message); // 向所有连接的客户端广播消息
} catch (IOException e) {
e.printStackTrace();
}
}
}
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
// 向客户端发送消息
public void sendMessage(String message) throws IOException {
if (this.session != null && this.session.isOpen()) {
this.session.getBasicRemote().sendText(message); // 发送消息
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
}
上述代码中,我们创建了一个 WebSocket 服务器并监听了 8080 端口。当有客户端连接时,服务器会触发 connection
事件,处理来自客户端的消息并将其广播给所有已连接的客户端。
4.2 创建前端页面
接下来,我们需要创建一个前端页面,用户可以在其中输入文本并实时看到其他用户的编辑内容。我们将使用 WebSocket API 与后端建立连接。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实时共享文本框 - WebSocket 实现</title>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f9;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
h2 {
margin-bottom: 20px;
}
#textBox {
width: 80%;
max-width: 900px;
height: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #fff;
resize: none;
box-sizing: border-box;
}
#message {
margin-top: 20px;
padding: 10px;
width: 80%;
max-width: 900px;
border: 1px solid #ddd;
border-radius: 5px;
background-color: #fafafa;
font-size: 14px;
color: #555;
}
#message span {
font-weight: bold;
}
.status {
margin: 10px 0;
color: #333;
}
.error {
color: red;
}
.success {
color: green;
}
.info {
color: #555;
}
</style>
</head>
<body>
<h2>实时共享文本框</h2>
<textarea id="textBox" rows="20" cols="80" placeholder="开始编辑文本..."></textarea><br />
<script type="text/javascript">
let websocket = null;
const sid = "100"; // 这里可以更改为动态获取的 sid,例如通过 URL 获取
// 判断浏览器是否支持 WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket(`ws://192.168.113.45:8080/api/websocket/sharedText/${sid}`);
} else {
alert('当前浏览器不支持 WebSocket');
}
// 连接错误时处理
websocket.onerror = () => {
updateStatus('WebSocket连接发生错误', 'error');
};
// 连接成功时处理
websocket.onopen = () => {
updateStatus('WebSocket连接成功', 'success');
};
// 接收消息时处理
websocket.onmessage = (event) => {
console.log(event);
updateTextBox(event.data);
};
// 连接关闭时处理
websocket.onclose = () => {
updateStatus('WebSocket连接关闭', 'info');
};
// 窗口关闭时确保关闭 WebSocket 连接
window.onbeforeunload = () => {
closeWebSocket();
};
// 更新状态消息
function updateStatus(message, type) {
const statusDiv = document.getElementById('message');
statusDiv.innerHTML = `<span class="${type}">${message}</span>`;
}
// 关闭 WebSocket 连接
function closeWebSocket() {
if (websocket) {
websocket.close();
}
}
// 监听文本框输入事件
document.getElementById('textBox').addEventListener('input', function () {
const message = this.value;
if (message !== previousMessage) {
websocket.send(message); // 发送消息到 WebSocket
previousMessage = message; // 更新当前文本
}
});
let previousMessage = ''; // 用于记录文本框内容,避免重复发送
// 更新文本框内容
function updateTextBox(content) {
// 防止不停地将同一内容发送给其他用户
if (document.getElementById('textBox').value !== content) {
document.getElementById('textBox').value = content;
}
}
</script>
</body>
</html>
在前端页面中,我们创建了一个简单的文本框 (<textarea>
) 供用户输入文本。当用户在文本框中输入内容时,input
事件会触发,内容会通过 WebSocket 发送给服务器。服务器收到消息后,会将其广播给所有其他连接的客户端,客户端接收到广播消息后会更新自己的文本框内容。
4.3 测试与运行
直接启动SpringBoot服务即可,同时打开web网页。
最终效果如下:
在一个网页端编辑,另一个网页端能及时收到变更。
5. 小结
通过这篇博客,我们实现了一个简单的实时共享文本框,利用 WebSocket 技术来实现多人实时编辑同一份文本。每当一个用户编辑文本时,服务器会将该编辑广播给其他在线用户,从而实现实时同步。这是一个基本的多人协作编辑功能,适用于在线文档编辑、聊天系统等场景。
在实际应用中,我们可以根据需求扩展更多功能,例如用户身份管理、权限控制、文本格式化、撤销/重做功能等。通过 WebSocket,我们不仅可以实现实时通信,还能为用户提供流畅的协作体验。在开发中,WebSocket 仍然是一个非常强大的工具,适用于许多实时协作的场景。