通信(2) : WebSocket实时监听日志

参考 : 

        Java监控本地日志并实现实时查看 - 知乎

        HTML5 - Web Socket使用详解(附样例)

        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>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值