一、配置文件application.properties添加配置
#netty 配置
server.ip=127.0.0.1
server.ip.port=6060
二、创建NettyServer类
@Slf4j
@Component
public class NettyServer {
@Autowired
private ServerChannelInitializer serverInit;
private final EventLoopGroup parentGroup = new NioEventLoopGroup();
private final EventLoopGroup childGroup = new NioEventLoopGroup();
private Channel channel;
/**
* 启动服务
* @param address
* @return
*/
public ChannelFuture start(InetSocketAddress address) {
ChannelFuture f = null;
try {
ServerBootstrap b = new ServerBootstrap();
b.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.childHandler(serverInit)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true);
b.option(ChannelOption.SO_BACKLOG, 1024);
b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000);
f = b.bind(address).syncUninterruptibly();
channel = f.channel();
f.channel().closeFuture().sync();
} catch (Exception e) {
log.error("netty server 发生错误 :{}",e.getMessage());
}finally {
if(f!=null && f.isSuccess()) {
log.info("Netty server 监听 {} 端口号 :{}",address.getHostName(),address.getPort());
}
}
return f;
}
public void destroy() {
log.info("关闭netty");
if(channel != null) { channel.close();}
childGroup.shutdownGracefully();
parentGroup.shutdownGracefully();
log.info("成功关闭netty");
}
}
三、创建主启动类,并在启动类中添加netty的服务监听的启动。
@SpringBootApplication
public class Application implements CommandLineRunner{
private static final Logger logger = LoggerFactory.getLogger(Application.class);
@Autowired
private NettyServer nettyServer;
@Value("${server.ip}")
private String ip;
@Value("${server.ip.port}")
private int port;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) throws Exception {
logger.info("服务器ip:{} ,端口号 port:{}",ip,port);
InetSocketAddress address = new InetSocketAddress(ip, port);
ChannelFuture f = nettyServer.start(address);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
nettyServer.destroy();
} catch (Exception e) {
logger.error(e.getMessage());
}
}
});
//服务端管道关闭的监听器并同步阻塞,直到channel关闭,线程才会往下执行,结束进程
if(f!=null)
f.channel().closeFuture().syncUninterruptibly();
}
四、通道初始化
@Component
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel>{
@Autowired
private ApplicationContext appContext;
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(LengthFieldBasedFrameDecoder());
socketChannel.pipeline().addLast(LengthFieldBasedFrameDecoder3());
socketChannel.pipeline().addLast(LengthFieldBasedFrameDecoder2());
socketChannel.pipeline().addLast(MessageToMessageEncoder());
socketChannel.pipeline().addLast(MessageToMessageEncoder3());
socketChannel.pipeline().addLast(MessageToMessageEncoder2());
socketChannel.pipeline().addLast(new IdleStateHandler(35, 35, 35));
socketChannel.pipeline().addLast(serverHandler());
}
private ServiceHandler serverHandler() {
ServiceHandler serverHandler = appContext.getBean(ServiceHandler.class);
return serverHandler;
}
private LengthFieldBasedFrameDecoder LengthFieldBasedFrameDecoder() {
RechargeLengthFieldBasedFrameDecoder r =appContext.getBean(LengthFieldBasedFrameDecoder.class);
return r;
}
private LengthFieldBasedFrameDecoder2 LengthFieldBasedFrameDecoder2() {
LengthFieldBasedFrameDecoder2 r =appContext.getBean(LengthFieldBasedFrameDecoder2.class);
return r;
}
private LengthFieldBasedFrameDecoder3 LengthFieldBasedFrameDecoder3() {
LengthFieldBasedFrameDecoder3 r =appContext.getBean(LengthFieldBasedFrameDecoder3.class);
return r;
}
private MessageToMessageEncoder MessageToMessageEncoder() {
MessageToMessageEncoder r =appContext.getBean(MessageToMessageEncoder.class);
return r;
}
private MessageToMessageEncoder2 MessageToMessageEncoder2() {
MessageToMessageEncoder2 r =appContext.getBean(MessageToMessageEncoder2.class);
return r;
}
private MessageToMessageEncoder3 MessageToMessageEncoder3() {
MessageToMessageEncoder3 r =appContext.getBean(MessageToMessageEncoder3.class);
return r;
}
}
五、自定义解码器
@Scope("prototype")
@Component
public class LengthFieldBasedFrameDecoder3 extends LengthFieldBasedFrameDecoder {
private static final Logger log = LoggerFactory
.getLogger(com.it.foundwater.util.LengthFieldBasedFrameDecoder3.class);
public DALengthFieldBasedFrameDecoder3() {
super(262144, 2, 1, -2, 0);
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf inByteData = (ByteBuf) super.decode(ctx, in);
if (inByteData == null || inByteData.readableBytes() == 0) {
log.info("没有信息,跳过");
ByteBuf frame = in.retainedDuplicate();
in.skipBytes(in.readableBytes());
return frame;
}
try {
String data = PackageDataUtils.decodeByteBuff(inByteData);
log.info("收到数据:{}", data);
data = PackageDataUtils.DACheckDataFormat(data);
if (StringUtils.isEmpty(data)) {
log.info("验证失败!");
return in;
}
Object obj = filterDA(data);
if (obj != null) {
return obj;
}
return in;
} catch (Exception e) {
log.error("解析协议失败", e);
return in;
}
}
private Object filterDA(String data) {
DADataBean bean = new DADataBean();
PackageDataUtils.DataBean(bean, data);
return bean;
}
protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
log.info("自定义长度解码器offset:" + offset + " length:" + length + " dataSize:" + buf.readableBytes());
long frameLength = 0L;
try {
buf = buf.order(order);
String lenStr = "";
String temp = null;
int i, len;
for (i = 0, len = length; i < len; i++) {
temp = Integer.toHexString(buf.getUnsignedByte(i) & 0xFF);
if (temp.length() < 2) {
temp = "0" + temp;
}
lenStr = String.valueOf(lenStr) + temp;
}
log.info("帧头数据:" + lenStr);
lenStr = "";
temp = null;
for (i = 0, len = length; i < len; i++) {
temp = Integer.toHexString(buf.getUnsignedByte(offset + i) & 0xFF);
if (temp.length() < 2) {
temp = "0" + temp;
}
lenStr = String.valueOf(lenStr) + temp;
}
lenStr = PackageDataUtils.convert(lenStr);
frameLength = Long.parseLong(PackageDataUtils.hexToLongString(lenStr));
log.info("帧长度:" + frameLength + " lenStr:" + lenStr);
} catch (Exception e) {
throw new DecoderException("unsupported lengthFieldLength: " + length + " (expected: 1, 2, 3, 4, or 8)");
}
offset = 0;
return frameLength;
}
}
六、数据处理工具类
@Slf4j
public class PackageDataUtils {
//解析报文(将字报文转换为字符串报文)
public static String decodeByteBuff(ByteBuf in) {
log.debug("解析开始");
StringBuilder stringBuilder = new StringBuilder();
StringBuilder str = new StringBuilder();
try {
byte[] b = new byte[in.readableBytes()];
in.readBytes(b);
for (int i = 0; i < b.length; i++) {
str.append(Byte.toString(b[i]));
int v = b[i] & 0xFF;
String hv = Integer.toHexString(v);
if (hv.length() < 2) {
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
}catch (Exception e) {
log.error("数据解码异常:",e);
}finally {
//ReferenceCountUtil.release(in);
}
String data = stringBuilder.toString();
log.info("原始数据:" + str.toString());
return data;
}
//校验报文
public static String CheckDataFormat(String data) {
String head = data.substring(0, 4);
if (!ConstantUtils.HEAD.equals(head)) {
log.info("帧起始符校验失败!");
return null;
}
int lenth = data.length();
String Totalstr = data.substring(8, 10);
String checkStr = intToHexString(lenth / 2, 2);
log.info("{}计算后的报文长度:{}", Totalstr, Integer.valueOf(lenth));
if (!Totalstr.equals(checkStr)) {
log.info("报文长度校验失败!");
return null;
}
String responseCode = dataFormatting(data.substring(lenth - 8, lenth - 4));
String dataCode = data.substring(2, lenth - 8);
String crcCode = CRC16ToString(dataCode);
crcCode = StringUtil.formatString(crcCode, 4);
if (!responseCode.equalsIgnoreCase(crcCode)) {
log.info("心跳包的校验码:{}", responseCode);
log.info("CRC生成的校验码:{}", crcCode);
log.info("校验码校验失败!");
return null;
}
return data;
}
//将报文解析bean
public static void DataBean(DADataBean dataBean, String data) {
int length = data.length();
dataBean.setStartFrame(data.substring(0, 4));
dataBean.setResponseTotal(hexValToInt(data.substring(8, 10)).toString());
dataBean.setResponseVersion(data.substring(10, 12));
dataBean.setDeviceNoLength("6");
dataBean.setDeviceNo(data.substring(20,22)+data.substring(18,20)+data.substring(4,6));
dataBean.setData(data.substring(22, length - 8));
dataBean.setCheckCode(data.substring(length - 8, length - 4));
dataBean.setEndFlag(data.substring(length - 4, length));
}
}
七、定义适配器处理
@Slf4j
@Scope("prototype")
@Component
public class ServiceHandler extends ChannelInboundHandlerAdapter{
@Autowired
private AbnormalAnalysisService abnormalAnalysisService;
@Autowired
private DeviceMapper deviceMapper;
@Autowired
private DeviceReadDataHandleSecondService deviceReadDataHandleSecondService;
@Autowired
private ProcessDeviceService processDeviceService;
/**
* 接收到数据完成后回调,注意粘包和拆包问题
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof DataBean ) {
DataBean dataBean = (DataBean)msg;
processDeviceService.processCYdevice(dataBean);
if(ctx.channel()!=null) {
//PackageDataUtils.sendMessageClient(bean, ctx.channel());
dataBean.sendSocketToClient(ctx.channel());
}
} else if (msg instanceof DataBean1) {
DataBean1 dataBean1= (DataBean1)msg;
processDeviceService.processSYdevice(DataBean1);
if (ctx.channel() != null)
{
dataBean1.sendSocketToClient(ctx.channel());
}
}else {
log.info("接收不到数据或格式错误");
}
}
/**
* 在读取操作期间,有异常抛出时会调用。
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
/**
* 通道激活时触发,当客户端连接成功后调用
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String clientIP = "";
int port =0000;
try {
InetSocketAddress ipScoketAddress = (InetSocketAddress) ctx.channel().remoteAddress();
clientIP = ipScoketAddress.getAddress().getHostAddress();
port = ipScoketAddress.getPort();
} catch (Exception e) {
log.error(e.getMessage());
}
log.info("客户端连接的ip地址:{} ,端口号 :{}",clientIP,port);
}
/**
* 超时时间发生回调
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
if (evt instanceof IdleStateEvent) { // 2
IdleStateEvent event = (IdleStateEvent) evt;
String type = "";
if (event.state() == IdleState.READER_IDLE) {
type = "read idle timeout";
} else if (event.state() == IdleState.WRITER_IDLE) {
type = "write idle timeout";
} else if (event.state() == IdleState.ALL_IDLE) {
type = "all idle timeout";
if(ctx!=null) {
ctx.close();
}
}
log.debug( ctx.channel().id().asLongText()+"超时类型:" + type);
} else {
super.userEventTriggered(ctx, evt);
}
}
}
八、消息编码器(应答客户端)
@Slf4j
public class MessageToMessageEncoder extends MessageToMessageEncoder<DataBean> {
protected void encode(ChannelHandlerContext ctx, DataBean msg, List<Object> out) throws Exception {
try {
String dataStr = PackageDataUtils.syBuilderSocketDataStr(msg);
log.info("应答客户端:{}", dataStr);
ByteBuf bb = ctx.channel().alloc().buffer(dataStr.length() / 2);
for (int i = 0, len = dataStr.length(); i < len; i += 2) {
bb.writeByte((byte) Integer.parseInt(dataStr.substring(i, i + 2), 16));
}
out.add(bb);
} catch (Exception e) {
e.printStackTrace();
}
}
}