This commit is contained in:
2026-04-17 10:32:57 +08:00
parent 572ad1205f
commit 17c52c978c
691 changed files with 72342 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
package com.maibu.netty;
import com.fastbee.memory.MiddleGlobalMemory;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* 系统初始化资源类
* 实现CommandLineRunner接口,在Spring Boot启动时自动执行
*/
@Getter
@Component
public class InitResource implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(InitResource.class);
@Override
public void run(String... args) throws Exception {
logger.info("InitResource 开始初始化系统资源...");
// 初始化Vert.x
initVertx();
logger.info("InitResource 系统资源初始化完成");
}
/**
* 初始化Vert.x
*/
private void initVertx() {
try {
VertxOptions options = new VertxOptions()
.setWorkerPoolSize(20)
.setEventLoopPoolSize(10)
.setWarningExceptionTime(5000000000L);
MiddleGlobalMemory.vertx = Vertx.vertx(options);
logger.info("Vert.x 初始化成功");
} catch (Exception e) {
logger.error("Vert.x 初始化失败: {}", e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,223 @@
package com.maibu.netty;
import com.fastbee.common.MiddleConstant;
import com.fastbee.netty.handler.*;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.TimeUnit;
/**
* Netty客户端实现类
* 提供与TCP服务器的连接管理、消息发送、重连机制和心跳检测功能
*/
public class NettyClient {
private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
// private static volatile NettyClient instance;
public String token;
public String userName;
public String requestDeviceId; //前端需要连接的设备
public String currentDeviceId; //当前连接的设备
public Long heartBeatTimer;
public String key; //userName + web + token
public String host;
public int port;
public int reconnectDelay = 5;
public int heartbeatInterval = 5;
public EventLoopGroup group;
public Bootstrap bootstrap;
public Channel channel;
public Long lastSwitchTime = -1L;
public Long Interval = 5000L;
public static final byte[] HEARTBEAT_PACKET = new byte[]{(byte) 0xAB, (byte) 0xAA, (byte) 0xFF, (byte) 0xAA, (byte) 0xAB};
// /**
// * 获取单例实例(无回调)
// */
// public static NettyClient getInstance() {
// if (instance == null) {
// synchronized (NettyClient.class) {
// if (instance == null) {
// instance = new NettyClient();
// }
// }
// }
// return instance;
// }
/**
* 初始化并连接到服务器
*/
public void initConnect(String host, int port) {
if (channel != null && channel.isActive()) {
logger.info("Netty客户端已连接,不重复初始化");
return;
}
this.host = host;
this.port = port;
connect();
}
/**
* 初始化Netty客户端
*/
public void initClient() {
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 设置共享属性
ch.attr(MiddleConstant.ATT_MASTER_KEY).set(key);
// 空闲检测处理器(心跳)
pipeline.addLast(new IdleStateHandler(0, heartbeatInterval, 0, TimeUnit.SECONDS));
pipeline.addLast(new MiddleHeaderFooterDecoder());
// 编解码器
pipeline.addLast(new MiddleHexDecoder());
pipeline.addLast(new MiddleHexEncoder());
// 自定义处理器
pipeline.addLast(new ClientHandler());
}
});
}
/**
* 连接到服务器
*/
public void connect() {
if (bootstrap == null || host == null || port <= 0) {
logger.error("Netty客户端未正确初始化");
return;
}
logger.info("正在连接到Netty服务器: {}:{}", host, port);
bootstrap.connect(host, port).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
channel = future.channel();
logger.info("Netty客户端连接成功: {}:{}", host, port);
// notifyConnectionStatus(true);
} else {
logger.error("Netty客户端连接失败,{}秒后重试: {}", reconnectDelay, future.cause().getMessage());
// notifyConnectionStatus(false);
// 重连
group.schedule(this::connect, reconnectDelay, TimeUnit.SECONDS);
}
});
}
/**
* 断开连接
*/
public void disconnect() {
if (channel != null) {
channel.close();
channel = null;
}
// notifyConnectionStatus(false);
logger.info("Netty客户端已断开连接");
}
/**
* 发送数据
*/
public void sendData(byte[] data) {
if (channel != null && channel.isActive()) {
channel.writeAndFlush(data).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
logger.debug("Netty客户端发送数据成功: {}", bytesToHex(data));
} else {
logger.error("Netty客户端发送数据失败: {}", future.cause().getMessage());
}
});
} else {
logger.error("Netty客户端未连接,发送数据失败");
}
}
/**
* 发送十六进制字符串数据
*/
public void sendHexData(String hexData) {
try {
byte[] data = hexStringToBytes(hexData);
sendData(data);
} catch (IllegalArgumentException e) {
logger.error("无效的十六进制字符串: {}", hexData);
}
}
/**
* 十六进制字符串转字节数组
*/
private static byte[] hexStringToBytes(String hexString) {
hexString = hexString.replaceAll("\\s+", "");
if (hexString.length() % 2 != 0) {
throw new IllegalArgumentException("十六进制字符串长度必须为偶数");
}
byte[] result = new byte[hexString.length() / 2];
for (int i = 0; i < hexString.length(); i += 2) {
result[i / 2] = (byte) Integer.parseInt(hexString.substring(i, i + 2), 16);
}
return result;
}
/**
* 字节数组转十六进制字符串
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString().trim();
}
/**
* 检查客户端是否已连接
*/
public boolean isConnected() {
return channel != null && channel.isActive();
}
/**
* 设置重连延迟时间(秒)
*/
public void setReconnectDelay(int reconnectDelay) {
if (reconnectDelay > 0) {
this.reconnectDelay = reconnectDelay;
}
}
/**
* 设置心跳间隔时间(秒)
*/
public void setHeartbeatInterval(int heartbeatInterval) {
if (heartbeatInterval > 0) {
this.heartbeatInterval = heartbeatInterval;
}
}
}

View File

@@ -0,0 +1,494 @@
package com.maibu.netty.handler;
import cn.hutool.json.JSONObject;
import com.fastbee.common.MiddleCommandConstant;
import com.fastbee.common.MiddleConstant;
import com.fastbee.common.utils.json.JsonUtils;
import com.fastbee.common.utils.spring.SpringUtils;
import com.fastbee.dto.*;
import com.fastbee.memory.MiddleGlobalMemory;
import com.fastbee.netty.NettyClient;
import com.fastbee.utils.HttpService;
import com.fastbee.websocket.WebsocketHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.java_websocket.WebSocket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Netty客户端处理器
*/
@Slf4j
public class ClientHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = LoggerFactory.getLogger(ClientHandler.class);
private static final HttpService httpService = SpringUtils.getBean(HttpService.class);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
byte[] data = (byte[]) msg;
logger.debug("channelRead2: {}", Arrays.toString(data));
//假如是心跳包
String key = ctx.channel().attr(MiddleConstant.ATT_MASTER_KEY).get();
WebSocket webSocket = MiddleGlobalMemory.onlineSockets.get(key);
if (isHeartbeat(data)) {
//websocket 不在了停止发 心跳
if (webSocket != null && webSocket.isOpen()) {
ctx.writeAndFlush(data);
}
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
if (nettyClient != null && StringUtils.isEmpty(nettyClient.currentDeviceId)) {
if (nettyClient.lastSwitchTime == -1 || System.currentTimeMillis() - nettyClient.lastSwitchTime > nettyClient.Interval) {
if (!StringUtils.isEmpty(nettyClient.requestDeviceId) && !StringUtils.isEmpty(key)) {
switchDevice(nettyClient, key);
nettyClient.lastSwitchTime = System.currentTimeMillis();
}
}
}
} else if (isPushMsg(data)) {
try {
int dataStart = 3; // 跳过 0:AB 1:AA 2:cmd
// 数据体结束位置(不含CRC和帧尾)
int dataEnd = data.length - 4; // 去掉 CRC_L、CRC_H、AA、AB
int dataLen = dataEnd - dataStart;
if (dataLen <= 0) {
System.out.println("数据体长度异常");
return;
}
byte[] jsonBytes = Arrays.copyOfRange(data, dataStart, dataEnd);
String json = new String(jsonBytes, StandardCharsets.UTF_8);
logger.debug("收到推送2: {}", json);
if (StringUtils.isNotBlank(json)) {
JSONObject obj = new JSONObject(json);
if (obj.get("event") != null) {
//状态变化 上下线
String eventType = obj.get("event").toString();
String deviceId = obj.get("deviceId").toString();
if ("device_status_changed".equals(eventType)) {
String status = obj.get("status").toString();
WebOlineStatusMessageDTO dto = new WebOlineStatusMessageDTO();
dto.setDeviceId(deviceId);
dto.setType(MesType.device_status_changed);
if (status.equals("offline")) {
dto.setOnlineStatus(0);
} else {
dto.setOnlineStatus(1);
}
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(dto));
} else if ("device_error_push".equals(eventType)) {
//todo 更新推送的错误
String details = obj.get("details").toString();
WebOlineStatusMessageDTO dto = new WebOlineStatusMessageDTO();
dto.setDeviceId(deviceId);
dto.setType(MesType.device_error_push);
dto.setHasError(1);
dto.setDescription("");
dto.setErrorCode("");
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(dto));
}
} else {
/* 判断 respond 字段 */
if (obj.get("request") != null) {
String deviceId = obj.get("deviceId").toString();
String userName = obj.get("userId").toString();
String platform = obj.get("platform").toString();
//直接转发 websocket 申请
WebAuthResponseDTO request = new WebAuthResponseDTO();
request.setType(MesType.switchPermission);
request.setUserName(userName);
request.setDeviceId(deviceId);
request.setPlatform(platform);
logger.debug("json: {}", JsonUtils.toJsonString(request));
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(request));
} else {
if (obj.get("respond") != null) {
if ("have_logged_in".equals(obj.get("respond").toString())) {
/* 如果回复 have_logged_in 则调用logout */
//System.out.println("检测到重复登录,执行 logout");
//NettyClient nettyClient = GlobalMemory.nettyClientMap.get(key);
//WebHasLoginDTO dto = new WebHasLoginDTO();
//dto.setToken(nettyClient.token);
//dto.setUserName(nettyClient.userName);
//dto.setType(MesType.have_logged_in);
//WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(dto));
} else {
//todo respond netty_server返回结果处理
DeviceRespondDTO respond = JsonUtils.parseObject(obj.get("respond").toString(), DeviceRespondDTO.class);
if (respond != null) {
//直接转发 websocket 申请
WebSwitchControlResponseDTO responseDTO = new WebSwitchControlResponseDTO();
responseDTO.setType(MesType.switchResult);
responseDTO.setRespond(respond);
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(responseDTO));
}
}
} else {
if (obj.get("type").toString() != null && "detectionReport".equals(obj.get("type").toString())) {
WebsocketHandler.sendMessageToClient(webSocket, json);
}
}
}
}
//error 推送
}
//TODO web 无切换控制请求
} catch (Exception e) {
logger.error("事件推送解析失败:{}", e.getMessage());
}
} else if (isStatusDate(data)) {
//状态消息推送给前端
WebStatusMessageDTO dto = createWebDeviceStatusMessage(data);
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(dto));
} else if (isPath(data)) {
//已到达
if (data[5] == (byte) 0x01) {
}
}
} catch (Exception e) {
logger.error("channelRead error:{}", e.getMessage());
}
}
public WebStatusMessageDTO createWebDeviceStatusMessage(byte[] data) {
WebStatusMessageDTO webStatusMessageDTO = new WebStatusMessageDTO();
if (data[2] == MiddleCommandConstant.status) {
String deviceStatus = new String(data, 2, data.length - 4, StandardCharsets.UTF_8).trim();
String[] split = deviceStatus.split(",");
List<DeviceStatusDetail> transferData = transferStatusData(split);
webStatusMessageDTO.setData(transferData);
webStatusMessageDTO.setType(MesType.deviceInfo);
}
return webStatusMessageDTO;
}
private List<DeviceStatusDetail> transferStatusData(String[] split) {
List<DeviceStatusDetail> transferData = new ArrayList<>();
DeviceStatusDetail detail0 = new DeviceStatusDetail();
detail0.setName("voltage");
detail0.setValue(split[0]);
detail0.setUnit("");
transferData.add(detail0);
DeviceStatusDetail detail1 = new DeviceStatusDetail();
detail1.setName("leftTargetSpeed");
detail1.setValue(split[1]);
detail1.setUnit("");
transferData.add(detail1);
DeviceStatusDetail detail2 = new DeviceStatusDetail();
detail2.setName("rightTargetSpeed");
detail2.setValue(split[2]);
detail2.setUnit("");
transferData.add(detail2);
DeviceStatusDetail detail3 = new DeviceStatusDetail();
detail3.setName("leftMeasureSpeed");
detail3.setValue(split[3]);
detail3.setUnit("");
transferData.add(detail3);
DeviceStatusDetail detail4 = new DeviceStatusDetail();
detail4.setName("rightMeasureSpeed");
detail4.setValue(split[4]);
detail4.setUnit("");
transferData.add(detail4);
DeviceStatusDetail detail5 = new DeviceStatusDetail();
detail5.setName("leftCurrent");
detail5.setValue(split[5]);
detail5.setUnit("");
transferData.add(detail5);
DeviceStatusDetail detail6 = new DeviceStatusDetail();
detail6.setName("rightCurrent");
detail6.setValue(split[6]);
detail6.setUnit("");
transferData.add(detail6);
DeviceStatusDetail detail7 = new DeviceStatusDetail();
detail7.setName("leftMotorTemp");
detail7.setValue(split[7]);
detail7.setUnit("");
transferData.add(detail7);
DeviceStatusDetail detail8 = new DeviceStatusDetail();
detail8.setName("rightMotorTemp");
detail8.setValue(split[8]);
detail8.setUnit("");
transferData.add(detail8);
DeviceStatusDetail detail9 = new DeviceStatusDetail();
detail9.setName("chipTemp");
detail9.setValue(split[9]);
detail9.setUnit("");
transferData.add(detail9);
DeviceStatusDetail detail10 = new DeviceStatusDetail();
detail10.setName("yaw");
detail10.setValue(split[10]);
detail10.setUnit("");
transferData.add(detail10);
DeviceStatusDetail detail11 = new DeviceStatusDetail();
detail11.setName("pitch");
detail11.setValue(split[11]);
detail11.setUnit("");
transferData.add(detail11);
DeviceStatusDetail detail12 = new DeviceStatusDetail();
detail12.setName("roll");
detail12.setValue(split[12]);
detail12.setUnit("");
transferData.add(detail12);
DeviceStatusDetail detail13 = new DeviceStatusDetail();
detail13.setName("satelliteCnt");
detail13.setValue(split[13]);
detail13.setUnit("");
transferData.add(detail13);
DeviceStatusDetail detail14 = new DeviceStatusDetail();
detail14.setName("headingStatus");
detail14.setValue(split[14]);
detail14.setUnit("");
transferData.add(detail14);
DeviceStatusDetail detail15 = new DeviceStatusDetail();
detail15.setName("headingStatus");
detail15.setValue(split[15]);
detail15.setUnit("");
transferData.add(detail15);
DeviceStatusDetail detail16 = new DeviceStatusDetail();
detail16.setName("latitude");
detail16.setValue(split[16]);
detail16.setUnit("");
transferData.add(detail16);
DeviceStatusDetail detail17 = new DeviceStatusDetail();
detail17.setName("longitude");
detail17.setValue(split[17]);
detail17.setUnit("");
transferData.add(detail17);
//18 时间暂未给值
DeviceStatusDetail detail19 = new DeviceStatusDetail();
detail19.setName("cuttingSpeed");
detail19.setValue(split[19]);
detail19.setUnit("");
transferData.add(detail19);
DeviceStatusDetail detail20 = new DeviceStatusDetail();
detail20.setName("controlMode");
detail20.setValue(split[20]);
detail20.setUnit("");
transferData.add(detail20);
DeviceStatusDetail detail21 = new DeviceStatusDetail();
detail21.setName("battery");
detail21.setValue(split[21]);
detail21.setUnit("");
transferData.add(detail21);
DeviceStatusDetail detail22 = new DeviceStatusDetail();
detail22.setName("workingArea");
detail22.setValue(split[22]);
detail22.setUnit("");
transferData.add(detail22);
DeviceStatusDetail detail23 = new DeviceStatusDetail();
detail23.setName("obstacleSign");
detail23.setValue(split[23]);
detail23.setUnit("");
transferData.add(detail23);
return transferData;
}
@Override
public void channelActive(ChannelHandlerContext ctx) {
logger.debug("连接开始channelActive:ChannelId:{} ", ctx.channel().id().asLongText());
//websocket连上后连接netty成功后发送登录认证包
// key username + : web + token
String key = ctx.channel().attr(MiddleConstant.ATT_MASTER_KEY).get();
System.out.println(key);
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
if (nettyClient != null) {
// 构造并发送认证包
byte[] deviceNameBytes = key.getBytes(StandardCharsets.UTF_8);
byte[] packet = new byte[3 + deviceNameBytes.length + 4];
packet[0] = (byte) 0xAB;
packet[1] = (byte) 0xAA;
packet[2] = 0x03;
System.arraycopy(deviceNameBytes, 0, packet, 3, deviceNameBytes.length);
int tailStart = 3 + deviceNameBytes.length;
packet[tailStart] = 0x00;
packet[tailStart + 1] = 0x00;
packet[tailStart + 2] = (byte) 0xAA;
packet[tailStart + 3] = (byte) 0xAB;
ctx.writeAndFlush(packet);
// startHeartbeat(nettyClient, ctx, key);
}
}
public static void switchDevice(NettyClient nettyClient, String key) {
try {
// 2. 调用 POST 接口:传入 Token B(与 Token A 不同)
String token = nettyClient.token;
String postUrl = MiddleConstant.nettyApiUrl + "/forward/device/switchDevice";
NettySwitchDeviceDTO dto = new NettySwitchDeviceDTO();
dto.setDeviceId(nettyClient.requestDeviceId);
dto.setPlatform("web");
String result = httpService.doPost(postUrl, token, dto);
ResultDTO resultDTO = JsonUtils.parseObject(result, ResultDTO.class);
if (resultDTO != null && resultDTO.getData() != null) {
if ((boolean) resultDTO.getData()) {
logger.debug("用户:{}切换设备:{}成功", key, nettyClient.requestDeviceId);
//切换成功推送 websocket
nettyClient.currentDeviceId = nettyClient.requestDeviceId;
// nettyClient.requestDeviceId = null;
WebSocket webSocket = MiddleGlobalMemory.onlineSockets.get(key);
if (webSocket != null && webSocket.isOpen()) {
WebAuthResponseDTO responseDTO = new WebAuthResponseDTO();
responseDTO.setDeviceId(nettyClient.currentDeviceId);
responseDTO.setUserName(nettyClient.userName);
responseDTO.setType(MesType.AUTH);
responseDTO.setIsAuth(true);
WebsocketHandler.sendMessageToClient(webSocket, JsonUtils.toJsonString(responseDTO));
}
} else {
logger.debug("用户:{}切换设备:{}失败", key, nettyClient.requestDeviceId);
}
}
} catch (Exception e) {
log.error("切换设备,switchDevice发生错误", e);
}
}
/**
* 启动心跳检测
*/
private void startHeartbeat(NettyClient nettyClient, ChannelHandlerContext ctx, String key) {
stopHeartbeat(nettyClient);
MiddleGlobalMemory.vertx.setPeriodic(200, timerId -> {
try {
nettyClient.heartBeatTimer = timerId;
if (ctx.channel() != null && ctx.channel().isActive()) {
ctx.channel().writeAndFlush(NettyClient.HEARTBEAT_PACKET);
logger.debug("Netty客户端:{}发送心跳包", nettyClient.key);
}
} catch (Exception e) {
logger.error("循环任务执行异常:" + e.getMessage());
}
});
}
/**
* 关闭心跳检测
*/
private void stopHeartbeat(NettyClient nettyClient) {
if (nettyClient != null && nettyClient.heartBeatTimer != null) {
MiddleGlobalMemory.vertx.cancelTimer(nettyClient.heartBeatTimer);
nettyClient.heartBeatTimer = null;
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
String key = ctx.channel().attr(MiddleConstant.ATT_MASTER_KEY).get();
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
// stopHeartbeat(nettyClient);
nettyClient.currentDeviceId = null;
logger.debug("连接断开channelInactive,ChannelId:{} ", ctx.channel().id().asLongText());
WebSocket webSocket = MiddleGlobalMemory.onlineSockets.get(key);
if (webSocket != null && webSocket.isOpen()) {
ctx.channel().eventLoop().schedule(nettyClient::connect, nettyClient.reconnectDelay, TimeUnit.SECONDS);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
String key = ctx.channel().attr(MiddleConstant.ATT_MASTER_KEY).get();
NettyClient nettyClient = MiddleGlobalMemory.nettyClientMap.get(key);
// stopHeartbeat(nettyClient);
cause.printStackTrace();
logger.error("异常断开exceptionCaught:{}", cause.getMessage());
ctx.close();
}
private boolean isHeartbeat(byte[] data) {
return data.length == 5 &&
data[0] == (byte) 0xAB &&
data[1] == (byte) 0xAA &&
data[2] == (byte) 0xFF &&
data[3] == (byte) 0xAA &&
data[4] == (byte) 0xAB;
}
/// 服务器重新请求下位机连接指令。即重新发送连接码
private boolean isReconnectCommand(byte[] data) {
return data.length == 9 &&
data[0] == (byte) 0xAB &&
data[1] == (byte) 0xAA &&
data[2] == (byte) 0x03 &&
data[3] == (byte) 0x00 &&
data[4] == (byte) 0x00 &&
data[5] == (byte) 0x00 &&
data[6] == (byte) 0x00 &&
data[7] == (byte) 0xAA &&
data[8] == (byte) 0xAB;
}
private boolean isPushMsg(byte[] data) {
return data[0] == (byte) 0xAB &&
data[1] == (byte) 0xAA &&
data[2] == (byte) 0x12 &&
data[data.length - 2] == (byte) 0xAA &&
data[data.length - 1] == (byte) 0xAB;
// // 只要是以 '{' 开头、以 '}' 结尾,就认为是事件 JSON
// if (data == null || data.length < 2) return false;
// return data[0] == '{' && data[data.length - 1] == '}';
}
private boolean isStatusDate(byte[] data) {
return data[0] == (byte) 0xAB &&
data[1] == (byte) 0xAA &&
data[2] == (byte) 0x02 &&
data[data.length - 2] == (byte) 0xAA &&
data[data.length - 1] == (byte) 0xAB;
}
private boolean isPath(byte[] data) {
return data[0] == (byte) 0xAB &&
data[1] == (byte) 0xAA &&
data[2] == (byte) 0x01 &&
data[data.length - 2] == (byte) 0xAA &&
data[data.length - 1] == (byte) 0xAB;
}
}

View File

@@ -0,0 +1,93 @@
package com.maibu.netty.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.apache.commons.codec.binary.Hex;
import java.util.List;
public class MiddleHeaderFooterDecoder extends ByteToMessageDecoder {
private static final byte[] HEADER = {(byte) 0xAB, (byte) 0xAA};
private static final byte[] FOOTER = {(byte) 0xAA, (byte) 0xAB};
private static final int MAX_FRAME_LENGTH = 500;
private static final boolean DEBUG = true; // 可控制是否打印日志
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
if (DEBUG) {
byte[] raw = new byte[in.readableBytes()];
in.getBytes(in.readerIndex(), raw);
System.out.println("收到原始数据:" + Hex.encodeHexString(raw).toUpperCase());
}
int readerIndex = in.readerIndex();
while (readerIndex <= in.writerIndex() - HEADER.length) {
// 找帧头
int headerPos = findPattern(in, HEADER, readerIndex);
if (headerPos == -1) {
// 没找到帧头,丢弃无效数据
in.readerIndex(in.writerIndex() - (HEADER.length - 1));
return;
}
// 从 header 之后找帧尾
int searchStart = headerPos + HEADER.length;
int footerPos = -1;
while (searchStart <= in.writerIndex() - FOOTER.length) {
footerPos = findPattern(in, FOOTER, searchStart);
if (footerPos == -1) break; // 没找到尾,等待更多数据
// 检查是否有嵌套帧头
int nestedHeader = findPattern(in, HEADER, searchStart);
if (nestedHeader != -1 && nestedHeader < footerPos) {
// 有嵌套帧头,从新头重新搜索
headerPos = nestedHeader;
searchStart = headerPos + HEADER.length;
continue;
}
break;
}
if (footerPos == -1) {
// 第一帧未完整,保留数据,等待更多
in.readerIndex(headerPos);
return;
}
int frameLength = footerPos + FOOTER.length - headerPos;
if (frameLength > MAX_FRAME_LENGTH) {
// 超长帧,跳过异常数据
in.readerIndex(headerPos + 1); // 从下一个字节重新找
readerIndex = in.readerIndex();
continue;
}
// 提取完整帧
in.readerIndex(headerPos);
ByteBuf frame = in.readRetainedSlice(frameLength);
out.add(frame);
// 下次从 footer 后面继续解析
readerIndex = in.readerIndex();
}
// 更新 readerIndex
in.readerIndex(readerIndex);
}
private int findPattern(ByteBuf buf, byte[] pattern, int fromIndex) {
for (int i = fromIndex; i <= buf.writerIndex() - pattern.length; i++) {
boolean match = true;
for (int j = 0; j < pattern.length; j++) {
if (buf.getByte(i + j) != pattern[j]) {
match = false;
break;
}
}
if (match) return i;
}
return -1;
}
}

View File

@@ -0,0 +1,17 @@
package com.maibu.netty.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class MiddleHexDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
byte[] data = new byte[in.readableBytes()];
in.readBytes(data);
out.add(data);
}
}

View File

@@ -0,0 +1,13 @@
package com.maibu.netty.handler;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
public class MiddleHexEncoder extends MessageToByteEncoder<byte[]> {
@Override
protected void encode(ChannelHandlerContext ctx, byte[] msg, ByteBuf out) {
out.writeBytes(msg);
}
}