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