目录
1 什么是websocket
2 实现步骤
2.1 导入依赖
2.2 编写代码
1 什么是websocket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它主要用于在客户端和服务器之间建立持久的连接,允许实时数据交换。WebSocket 的设计目的是为了提高 Web 应用程序的交互性,减少延迟和带宽的使用。
-
全双工通信:客户端和服务器可以同时发送和接收数据,而不需要等待对方完成发送。
-
持久连接:建立一次连接后,可以保持该连接,直到主动关闭。这比传统的 HTTP 请求/响应模型更加高效。
-
低延迟:由于不需要为每个请求建立新的连接,WebSocket 可以显著减少延迟。
-
节省带宽:在 WebSocket 中,只有数据被发送而不需要携带大量的头部信息,这减少了带宽的消耗。
2 实现步骤
实施前提:默认在springBoot环境下实施
2.1 导入依赖
<!--WebSocket依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version> 3.3.4</version>
</dependency>
2.2 编写代码
WebSocketConfig:主要实现websocket的一些配置
package com.hyh.admin.config.websocket;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
* WebSocket配置
* @author hyh
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements ServletContextInitializer {
/*
* ServerEndpointExporter 作用
* 这个Bean会自动注册使用@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
/*
* 解除websocket对数据大小的限制
* @param servletContext Servlet上下文
*
*/
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 解除websocket对数据大小的限制
servletContext.setInitParameter("org.apache.tomcat.websocket.textBufferSize","10240000");
servletContext.setInitParameter("org.apache.tomcat.websocket.binaryBufferSize","10240000");
}
}
WebSocketSingleServe:具体的实现聊天的实时代码需求
package com.hyh.admin.config.websocket;
import com.hyh.ad.common.core.domain.model.SysUser;
import com.hyh.admin.config.websocket.context.SpringBeanContext;
import com.hyh.admin.domain.Messages;
import com.hyh.admin.service.MessageService;
import com.hyh.admin.sys.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* WebSocket 单聊服务端
*/
@ServerEndpoint("/singleChat/{username}")
@Component
public class WebSocketSingleServe implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(WebSocketSingleServe.class);
// 记录当前在线的连接
public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(@PathParam("username") String username, Session session) {
// 将用户的session放入map中
session.getUserProperties().put("username", username);
sessionMap.put(username, session);
log.info("用户:{}",session.getUserProperties().get("username"));
log.info("用户:{} 连接成功,session:{},总数:{}", username, session.getId(), sessionMap.size());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(Session session) {
try {
sessionMap.values().remove(session);
log.info("连接关闭,session:{},总数:{}", session.getId(), sessionMap.size());
} catch (Exception e) {
log.error("连接关闭异常:{}", e.getMessage());
}
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session fromSession) {
// 假设消息格式为 "username:file:data"
String[] parts = message.split(":", 3);
if (parts.length == 3) {
String targetUsername = parts[0].trim(); // 目标用户
String type = parts[1].trim(); // 消息类型(text/file)
String content = parts[2].trim(); // 消息内容
log.info("收到消息:{},类型:{},内容:{}", targetUsername, type, content);
// 根据类型处理消息
if ("text".equals(type)) {
// 发送文本消息
sendMessageToUser(targetUsername, content, type);
} else if ("image".equals(type)) {
// 发送文件消息
sendFileToUser(targetUsername, content, type);
}else if ("file".equals(type)) {
// 发送文件消息
sendFileToUser(targetUsername, content, "file");
}
// 消息持久化
String username = (String) fromSession.getUserProperties().get("username");
saveMessage(username, targetUsername, content, type);
}
}
/*
* 消息持久化
*/
private void saveMessage(String sendUsername, String targetUsername, String msg, String type) {
// 保存消息
try {
MessageService messageService = SpringBeanContext.getContext().getBean(MessageService.class);
ISysUserService sysUserService = SpringBeanContext.getContext().getBean(ISysUserService.class);
SysUser targetUser = sysUserService.selectUserByUserName(targetUsername);
Long targetUserId = targetUser.getId();
SysUser sendUser = sysUserService.selectUserByUserName(sendUsername);
Long userId = sendUser.getId();
Messages messages = new Messages();
messages.setSenderId(userId);
messages.setReceiverId(targetUserId);
messages.setContent(msg);
messages.setMessageType(type); // 保存消息类型
messageService.addMessage(messages);
log.info("消息持久化成功");
} catch (Exception e) {
log.error("消息持久化失败:{}", e.getMessage());
}
}
/*
* 发送文件给用户
*/
private void sendFileToUser(String targetUsername, String fileContent, String type) {
Session targetSession = sessionMap.get(targetUsername);
if (targetSession != null) {
try {
targetSession.getBasicRemote().sendText(type + "|" + fileContent); // 文件发送格式
log.info("发送文件给用户:{},发送成功", targetUsername);
} catch (IOException e) {
log.error("发送文件失败:{}", e.getMessage());
}
}
}
/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误,session:{} ,错误信息:{}", session.getId(), error);
}
/**
* 服务端发送消息给指定用户
* @param username 目标用户
* @param message 消息内容
*/
public void sendMessageToUser(String username, String message, String type) {
Session session = sessionMap.get(username);
if (session != null && session.isOpen()) {
try {
session.getBasicRemote().sendText(type + "|" + message);
log.info("发送给用户:{},内容:{}", username, message);
} catch (IOException e) {
log.error("发送消息失败:{}", e.getMessage());
}
} else {
log.warn("用户:{} 不在线,无法发送消息", username);
}
}
@Override
public void afterPropertiesSet() throws Exception {
log.info("WebSocket服务端启动");
}
}
onopen方法主要用于连接的的方法,所有和websocket发起连接的客户端都会经过这个方法。
onmessage方法主要用于发送消息的方法,其中定义了发送消息的格式,可以自行定义。
前端代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>聊天界面</title>
<style>
body { font-family: Arial, sans-serif; }
#messages { border: 1px solid #ccc; height: 300px; overflow-y: scroll; margin-bottom: 10px; }
input, button { margin: 5px; }
</style>
</head>
<body>
<h2>聊天界面</h2>
<input type="text" id="targetUser" placeholder="输入目标用户名...">
<input type="text" id="message" placeholder="输入消息...">
<button id="sendBtn">发送</button>
<div id="messages"></div>
<img src="https://c-ssl.duitang.com/uploads/item/202003/27/20200327141738_ulbvu.jpg" alt="">
<script>
const username = prompt("请输入您的用户名:"); // 获取当前用户的用户名
const socket = new WebSocket(`ws://127.0.0.1:8088/singleChat/${username}`);
socket.onopen = function() {
console.log(`${username} 已连接`);
};
socket.onmessage = function(event) {
const messagesDiv = document.getElementById("messages");
messagesDiv.innerHTML += `<p>${event.data}</p>`;
messagesDiv.scrollTop = messagesDiv.scrollHeight; // 滚动到底部
};
document.getElementById("sendBtn").onclick = function() {
const targetUser = document.getElementById("targetUser").value;
const messageInput = document.getElementById("message").value;
const message = `${targetUser}:text:${messageInput}`; // 格式化消息
socket.send(message);
// 显示自己发送的消息
const messagesDiv = document.getElementById("messages");
messagesDiv.innerHTML += `<p>我: ${messageInput}</p>`;
messagesDiv.scrollTop = messagesDiv.scrollHeight; // 滚动到底部
document.getElementById("message").value = ""; // 清空输入框
};
</script>
</body>
</html>
vue的部分代码和项目完整的截图为:
this.socket = new WebSocket(
`ws://127.0.0.1:8088/singleChat/${localStorage.getItem("username")}`
);
this.socket.onopen = () => {
console.log(localStorage.getItem("username") + " 连接成功");
};
// 只设置一次 onmessage 处理逻辑
this.socket.onmessage = (event) => {
const message = event.data; // 假设格式为 "type:content"
const parts = message.split("|"); // 按冒号分割
if (parts.length === 2) {
const type = parts[0].trim(); // 消息类型
const content = parts[1].trim(); // 消息内容
this.contactRecord.push({
id: Date.now(), // 使用时间戳作为消息 ID
senderId: this.user.id, // 或者其他用户的 ID
content: content,
messageType: type, // 添加类型
});
// 进度条滚动到底部
this.scrollToBottom();
}
};
},
需要源码的请私信我:
谢谢各位的支持!!!