Tcp SSE Utils

java 复制代码
  private static TcpConnectionManager instance;
 public static synchronized TcpConnectionManager getInstance() {
        if (instance == null) {
            instance = new TcpConnectionManager();
        }
        return instance;
    }

    private Map<Long, ServerSocket> connections = new ConcurrentHashMap<>();

    // 连接到指定设备
    public boolean connectToEquipment(ZhgdEquipment equipment) {
        try {
            Long equipmentId = equipment.getEquipmentId();
            ServerSocket serverSocket = new ServerSocket(equipment.getEquipmentPort());
            // 存储连接
            connections.put(equipmentId, serverSocket);

            // 启动监听线程
            startListening(serverSocket, equipmentId);
            log.info("启动监听线程 成功:" + equipment.getEquipmentId() + equipment.getEquipmentName());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            log.info("启动监听线程 失败:" + equipment.getEquipmentId());
            return false;
        }
    }

    // 断开指定设备的连接
    public void disconnectEquipment(Long equipmentId) {
        ServerSocket socket = connections.get(equipmentId);
        if (socket != null && !socket.isClosed()) {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        connections.remove(equipmentId);
        log.info("关闭监听线程 成功:" + equipmentId);
    }


    // 启动监听线程处理设备返回的数据
    private void startListening(ServerSocket socket, Long equipmentId) {
        Thread listenerThread = new Thread(() -> {
            try {
                // 等待客户端连接
                Socket clientSocket = socket.accept();
                clientSocket.setKeepAlive(true);
                InputStream in = clientSocket.getInputStream();
                byte[] buffer = new byte[1024];
                int len;

                while ((len = in.read(buffer)) != -1 && !socket.isClosed()) {
                    // 处理接收到的数据
                    handleReceivedData(equipmentId, buffer, len);

                }
            } catch (IOException e) {
                // 连接断开,清理资源
                disconnectEquipment(equipmentId);
            }
        });
        listenerThread.setDaemon(true);
        listenerThread.start();
    }

    // 处理接收到的数据
    private void handleReceivedData(Long equipmentId, byte[] data, int length) {
       //todo
    }

    /**
     * 从指定偏移量提取指定长度的数据并转换为十六进制字符串
     *
     * @param data   原始数据数组
     * @param offset 偏移量(从0开始)
     * @param length 提取数据长度
     * @return 十六进制字符串表示
     */
    private String extractHexData(byte[] data, int offset, int length) {
        if (data.length < offset + length) {
            return "";
        }

        byte[] extractedData = new byte[length];
        System.arraycopy(data, offset, extractedData, 0, length);

        StringBuilder hexString = new StringBuilder();
        for (byte b : extractedData) {
            hexString.append(String.format("%02X", b));
        }

        return hexString.toString().trim();
    }

    // 获取所有连接的设备ID
    public Set<Long> getConnectedEquipmentIds() {
        return connections.keySet();
    }

    // 检查设备是否连接
    public boolean isEquipmentConnected(Long equipmentId) {
        ServerSocket socket = connections.get(equipmentId);
        return socket != null && !socket.isClosed();
    }

    // 关闭所有连接
    public void closeAllConnections() {
        for (Long equipmentId : connections.keySet()) {
            disconnectEquipment(equipmentId);
        }
        connections.clear();
    }

建立TCP连接,处理返回的数据,并转换为十六进制字符串

java 复制代码
    private static final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
//    @CrossOrigin(origins = "*")
    @GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter stream() {
        SseEmitter emitter = new SseEmitter();
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    emitter.send("消息 " + i);  // 向客户端发送数据
                    Thread.sleep(1000);  // 模拟延时
                }
                emitter.complete();  // 发送完成
            } catch (IOException | InterruptedException e) {
                emitter.completeWithError(e);
            }
        }).start();
        return emitter;
    }
    /**
     * 创建 SSE 连接
     *
     * @param userId 用户ID
     * @return SSE 连接
     */
//    @CrossOrigin(origins = "*")
    @GetMapping(path = "/connect/{userId}", produces = "text/event-stream;charset=utf-8")
    public SseEmitter connect(@PathVariable String userId) {
        SseEmitter emitter = new SseEmitter(1800000L); // 30分钟
        // 注册回调
//        emitter.onCompletion(() -> emitters.remove(userId));
//        emitter.onTimeout(() -> emitters.remove(userId));
//        emitter.onError((e) -> emitters.remove(userId));

        emitters.put(userId, emitter);
        try {
            emitter.send(SseEmitter.event().name("CONNECT").data("Connected successfully"));
        } catch (IOException e) {
            // 处理发送失败
        }
        startHeartbeat(emitter);
        return emitter;
    }

    private void startHeartbeat(SseEmitter emitter) {
        // 定期发送心跳包
        Thread heartbeatThread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    emitter.send(SseEmitter.event()
                            .name("heartbeat")
                            .data(System.currentTimeMillis()));
                    Thread.sleep(5000); // 每25秒发送一次心跳
                } catch (Exception e) {
                    break;
                }
            }
        });
        heartbeatThread.setDaemon(true);
        heartbeatThread.start();
    }
    /**
     * 关闭 SSE 连接
     *
     * @param userId 用户ID
     * @return 是否成功关闭
     */
    @DeleteMapping("/disconnect/{userId}")
    public AjaxResult disconnect(@PathVariable String userId) {
        SseEmitter emitter = emitters.remove(userId);
        if (emitter != null) {
            emitter.complete();
            return AjaxResult.success("连接已关闭");
        }
        return AjaxResult.error("未找到连接");
    }


    /**
     * 推送消息给指定用户
     *
     * @param userId 用户ID
     * @param data   消息内容
     */
    public void pushMessageToUser(String userId, Object data) {
        SseEmitter emitter = emitters.get(userId);
        if (emitter != null) {
            try {
                emitter.send(SseEmitter.event().name("message").data(data));
            } catch (IOException e) {
                e.printStackTrace();
//                emitters.remove(userId);
            }
        } else {
            System.out.println("未找到用户:" + userId);
        }
    }

SSE (Server-Sent Events) 与 WebSocket

  1. 通信模式

SSE:单向通信 ,服务器向客户端推送数据,客户端无法通过同一连接向服务器发送数据

WebSocket:双向通信 ,客户端和服务器可以互相发送数据

  1. 协议基础

SSE:基于HTTP协议 ,使用标准的HTTP连接

WebSocket:使用独立的WebSocket协议 ,在握手后建立独立连接

  1. 连接建立

SSE:简单的HTTP请求 ,服务器返回特定的MIME类型(text/event-stream

WebSocket:需要握手 过程,从HTTP升级到WebSocket协议

  1. 浏览器兼容性

SSE:较老的浏览器可能不支持,但在现代浏览器中支持良好

WebSocket:需要较新的浏览器版本,但支持度也相当广泛

  1. 实现复杂度

SSE:实现相对简单,服务器端只需按SSE格式输出数据

WebSocket:需要处理连接管理、消息格式等更多细节

相关推荐
susu108301891113 小时前
springboot3.5.8整合minio8.5.9
java·springboot
不知道累,只知道类14 小时前
深入理解 Java 虚拟线程 (Project Loom)
java·开发语言
myzshare14 小时前
实战分享:我是如何用SSM框架开发出一个完整项目的
java·mysql·spring cloud·微信小程序
Chan1614 小时前
【 Java八股文面试 | JavaSE篇 】
java·jvm·spring boot·面试·java-ee·八股
wen__xvn14 小时前
代码随想录算法训练营DAY10第五章 栈与队列part01
java·前端·算法
独自破碎E15 小时前
解释一下NIO、BIO、AIO
java·开发语言·nio
国强_dev15 小时前
在 Java 开发及其生态圈中“声东击西”的误导性错误
java·开发语言
FG.15 小时前
LangChain4j
java·spring boot·langchain4j
linweidong15 小时前
C++thread pool(线程池)设计应关注哪些扩展性问题?
java·数据库·c++