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
- 通信模式
SSE:单向通信 ,服务器向客户端推送数据,客户端无法通过同一连接向服务器发送数据
WebSocket:双向通信 ,客户端和服务器可以互相发送数据
- 协议基础
SSE:基于HTTP协议 ,使用标准的HTTP连接
WebSocket:使用独立的WebSocket协议 ,在握手后建立独立连接
- 连接建立
SSE:简单的HTTP请求 ,服务器返回特定的MIME类型(text/event-stream )
WebSocket:需要握手 过程,从HTTP升级到WebSocket协议
- 浏览器兼容性
SSE:较老的浏览器可能不支持,但在现代浏览器中支持良好
WebSocket:需要较新的浏览器版本,但支持度也相当广泛
- 实现复杂度
SSE:实现相对简单,服务器端只需按SSE格式输出数据
WebSocket:需要处理连接管理、消息格式等更多细节