前景:服务处于一直对外暴漏监听状态,等待第三方设备连结,连接方式一定是配在设备中的
maven 依赖
java
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.43.Final</version>
</dependency>
netty 服务配置
java
package com.demo;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
/**
* Netty 服务配置 负责启动netty服务,*等待设备连接*
* 绑定端口、初始化nettyHandler
*/
@Component
@Slf4j
public class NettyServer {
// *自定义流程处理器*
@Autowired
private NettyChannelInitializer nettyHandler;
// 监听设备的端口号
@Value("${netty.server.port}")
private int port;
public NettyServer() {
}
public NettyServer(int port) {
super();
this.port = port;
}
public void start() throws InterruptedException {
log.info("服务器启动中,端口:" + this.port);
this.bind();
}
private void bind() throws InterruptedException {
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
// 绑定端口
SocketAddress socketAddress = new InetSocketAddress(port);
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
//设定主线程和工作线程
serverBootstrap.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.localAddress(socketAddress)
// 处理器初始化
.childHandler(nettyHandler);
ChannelFuture channelFuture = serverBootstrap.bind().sync();
channelFuture.channel().closeFuture().sync();
} finally {
// 关闭服务线程
parentGroup.shutdownGracefully().sync();
childGroup.shutdownGracefully().sync();
}
}
}
流程处理器
java
package com.demo;
import com.fcev.tcpserver.protocoltokafka.decoder.ByteToArrayDecoder;
import com.fcev.tcpserver.protocoltokafka.decoder.NginxProxyProtocolHandler;
import com.fcev.tcpserver.protocoltokafka.decoder.ProtocolLogAdapter;
import com.fcev.tcpserver.encoder.ProtocolEncoder;
import com.fcev.tcpserver.handler.NettyLoggingHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Netty 通道初始化器
*
* 核心作用:
* 每当有 TBox 客户端(货车终端)TCP 连接上来时,
* 自动给这个连接绑定一整套【处理器流水线】,
* 用来处理:解码、拆包、日志、超时、发 Kafka
*/
@Component
public class NettyChannelInitializer extends ChannelInitializer<SocketChannel> {
// *注入:业务适配器* 这里写后续逻辑代码
@Autowired
private ProtocolLogAdapter protocolLogAdapter;
/**
* 当设备连接成功后,Netty 自动调用这个方法,给这个连接绑定一整套【处理器流水线】
*/
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline channelPipeline = socketChannel.pipeline();
//【1】出站编码器 → 回复应答消息
channelPipeline.addLast(new ProtocolEncoder());
// 【2】读超时处理器断联处理-5分钟
channelPipeline.addLast(new ReadTimeoutHandler(5*60));
//【3】日志打印处理器
channelPipeline.addLast(new NettyLoggingHandler());
// 【4】获取设备真实IP(如果前面挂了Nginx代理),解决Nginx转发后获取不到真实客户端IP的问题
channelPipeline.addLast(new NginxProxyProtocolHandler());
/*
【5】拆包器(最关键!解决TCP粘包/半包问题),这里的参数必须根据具体协议定
一条报文最大 4096 字节;
从第22个字节开始,能读到"报文总长度"
长度占 2 个字节
最后要 +1 字节(校验位)
解析完直接把完整包给业务层
*/
channelPipeline.addLast(new LengthFieldBasedFrameDecoder(4096, 22, 2, 1, 0));
//打印日志
// channelPipeline.addLast(new NettyLoggingHandler());
//【6】ByteBuf 转 字节数组,把Netty的二进制数据转成Java标准byte[]
channelPipeline.addLast(new ByteToArrayDecoder());
//【7】原始报文发 Kafka(核心业务)将原始报文转成字符串,发送到kafka中,用来记录日志
channelPipeline.addLast(protocolLogAdapter);
//将报文解析成ProtocolMessage对象,根据命令id发送到相应的topic
// channelPipeline.addLast(messageToKafkaAdapter);
}
}
业务适配器: ProtocolLogAdapter 编写具体的业务实现,比如ip识别、报文头部识别、channelPipeline.addLast(new LengthFieldBasedFrameDecoder(4096, 22, 2, 1, 0));针对不同的业务而定。
@Slf4j
@Component
@ChannelHandler.Sharable
public class ProtocolLogAdapter extends ChannelInboundHandlerAdapter {
// 1. 接收数据 → 最重要
@Override
channelRead(){
//释放 Netty 内存,内存泄漏、服务器宕机
ReferenceCountUtil.release(msg)
//确认 ACK,否则回发重复的消息
ctx.writeAndFlush (协议应答,根据协议而定)
}
// 2. 断开连接 → 清理资源
@Override
channelUnregistered(...)
// 3. 报错处理 → 防止程序崩
@Override
exceptionCaught(...)
}