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:需要处理连接管理、消息格式等更多细节

相关推荐
云烟成雨TD17 小时前
Spring AI Alibaba 1.x 系列【6】ReactAgent 同步执行 & 流式执行
java·人工智能·spring
于慨17 小时前
Lambda 表达式、方法引用(Method Reference)语法
java·前端·servlet
swg32132117 小时前
Spring Boot 3.X Oauth2 认证服务与资源服务
java·spring boot·后端
gelald17 小时前
SpringBoot - 自动配置原理
java·spring boot·后端
殷紫川17 小时前
深入理解 AQS:从架构到实现,解锁 Java 并发编程的核心密钥
java
一轮弯弯的明月17 小时前
贝尔数求集合划分方案总数
java·笔记·蓝桥杯·学习心得
chenjingming66617 小时前
jmeter线程组设置以及串行和并行设置
java·开发语言·jmeter
BoomHe17 小时前
Android AOSP13 原生 Launcher3 壁纸获取方式
android
殷紫川17 小时前
深入拆解 Java volatile:从内存屏障到无锁编程的实战指南
java
eddieHoo17 小时前
查看 Tomcat 的堆内存参数
java·tomcat