springboot 中 webSocket 学习记录
依赖
<!--WebSocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
添加 websocket 配置类
@Configuration
@EnableWebSocket // 开启 WebSocket 支持
public class WebSocketConfig {
/**
* 必须要有的
*
* @return serverEndpointExporter
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
/**
* WebSocket 配置信息
*
* @return servletServerContainerFactoryBean
*/
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean bean = new ServletServerContainerFactoryBean();
// 文本缓冲区大小
bean.setMaxTextMessageBufferSize(8192);
// 字节缓冲区大小
bean.setMaxBinaryMessageBufferSize(8192);
return bean;
}
}
需求代码
注意路径放行,特别是使用 Spring Security 或 Shiro,很容易遗忘放行请求路径
@Component
@ServerEndpoint("/websocket")
public class BaseWebSocket {
Logger logger = LoggerFactory.getLogger(this.getClass());
// ConcurrentHashMap, 保证线程安全, static全局共享 session
// 这里之所以static,是因为这个类不是单例的!!
// 它虽然有@Controller注解,但是不适用Ioc容器中拿对象,每一次请求过来都是一个新的对象
/**
* 存放 session
*/
public final static Map<String, Session> SESSION_MAP = new ConcurrentHashMap<>();
/**
* 存放 userName
*/
public final static Map<String, String> USERNAME_MAP = new ConcurrentHashMap<>();
/**
* 连接时触发
*
* @param session session
* @param config config
*/
@OnOpen
public void openSession(Session session, EndpointConfig config) {
// 将session存起来, 用于服务器向浏览器发送消息
SESSION_MAP.put(session.getId(), session);
//getQueryString获取查询字符串。
String queryStrin = session.getQueryString();
//解决使用.getQueryString方法获取到路径信息后乱码问题
String queryString = URLDecoder.decode(queryStrin, StandardCharsets.UTF_8);
String name = queryString.substring(queryString.indexOf("=")+1);
USERNAME_MAP.put(session.getId(),name);
String res = "OnOpen [" + name + "]进入房间";
sendAll(res);
logger.info(res);
}
/**
* 有消息时触发
*
* @param session session
* @param message message
*/
@OnMessage
public void onMessage(Session session, String message) {
String res = "OnMessage [" + session.getId() + "]" + message;
sendAll(res);
logger.info(res);
}
/**
* 响应字节流
*
* @param session session
* @param message message
*/
@OnMessage
public void onMessage(Session session, byte[] message) {
// 这个以后再说
}
/**
* 连接关闭时触发
*
* @param session session
* @param closeReason closeReason
*/
@OnClose
public void closeSession(Session session, CloseReason closeReason) {
//记得移除相对应的session
SESSION_MAP.remove(session.getId());
String res = "OnClose [" + USERNAME_MAP.get(session.getId()) + "]离开了房间";
USERNAME_MAP.remove(session.getId());
sendAll(res);
logger.info(res);
}
/**
* 有异常时触发
* @param session
* @param throwable
*/
@OnError
public void sessionError(Session session, Throwable throwable) {
// 通常有异常会关闭 session
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* sendAll
*
* @param message message
*/
public void sendAll(String message) {
for (Session s : SESSION_MAP.values()) {
// 获得session发送消息的对象
// Basic是同步, 会阻塞
// Async是异步, 这个会有多线程并发导致异常, 发送消息太快也会有并发异常, 需要有 消息队列 来辅助使用
final RemoteEndpoint.Basic remote = s.getBasicRemote();
try {
// 发送消息
remote.sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>websocket-demo</title>
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.2.1/css/bootstrap.min.css">
</head>
<body>
<div class="container py-3">
<div class="row">
<div class="col-6">
<div>
<label for="messageArea">聊天信息:</label>
</div>
<div>
<textarea id="messageArea" readonly class="w-100" style="height: 75vh;"></textarea>
</div>
</div>
<div class="col">
<div class="my-1">
<label for="messageArea">用 户 名:</label>
</div>
<div class="my-1">
<input type="text" id="username" autocomplete="off">
</div>
<div class="my-1">
<button class="btn-info" id="joinRoomBtn">进入聊天室</button>
<button class="btn-warning" id="leaveRoomBtn">离开聊天室</button>
</div>
<hr/>
<div class="my-1">
<label for="sendMessage">输入消息:</label>
</div>
<div>
<textarea id="sendMessage" rows="5" class="w-100" style="max-height: 50vh"></textarea>
</div>
<div class="my-1">
<button class="btn-primary" id="sendBtn">发送消息</button>
</div>
</div>
</div>
</div>
]
<script src="https://s3.pstatp.com/cdn/expire-1-M/jquery/3.3.1/jquery.min.js"></script>
<script>
let webSocket;
// ip和端口号用自己项目的
// {websocket}: 其实是刚刚那个@ServerEndpoint("/websocket")中定义的
let url = 'ws://localhost:8081/test/websocket';
$('#username').keyup(function (e) {
let keycode = e.which;
if (keycode === 13) {
$('#joinRoomBtn').click();
}
});
// 进入聊天室
$('#joinRoomBtn').click(function () {
let username = $('#username').val();
//判断当前浏览器是否支持WebSocket
if('WebSocket' in window){
webSocket = new WebSocket(url+"?username="+username);
}else{
alert('Not support websocket')
}
webSocket.onopen = function () {
console.log('webSocket连接创建。。。');
}
webSocket.onclose = function () {
console.log('webSocket已断开。。。');
$('#messageArea').append('websocket已断开\n');
}
webSocket.onmessage = function (event) {
$('#messageArea').append(event.data + '\n');
}
webSocket.onerror = function (event) {
console.log(event)
console.log('webSocket连接异常。。。');
}
});
// 退出聊天室
$('#leaveRoomBtn').click(function () {
if (webSocket) {
// 关闭连接
webSocket.close();
}
});
// 发送消息
$('#sendBtn').click(function () {
var msg = $('#sendMessage').val();
if (msg.trim().length === 0) {
alert('请输入内容');
return;
}
webSocket.send($('#sendMessage').val());
$('#sendMessage').val('');
});
// 监听窗口关闭事件,当窗口关闭时
// 主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
if (webSocket) {
// 关闭连接
webSocket.close();
}
};
</script>
</body>
</html>
自动推送信息
1、 session 共享
session 共享后拿到 session 即可推送
2、建立客户端
/**
* websocket 客户端
*/
@ClientEndpoint
public class WebSocketClient {
@OnOpen
public void onOpen(Session session) {
// 打开通信通道时发生
System.out.println("client onOpen");
}
@OnMessage
public void onMessage(String message, Session session) {
// 处理接收到的消息
System.out.println("client onMessage");
}
@OnClose
public void onClose(Session session, CloseReason reason) {
// 关闭连接
System.out.println("client onClose");
}
@OnError
public void onError(Session session, Throwable error) {
// 错误处理
System.out.println("client onError");
}
}
客户端与服务端链接,然后用客户端发送信息给服务端,服务端再次发送信息
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
Session session = container.connectToServer(WebSocketClient.class, URI.create("ws://localhost:8081/test/websocket?username=admin"));
session.getBasicRemote().sendText("[系统通知] ... ...");
原文参考: https://blog.csdn.net/weixin_43874301/article/details/129348769