SpringWebsocket聊天室
这里用于从头搭建自己需要搭建私域交流聊天室, 用于可以内部定制处理, 数据备份写入 Redis 等待其他进程落地关系数据库.
<dependencies>
<!-- WebSocket库 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- spring 开发配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- ....... -->
</dependencies>
默认这样处理就行了, 主要是先搭建好基本框架. 先处理下 echo 流程.
package com.meteorcat.chats;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.lang.NonNull;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* 这里先采用 TextWebSocketHandler 来做接收处理
* TextWebSocketHandler 为文本流, 而 BinaryWebSocketHandler 为二进制流
*
* @author MeteorCat
*/
public class ChatRuntime extends TextWebSocketHandler {
/**
* 日志句柄
*/
protected final Log logger = LogFactory.getLog(this.getClass());
/**
* 当客户端服务端连接的回调切片(OnOpen)
*
* @param session WebSocket句柄
* @throws Exception 异常
*/
@Override
public void afterConnectionEstablished(@NonNull WebSocketSession session) throws Exception {
logger.debug("OnEstablished");
}
/**
* 当客户端服务端关闭的回调切片(OnClose)
*
* @param session WebSocket句柄
* @param status 关闭状态 null 为正常关闭
* @throws Exception 异常
*/
@Override
public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus status) throws Exception {
logger.debug("OnClose");
}
/**
* 二进制消息传递的回调, 采用 handleTextMessage
* 如果集成 BinaryWebSocketHandler 则是需要实现 handleBinaryMessage
*
* @param session WebSocket句柄
* @param message 传输的数据内容
* @throws Exception 异常
*/
@Override
public void handleTextMessage(@NonNull WebSocketSession session, @NonNull TextMessage message) throws Exception {
logger.debug("OnMessage");
// 这里 echo 一下返回给客户端确认调通
session.sendMessage(message);
}
}
之后就是挂载配置对象:
package com.meteorcat.chats.config;
import com.meteorcat.chats.ChatRuntime;
import org.springframework.context.annotation.Bean;
import org.springframework.lang.NonNull;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
/**
* 注册Websocket配置
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
/**
* 对ServerContainer读写进行配置
*
* @return ServletServerContainerFactoryBean
*/
@Bean
public ServletServerContainerFactoryBean servletServerContainerFactoryBean() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
// 设置请求缓冲区
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
/**
* 注册服务端点
*
* @param registry 注册句柄
*/
@Override
public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
// 注册消息到 `/chat` 访问端点, 注意加上 setAllowedOrigins 防止域被禁止访问
registry.addHandler(chatHandler(), "/chat").setAllowedOrigins("*");
}
/**
* 获取配置唯一对象
*
* @return WebSocketHandler
*/
@Bean
public WebSocketHandler chatHandler() {
return new ChatRuntime();
}
}
最后的 application.properties:
# 先最简单配置下
spring.application.name = WebSocketChat
server.address = 127.0.0.1
server.port = 18080
# 日志
logging.level.com.meteorcat = debug
启动完成之后, 这里先配置下 H5 做下客户端请求:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>游戏聊天室</title>
<!-- 新 Bootstrap5 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/css/bootstrap.min.css">
<!-- popper.min.js 用于弹窗、提示、下拉菜单 -->
<script src="https://cdn.staticfile.org/popper.js/2.9.3/umd/popper.min.js"></script>
<!-- 最新的 Bootstrap5 核心 JavaScript 文件 -->
<script src="https://cdn.staticfile.org/twitter-bootstrap/5.1.1/js/bootstrap.min.js"></script>
<!-- popper.min.js 用于弹窗、提示、下拉菜单 -->
<script src="https://cdn.staticfile.org/zepto/1.2.0/zepto.min.js"></script>
</head>
<body>
<!-- Nav Begin -->
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="javascript:void(0)">WebSocket</a>
</li>
</ul>
</nav>
<!-- Nav End -->
<div class="container mt-4">
<div class="row">
<!-- Input Group Begin -->
<div class="col-sm-4">
<form autocomplete="off">
<div class="mb-3 input-group">
<span class="input-group-text">请求地址(ws://)</span>
<input type="url" class="form-control" id="address" name="address" value="127.0.0.1">
</div>
<div class="mb-3 input-group">
<span class="input-group-text">请求端口</span>
<input type="number" class="form-control" id="port" name="port" value="18080">
</div>
<div class="mb-3 input-group">
<span class="input-group-text">请求路径</span>
<input type="text" class="form-control" id="path" name="path" value="/chat">
</div>
<div class="mb-3">
<textarea class="form-control" placeholder="发送内容" name="message" id="message" cols="30"
rows="10" style="resize: none"></textarea>
</div>
<button type="button" class="btn btn-primary js-connect">连接</button>
<button type="button" class="btn btn-success js-send-text disabled">发送</button>
<button type="button" class="btn btn-danger js-close disabled">关闭</button>
</form>
</div>
<!-- Input Group End -->
<!-- Text Area Begin -->
<div class="col-sm-8">
<div class="mb-3">
<h2>请求结果</h2>
<div class="alert alert-info js-cards"></div>
</div>
</div>
<!-- Text Area End -->
</div>
</div>
<script>
// 全局共享的请求句柄
window.handlers = {
url: "",
socket: null,
text2buffer: function (text) {
let buf = new Uint8Array(text.length);
for (let i = 0; i < text.length; i++) {
buf[i] = text.charCodeAt(i);
}
return buf;
},
open: function (event) {
console.log(event);
},
close: function (event) {
$('.js-close').trigger("click");
},
error: function (event) {
$('.js-close').trigger("click");
},
message: function (event) {
const node = $('.js-cards');
node.prepend(
`<div class="card bg-success text-white">
<div class="card-body">响应数据: ${event.data}</div>
</div>
<br>`
);
}
};
$(function () {
// 连接WebSocket
$('.js-connect').click(function () {
// 参数内容
const address = $('#address').val();
const port = $('#port').val();
const path = $('#path').val();
// 构建请求URL
let url;
if (address.startsWith("ws://")) {
url = `${address}:${port}${path}`;
} else {
url = `ws://${address}:${port}${path}`;
}
// 初始化对象
window.handlers.url = url;
window.handlers.socket = new WebSocket(url);
// 绑定事件
window.handlers.socket.onopen = window.handlers.open;
window.handlers.socket.onclose = window.handlers.close;
window.handlers.socket.onerror = window.handlers.error;
window.handlers.socket.onmessage = window.handlers.message;
// 设置可用请求
$('.js-send-text').removeClass("disabled");
$('.js-close').removeClass("disabled");
});
// 关闭请求
$('.js-close').click(function () {
if (window.handlers.socket instanceof WebSocket) {
window.handlers.socket.close();
window.handlers.socket = null;
}
$('.js-send-text').addClass("disabled");
$('.js-close').addClass("disabled");
});
// 发送文本数据
$('.js-send-text').click(function () {
// 参数内容
const message = $('#message').val();
if (window.handlers.socket instanceof WebSocket) {
// 字符串转为Uint8字符串
window.handlers.socket.send(message);
}
});
});
</script>
</body>
</html>
确认测试没问题, 之后的 websocket 服务器将以此作为基点来开发.