223 lines
6.8 KiB
Java
223 lines
6.8 KiB
Java
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;
|
||
}
|
||
}
|
||
} |