目录
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*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}/** 解除websocket对数据大小的限制* @param servletContext Servlet上下文**/@Overridepublic 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<>();/*** 连接建立成功调用的方法*/@OnOpenpublic 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());}/*** 连接关闭调用的方法*/@OnClosepublic 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 客户端发送过来的消息*/@OnMessagepublic 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());}}}/*** 发生错误时调用*/@OnErrorpublic 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);}}@Overridepublic 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(), // 使用时间戳作为消息 IDsenderId: this.user.id, // 或者其他用户的 IDcontent: content,messageType: type, // 添加类型});// 进度条滚动到底部this.scrollToBottom();}};},
需要源码的请私信我:
谢谢各位的支持!!!