Spring Boot + WebSocket实现聊天
程序员文章站
2022-05-17 09:28:40
...
Spring Boot + WebSocket 实现聊天
采用全注解方式实现websocket服务端聊天
环境
- spring-boot-starter-parent-2.0.8.RELEASE
- Java 1.8
- maven 3.5.+
可根据具体实际情况进行版本的替换选择
依赖
websocket的依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.8.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring boot WebSocket相关包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.73</version>
</dependency>
</dependencies>
<!-- spring boot 插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
实现websocket服务端
package com.tianya.springboot.websocket.server;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.tianya.springboot.websocket.handle.AppIdMsgHandle;
import lombok.extern.slf4j.Slf4j;
/**
* @description
* WebSocket 服务端
* 每种应用下,对应的会话连接
* <pre>key: appId 不同应用,
* 如:
* msg对应消息推送,
* log对应日志推送等,
* chat对应聊天信息推送</pre>
* @author TianwYam
* @date 2020年9月17日下午1:47:22
*/
@Slf4j
@Service
@ServerEndpoint(value = "/ws/{appId}")
public class WebSocketServer {
// 自动注入消息处理的实现
// @Autowired 在这儿自动注入会报空指针异常,取不到
private static Set<AppIdMsgHandle> appIdMsgHandles ;
/*** 每种应用下,对应的会话连接 */
private static final ConcurrentHashMap<String, LinkedHashSet<Session>> APPID_CONNECT_SESSION_MAP = new ConcurrentHashMap<>();
/**
* @description
* session 连接 开始
* @author TianwYam
* @date 2020年9月17日下午2:27:12
* @param session 会话连接
* @param appId 应用ID
*/
@OnOpen
public void open(Session session, @PathParam(value = "appId") String appId) {
// 获取此应用下所有连接
LinkedHashSet<Session> sessions = getSessions(appId);
if (sessions == null) {
sessions = new LinkedHashSet<>();
APPID_CONNECT_SESSION_MAP.put(appId, sessions);
}
// 新会话session 连接成功
sessions.add(session);
log.info("新连接进入,目前[{}]总人数:{}", appId, sessions.size());
}
/**
* @description
* session 连接关闭
* @author TianwYam
* @date 2020年9月17日下午3:32:45
* @param session
* @param appId
*/
@OnClose
public void close(Session session, @PathParam(value = "appId") String appId) {
// 获取此应用下所有连接
LinkedHashSet<Session> sessions = getSessions(appId);
if (sessions != null) {
// 会话session 连接断开
sessions.remove(session);
log.info("连接断开,目前[{}]总人数:{}", appId, sessions.size());
}
}
/**
* @description
* 接受消息
* @author TianwYam
* @date 2020年9月17日下午3:39:22
* @param message 消息内容
* @param session 会话
* @param appId 应用
*/
@OnMessage
public void onMessage(String message, Session session, @PathParam(value = "appId") String appId) {
// 消息处理
for (AppIdMsgHandle appIdMsgHandle : appIdMsgHandles) {
if (Objects.equals(appIdMsgHandle.getAppId(), appId)) {
appIdMsgHandle.handleMsg(message, session);
}
}
}
/**
* @description
* 连接 报错
* @author TianwYam
* @date 2020年9月17日下午3:50:12
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("websocket发生错误", error);
}
/**
* @description
* 根据应用获取所有在线人员
* @author TianwYam
* @date 2020年9月17日下午3:17:04
* @param appId 应用ID
* @return
*/
public static LinkedHashSet<Session> getSessions(String appId){
return APPID_CONNECT_SESSION_MAP.get(appId);
}
/**
* @description
* 发送文本消息
* @author TianwYam
* @date 2020年9月17日下午3:52:52
* @param session
* @param message
*/
public static void sendTxtMsg(Session session , String message) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("websocket发生消息失败", e);
}
}
/**
* @description
* <pre>
* WebSocket是线程安全的,有用户连接时就会创建一个新的端点实例,
* 一个端点只能保证一个线程调用。总结就是,WebSocket是多例的。
* Autowired 是程序启动时就注入进去
* 用静态变量来保存消息处理实现类,避免出现空指针异常
* 另一种方式就是 使用 ApplicationContext.getBean()的方式也可以获取
* </pre>
* @author TianwYam
* @date 2020年9月17日下午7:43:13
* @param appIdMsgHandles
*/
@Autowired
public void setAppIdMsgHandles(Set<AppIdMsgHandle> appIdMsgHandles) {
WebSocketServer.appIdMsgHandles = appIdMsgHandles;
}
}
具体消息处理
用于在不同场景下,实现不同的消息处理机制
消息处理接口
package com.tianya.springboot.websocket.handle;
import javax.websocket.Session;
import org.springframework.stereotype.Service;
/**
* @description
* 应用消息处理
* @author TianwYam
* @date 2020年9月17日下午4:07:03
*/
@Service
public interface AppIdMsgHandle {
/**
* @description
* 消息处理机制
* @author TianwYam
* @date 2020年9月17日下午4:45:36
* @param message
* @param session
*/
void handleMsg(String message, Session session);
/**
* @description
* 获取应用ID
* @author TianwYam
* @date 2020年9月17日下午4:45:48
* @return
*/
String getAppId() ;
}
用于聊天的:聊天消息处理实现类
package com.tianya.springboot.websocket.handle.impl;
import java.util.LinkedHashSet;
import javax.websocket.Session;
import org.springframework.stereotype.Service;
import com.tianya.springboot.websocket.handle.AppIdMsgHandle;
import com.tianya.springboot.websocket.server.WebSocketServer;
@Service
public class ChatAppIdMsgHandleImpl implements AppIdMsgHandle{
private static final String APP_ID = "chat" ;
@Override
public void handleMsg(String message, Session session) {
LinkedHashSet<Session> sessions = WebSocketServer.getSessions(APP_ID);
if (sessions != null) {
for (Session otherSession : sessions) {
// 排除自己
// if (!Objects.equals(otherSession, session)) {
WebSocketServer.sendTxtMsg(otherSession, message);
// }
}
}
}
@Override
public String getAppId() {
return APP_ID;
}
}
启动类
添加websocket的扫描 自动注解
package com.tianya.springboot.websocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
@SpringBootApplication
// 添加websocket的扫描 自动注解类
@EnableWebSocket
public class WebSocketApplication {
public static void main(String[] args) {
SpringApplication.run(WebSocketApplication.class, args);
}
}
额外websocket配置
若是使用spring boot内置 web容器 tomcat ,则需要添加此类,否则会报错
若是使用外置web容器,则可以不用添加
package com.tianya.springboot.websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* @description
* 若是使用spring boot内置 web容器 tomcat ,则需要添加此类,否则会报错
* 若是使用外置web容器,则可以不用添加
* @author TianwYam
* @date 2020年9月17日上午11:30:03
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
客户端
界面JS
$(function() {
var websocket;
// 首先判断是否 支持 WebSocket
if('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/ws/chat");
} else if('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://localhost:8080/ws/chat");
} else {
websocket = new SockJS("http://localhost:8080/ws/chat");
}
// 打开时
websocket.onopen = function(event) {
console.log(" websocket.onopen ");
console.log(event);
$("#msg").append("<p>(<font color='blue'>欢迎加入聊天群</font>)</p>");
};
// 处理消息时
websocket.onmessage = function(event) {
console.log(event);
$("#msg").append("<p>(<font color='red'>" + event.data + "</font>)</p>");
console.log(" websocket.onmessage ");
};
websocket.onerror = function(event) {
console.log(" websocket.onerror ");
};
websocket.onclose = function(event) {
console.log(" websocket.onclose ");
};
// 点击了发送消息按钮的响应事件
$("#TXBTN").click(function(){
// 获取消息内容
var text = $("#tx").val();
// 判断
if(text == null || text == ""){
alert(" content can not empty!!");
return false;
}
// 发送消息
websocket.send(text);
$("#tx").val('');
});
});
界面HTML
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocket聊天</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="renderer" content="webkit">
<!-- 引入 JQuery -->
<script type="text/javascript" src="js/jquery.min.js"></script>
<!-- 引入 sockJS -->
<script type="text/javascript" src="js/sockjs.min.js" ></script>
<!-- 自定义JS文件 -->
<script type="text/javascript" src="js/index.js"></script>
</head>
<body>
<!-- 最外边框 -->
<div style="margin: 20px auto; border: 1px solid blue; width: 300px; height: 500px;">
<!-- 消息展示框 -->
<div id="msg" style="width: 100%; height: 70%; border: 1px solid yellow;overflow: auto;"></div>
<!-- 消息编辑框 -->
<textarea id="tx" style="width: 98%; height: 20%;"></textarea>
<!-- 消息发送按钮 -->
<button id="TXBTN" style="width: 100%; height: 8%;">发送数据</button>
</div>
</body>
</html>
项目结构
聊天效果
用户A:
用户B:
原先也写了一篇关于: Spring+SpringMVC+WebSocket实现聊天
可以访问:Spring+SpringMVC+WebSocket
希望有所帮助到各位同仁,希望能给个star,多谢!
本文项目地址:spring-boot-learn下面的spring-boot-learn-websocket项目
此项目工程是基于maven多模块工程来记录学习springboot的知识的一个过程
推荐阅读
-
Spring Boot实现STOMP协议的WebSocket的方法步骤
-
详解Spring Boot实战之Filter实现使用JWT进行接口认证
-
微信小程序websocket实现聊天功能
-
使用Spring Boot和AspectJ实现方法跟踪基础结构
-
spring boot 2 集成JWT实现api接口认证
-
WebSocket实现简单客服聊天系统
-
Spring Boot整合WebSocket
-
Angular+Bootstrap+Spring Boot实现分页功能实例代码
-
Spring Boot+AngularJS+BootStrap实现进度条示例代码
-
PHP框架实现WebSocket在线聊天通讯系统