11 changed files with 450 additions and 149 deletions
-
7.mvn/wrapper/maven-wrapper.properties
-
20pom.xml
-
4src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusConnection.java
-
4src/main/java/a8k/app/channel/iflytophald/channel/A8kCanBusService.java
-
4src/main/java/a8k/app/channel/iflytophald/driver/OptModuleDriver.java
-
32src/main/java/a8k/app/channel/iflytophald/type/protocol/A8kPacket.java
-
139src/main/java/a8k/app/channel/net/BoditechLisDoubleTrackTcpClient.java
-
122src/main/java/a8k/app/channel/net/BoditechLisSingleTrackTcpClient.java
-
4src/main/java/a8k/app/utils/ByteArrayUtils.java
-
17src/main/java/a8k/app/utils/NettyUtils.java
@ -1,2 +1,5 @@ |
|||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip |
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar |
|
||||
|
#distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.6/apache-maven-3.9.6-bin.zip |
||||
|
#wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar |
||||
|
# ???????? |
||||
|
distributionUrl=https://mirrors.cloud.tencent.com/apache/maven/maven-3/3.8.6/binaries/apache-maven-3.8.6-bin.zip |
||||
|
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar |
@ -0,0 +1,139 @@ |
|||||
|
package a8k.app.channel.net; |
||||
|
|
||||
|
import a8k.app.utils.ByteArrayUtils; |
||||
|
import a8k.app.utils.NettyUtils; |
||||
|
import io.netty.bootstrap.Bootstrap; |
||||
|
import io.netty.buffer.ByteBuf; |
||||
|
import io.netty.buffer.Unpooled; |
||||
|
import io.netty.channel.*; |
||||
|
import io.netty.channel.socket.SocketChannel; |
||||
|
import io.netty.channel.socket.nio.NioSocketChannel; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Slf4j |
||||
|
@ChannelHandler.Sharable |
||||
|
public class BoditechLisDoubleTrackTcpClient extends ChannelInboundHandlerAdapter { |
||||
|
|
||||
|
private final EventLoopGroup group = NettyUtils.buildEventLoopGroup("BoditechLisSingleTrackTcpClient", 1); |
||||
|
private final Bootstrap bootstrap = new Bootstrap(); |
||||
|
private final Integer reconnectDelay = 5; // 重连延迟时间,单位秒 |
||||
|
private String serverIp; |
||||
|
private Integer port; |
||||
|
private volatile Channel channel; |
||||
|
private volatile boolean isShuttingDown = false; |
||||
|
|
||||
|
|
||||
|
public void start(String serverIp, Integer port) { |
||||
|
this.serverIp = serverIp; |
||||
|
this.port = port; |
||||
|
|
||||
|
bootstrap.group(group) |
||||
|
.channel(NioSocketChannel.class) |
||||
|
.option(ChannelOption.TCP_NODELAY, true) |
||||
|
.option(ChannelOption.SO_KEEPALIVE, true) |
||||
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) |
||||
|
.handler(new ChannelInitializer<SocketChannel>() { |
||||
|
@Override |
||||
|
protected void initChannel(SocketChannel ch) { |
||||
|
ch.pipeline() |
||||
|
.addLast(BoditechLisDoubleTrackTcpClient.this); |
||||
|
} |
||||
|
}); |
||||
|
doConnect(); |
||||
|
} |
||||
|
public synchronized void shutdown() { |
||||
|
isShuttingDown = true; |
||||
|
if (channel != null) { |
||||
|
channel.close(); |
||||
|
} |
||||
|
group.shutdownGracefully(); |
||||
|
} |
||||
|
|
||||
|
public synchronized void tx(byte[] data) { |
||||
|
if (channel != null && channel.isActive()) { |
||||
|
ByteBuf buf = Unpooled.wrappedBuffer(data); |
||||
|
|
||||
|
channel.writeAndFlush(buf).addListener(future -> { |
||||
|
if (!future.isSuccess()) { |
||||
|
log.error("Failed to send data tail,{}", future.cause().getMessage()); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
log.warn("Channel is not active, cannot send data: {}", data); |
||||
|
// 可以考虑在这里实现消息队列,等连接恢复后重发 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 处理接收到的消息 |
||||
|
@Override |
||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { |
||||
|
// 直接接收原始ByteBuf |
||||
|
if (msg instanceof ByteBuf) { |
||||
|
ByteBuf buf = (ByteBuf) msg; |
||||
|
try { |
||||
|
byte[] bytes = new byte[buf.readableBytes()]; |
||||
|
buf.readBytes(bytes); |
||||
|
log.info("Received raw data ({} bytes): {}", bytes.length, ByteArrayUtils.toByteString(bytes)); |
||||
|
// |
||||
|
// PROCESS THE RECEIVED DATA HERE |
||||
|
// |
||||
|
} finally { |
||||
|
buf.release(); // 重要:释放ByteBuf |
||||
|
} |
||||
|
} else { |
||||
|
log.warn("Unexpected message type: {}", msg.getClass()); |
||||
|
} |
||||
|
} |
||||
|
// |
||||
|
// PRIVATE |
||||
|
// |
||||
|
|
||||
|
private void doConnect() { |
||||
|
if (isShuttingDown) { |
||||
|
return; |
||||
|
} |
||||
|
log.info("try connect to {}:{}", serverIp, port); |
||||
|
bootstrap.connect(serverIp, port).addListener((ChannelFuture future) -> { |
||||
|
if (future.isSuccess()) { |
||||
|
log.info("successfully connected to {}:{}", serverIp, port); |
||||
|
channel = future.channel(); |
||||
|
} else { |
||||
|
log.error("failed,{}", future.cause().getMessage()); |
||||
|
if (!isShuttingDown) |
||||
|
group.schedule(this::doConnect, 5, TimeUnit.SECONDS); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public synchronized void channelInactive(ChannelHandlerContext ctx) throws Exception { |
||||
|
log.warn("Connection lost, trying to reconnect..."); |
||||
|
if (channel != null && channel.equals(ctx.channel())) { |
||||
|
channel = null; |
||||
|
} |
||||
|
if (!isShuttingDown) |
||||
|
group.schedule(this::doConnect, reconnectDelay, TimeUnit.SECONDS); |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
BoditechLisDoubleTrackTcpClient client = new BoditechLisDoubleTrackTcpClient(); |
||||
|
client.start("0.0.0.0", 777); |
||||
|
|
||||
|
int count = 0; |
||||
|
while (true) { |
||||
|
try { |
||||
|
byte[] data = {0x01, 0x02, 0x03, 0x04, 0x05}; |
||||
|
client.tx(data); |
||||
|
Thread.sleep(1000); // 主线程保持运行状态 |
||||
|
} catch (InterruptedException e) { |
||||
|
log.error("Main thread interrupted: {}", e.getMessage()); |
||||
|
Thread.currentThread().interrupt(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,122 @@ |
|||||
|
package a8k.app.channel.net; |
||||
|
|
||||
|
import a8k.app.utils.NettyUtils; |
||||
|
import io.netty.bootstrap.Bootstrap; |
||||
|
import io.netty.channel.*; |
||||
|
import io.netty.channel.socket.SocketChannel; |
||||
|
import io.netty.channel.socket.nio.NioSocketChannel; |
||||
|
import io.netty.handler.codec.LineBasedFrameDecoder; |
||||
|
import io.netty.handler.codec.string.StringDecoder; |
||||
|
import io.netty.handler.codec.string.StringEncoder; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
|
||||
|
@Slf4j |
||||
|
@ChannelHandler.Sharable |
||||
|
public class BoditechLisSingleTrackTcpClient extends SimpleChannelInboundHandler<String> { |
||||
|
|
||||
|
private final EventLoopGroup group = NettyUtils.buildEventLoopGroup("BoditechLisSingleTrackTcpClient", 1); |
||||
|
private final Bootstrap bootstrap = new Bootstrap(); |
||||
|
private final Integer reconnectDelay = 5; // 重连延迟时间,单位秒 |
||||
|
private String serverIp; |
||||
|
private Integer port; |
||||
|
private volatile Channel channel; |
||||
|
private volatile boolean isShuttingDown = false; |
||||
|
|
||||
|
|
||||
|
public void start(String serverIp, Integer port) { |
||||
|
this.serverIp = serverIp; |
||||
|
this.port = port; |
||||
|
|
||||
|
bootstrap.group(group) |
||||
|
.channel(NioSocketChannel.class) |
||||
|
.option(ChannelOption.TCP_NODELAY, true) |
||||
|
.option(ChannelOption.SO_KEEPALIVE, true) |
||||
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) |
||||
|
.handler(new ChannelInitializer<SocketChannel>() { |
||||
|
@Override |
||||
|
protected void initChannel(SocketChannel ch) { |
||||
|
ch.pipeline() |
||||
|
.addLast(new LineBasedFrameDecoder(1024))//以/r/n为结束符的解码器 |
||||
|
.addLast(new StringEncoder()) |
||||
|
.addLast(new StringDecoder()) |
||||
|
.addLast(BoditechLisSingleTrackTcpClient.this); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
doConnect(); |
||||
|
} |
||||
|
|
||||
|
public synchronized void shutdown() { |
||||
|
isShuttingDown = true; |
||||
|
if (channel != null) { |
||||
|
channel.close(); |
||||
|
} |
||||
|
group.shutdownGracefully(); |
||||
|
} |
||||
|
|
||||
|
public synchronized void tx(String data) { |
||||
|
if (channel != null && channel.isActive()) { |
||||
|
channel.writeAndFlush(data).addListener(future -> { |
||||
|
if (!future.isSuccess()) { |
||||
|
log.error("Failed to send data tail"); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
log.warn("Channel is not active, cannot send data: {}", data); |
||||
|
// 可以考虑在这里实现消息队列,等连接恢复后重发 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void doConnect() { |
||||
|
if (isShuttingDown) { |
||||
|
return; |
||||
|
} |
||||
|
log.info("try connect to {}:{}", serverIp, port); |
||||
|
bootstrap.connect(serverIp, port).addListener((ChannelFuture future) -> { |
||||
|
if (future.isSuccess()) { |
||||
|
log.info("successfully connected to {}:{}", serverIp, port); |
||||
|
channel = future.channel(); |
||||
|
} else { |
||||
|
log.error("failed,{}", future.cause().getMessage()); |
||||
|
if (!isShuttingDown) |
||||
|
group.schedule(this::doConnect, 5, TimeUnit.SECONDS); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public synchronized void channelInactive(ChannelHandlerContext ctx) throws Exception { |
||||
|
log.warn("Connection lost, trying to reconnect..."); |
||||
|
if (channel != null && channel.equals(ctx.channel())) { |
||||
|
channel = null; |
||||
|
} |
||||
|
// 重连逻辑 |
||||
|
if (!isShuttingDown) |
||||
|
group.schedule(this::doConnect, reconnectDelay, TimeUnit.SECONDS); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
// 处理接收到的消息 |
||||
|
@Override |
||||
|
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { |
||||
|
log.info("Received message from server: {}({})", msg,msg.length()); |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
BoditechLisSingleTrackTcpClient client = new BoditechLisSingleTrackTcpClient(); |
||||
|
client.start("0.0.0.0", 777); |
||||
|
|
||||
|
int count = 0; |
||||
|
while (true) { |
||||
|
try { |
||||
|
client.tx("Test message " + (++count)); |
||||
|
Thread.sleep(1000); // 主线程保持运行状态 |
||||
|
} catch (InterruptedException e) { |
||||
|
log.error("Main thread interrupted: {}", e.getMessage()); |
||||
|
Thread.currentThread().interrupt(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
package a8k.app.utils; |
||||
|
|
||||
|
import io.netty.channel.EventLoopGroup; |
||||
|
import io.netty.channel.nio.NioEventLoopGroup; |
||||
|
import io.netty.util.concurrent.DefaultThreadFactory; |
||||
|
import io.netty.util.concurrent.ThreadPerTaskExecutor; |
||||
|
|
||||
|
import java.util.concurrent.Executor; |
||||
|
|
||||
|
public class NettyUtils { |
||||
|
public static EventLoopGroup buildEventLoopGroup(String name, int nThread) { |
||||
|
DefaultThreadFactory defaultThreadFactory = new DefaultThreadFactory(name, Thread.MAX_PRIORITY); |
||||
|
Executor executor = new ThreadPerTaskExecutor(defaultThreadFactory); |
||||
|
return new NioEventLoopGroup(nThread, executor); |
||||
|
} |
||||
|
|
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue