参考 :
SpringBoot使用WebSocket_springboot websocket_仰望银河系的博客-CSDN博客
java
maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
FileMonitor
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FileMonitor {
/**
* 绑定的websocket
*/
private String sessionId;
/**
* 绑定的监控日志路径
*/
private String logPath;
/**
* 监控时间间隔,单位ms
*/
private Long monitorDelay;
public FileMonitor(String sessionId, String logPath) {
this.sessionId = sessionId;
this.logPath = logPath;
this.monitorDelay = 500L;
startFileMonitor(monitorDelay);
}
public FileMonitor(String sessionId, String logPath, Long monitorDelay) {
this.sessionId = sessionId;
this.logPath = logPath;
this.monitorDelay = monitorDelay;
startFileMonitor(monitorDelay);
}
private void startFileMonitor(Long monitorDelay) {
Thread thread = new Thread(new FileMonitorRunnable(sessionId, logPath, monitorDelay));
thread.start();
}
}
FileMonitorRunnable
import com.alibaba.gts.flm.common.utils.ShellUtil;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
@Slf4j
public class FileMonitorRunnable implements Runnable {
private ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 100);
private CharBuffer charBuffer = CharBuffer.allocate(1024 * 50);
private CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
private boolean isRunning = true;
private String sessionId;
private String logPath;
private Long monitorDelay;
public FileMonitorRunnable(String sessionId, String logPath, Long monitorDelay) {
this.sessionId = sessionId;
this.logPath = logPath;
this.monitorDelay = monitorDelay;
}
@Override
public void run() {
File file = new File(logPath);
FileChannel channel = null;
try {
channel = new FileInputStream(file).getChannel();
channel.position(channel.size());
} catch (Exception e) {
log.info("监控文件失败,检查路径是否正确");
WebSocketUtils.endMonitor(sessionId);
e.printStackTrace();
}
long lastModified = file.lastModified();
// 初次连接
WebSocketUtils.sendMessageTo(sessionId, ShellUtil.exec("tail -n 100 " + logPath));
// 持续监听
while (isRunning) {
long now = file.lastModified();
if (now != lastModified) {
log.info("{}的连接正在通过线程{}监控{}的文件update", sessionId, Thread.currentThread().getName(), logPath);
String newContent = getNewContent(channel);
WebSocketUtils.sendMessageTo(sessionId, newContent);
lastModified = now;
}
try {
Thread.sleep(monitorDelay);
} catch (InterruptedException e) {
e.printStackTrace();
WebSocketUtils.endMonitor(sessionId);
}
isRunning = WebSocketUtils.currentSessionAlive(sessionId);
}
}
private String getNewContent(FileChannel channel) {
try {
byteBuffer.clear();
charBuffer.clear();
int length = channel.read(byteBuffer);
if (length != -1) {
byteBuffer.flip();
decoder.decode(byteBuffer, charBuffer, true);
charBuffer.flip();
return charBuffer.toString();
} else {
channel.position(channel.size());
}
} catch (Exception e) {
e.printStackTrace();
WebSocketUtils.endMonitor(sessionId);
}
return null;
}
}
WebSocketConfig
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
/**
* @author: xxt
* @date: 2022/5/23 16:22
* @Description: 开启WebSocket支持
*/
@Configuration
public class WebSocketConfig implements ServletContextInitializer {
/**
* 这个bean的注册,用于扫描带有@ServerEndpoint的注解成为websocket,如果你使用外置的tomcat就不需要该配置文件
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
}
}
WebSocketSeverRealTimeLogs
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
/**
* @author: xxt
* @date: 2022/5/23 16:27
* @Description: WebSocket操作类
*/
@ServerEndpoint("/websocket/realTimeLogs/{fileName}")
@Component
@Slf4j
public class WebSocketSeverRealTimeLogs {
/**
* 建立WebSocket连接
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "fileName") String fileName) {
String sessionId = session.getId();
log.info("WebSocket建立连接中,文件:{},session.id", fileName, sessionId);
Session historySession = WebSocketUtils.getSession(sessionId);
// historySession不为空,说明已经有人登陆账号,应该删除登陆的WebSocket对象
if (historySession != null) {
log.warn("旧连接不为空,注销旧连接");
WebSocketUtils.reduceSession(sessionId);
}
// 建立连接
WebSocketUtils.addSession(session);
// TODO 无法直接传递文件路径,转成base64后可传输
WebSocketUtils.startMonitor(sessionId, "/Users/xxx/logs/"+fileName);
}
/**
* 发生错误
*
* @param throwable e
*/
@OnError
public void onError(Throwable throwable) {
throwable.printStackTrace();
}
/**
* 连接关闭
*/
@OnClose
public void onClose(Session session) {
WebSocketUtils.reduceSession(session.getId());
}
/**
* 接收客户端消息
*
* @param message 接收的消息
*/
@OnMessage
public void onMessage(String message) {
log.info("收到客户端发来的消息:{}", message);
}
}
WebSocketUtils
import lombok.extern.slf4j.Slf4j;
import javax.websocket.Session;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class WebSocketUtils {
/**
* 已连接的websocket
*/
private static Map<String, Session> onlineSession = new HashMap<>();
/**
* 添加用户
*/
public static void addSession(Session session) {
onlineSession.put(session.getId(), session);
log.info("建立连接完成,sessionId:{},当前在线人数为:{}", session.getId(), onlineSession.size());
}
/**
* 获取用户
*/
public static Session getSession(String sessionId) {
return onlineSession.get(sessionId);
}
/**
* 移除用户
*/
public static void reduceSession(String sessionId) {
onlineSession.remove(sessionId);
log.info("sessionId({})连接断开,当前在线数为:{}", sessionId, onlineSession.size());
}
/**
* 开启监测
* 本质是一监控一线程
*/
public static void startMonitor(String sessionId, String logPath) {
Session session = onlineSession.get(sessionId);
new FileMonitor(session.getId(), logPath);
}
/**
* 关闭监控
* session关闭,相应线程也会关闭
*/
public static void endMonitor(String sessionId) {
Session session = onlineSession.get(sessionId);
sendMessageTo(sessionId, "<error>ERROR 监控线程出现异常!</error>");
try {
session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送消息给指定用户
*
* @param sessionId
* @param message
*/
public static void sendMessageTo(String sessionId, String message) {
Session session = onlineSession.get(sessionId);
try {
session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
endMonitor(sessionId);
}
}
/**
* session是否在线
* 用于决定线程是否关闭
*
* @param sessionId
* @return
*/
public static boolean currentSessionAlive(String sessionId) {
return onlineSession.containsKey(sessionId);
}
/**
* 群发消息
*/
public static void sendAllMessage(String message) {
log.info("发送消息:{}", message);
onlineSession.forEach((k, v) -> {
try {
v.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("群发消息发生错误:" + e.getMessage(), e);
}
});
}
}
html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>hangge.com</title>
<style>
</style>
<script type="text/javascript">
//用于填写发送消息的输入框
var messageBox;
//用于显示消息的div容器
var messageLog;
//WebSocket对象
var socket;
//页面加载完毕
window.onload = function() {
messageBox = document.getElementById('messageBox');
messageLog = document.getElementById('messageLog');
//wsAddress = document.getElementById('wsAddress');
}
//创建socket对象并绑定所有事件
function connect() {
//创建socket对象
socket = new WebSocket(wsAddress.value);
console.log(wsAddress.value)
//监听所有的Web socket事件
socket.onopen = connectionOpen;
socket.onmessage = messageReceived;
socket.onerror = errorOccurred;
socket.onclose = connectionClosed;
}
//断开连接按钮点击
function disconnect() {
socket.close();
}
//发送消息按钮点击
function sendMessage() {
//获取要发送的数据
var message = messageBox.value;
//通过socket发送消息
socket.send(message);
//告诉用户刚刚发生了什么
messageLog.innerHTML += "<br>发送:" + message;
}
//连接建立完毕事件响应
function connectionOpen(e) {
messageLog.innerHTML += "<br>--- Socket连接成功 ---";
}
//消息接收事件响应
function messageReceived(e) {
messageLog.innerHTML +=e.data;
console.log(e.data)
}
//错误事件响应
function errorOccurred(e) {
messageLog.innerHTML += "<br>发生错误:" + e.data;
}
//连接关闭事件响应
function connectionClosed(e) {
messageLog.innerHTML += "<br>--- Socket连接关闭 ---";
}
</script>
</head>
<body>
<input id="wsAddress" value="ws://127.0.0.1:8080/websocket/realTimeLogs/1.log" style="width: 400px;" />
<button onclick="connect()">连接</button>
<button onclick="disconnect()">断开</button>
<br />
<textarea id="messageLog" readonly cols="200" rows="100"></textarea>
</body>
</html>