首先在项目中引入依赖
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> </dependency>
要连接供应商的道闸等设备,所以是作为客户端来连接。
大概的初始化代码如下,SCREEN屏幕用的是UDP协议所以有略微的差异。
重点关注ChannelInitializer~initChannel,在这里面你可以加入自己实现的handler,连接建立后硬件发送指令会通过handler接收处理。
这里可以加入心跳机制和自定义的指令解析(处理半包粘包,处理成自己想要的信息)
DeviceClient client = this; Bootstrap bs = new Bootstrap(); NioEventLoopGroup group = new NioEventLoopGroup(); switch (device.getDtype()){ case "SCREEN": //端口5000 // 接收按钮指令 bs.group(group); bs.channel(NioDatagramChannel.class) .handler(new ChannelInitializer<NioDatagramChannel>() { @Override protected void initChannel(NioDatagramChannel ch) throws Exception { // ch.pipeline().addLast(new IdleStateHandler(15,0,0, TimeUnit.SECONDS)); //心跳 ch.pipeline().addLast(new ScreenHandler(client)); } }); this.bootstrap = bs; this.loopGroup = group; break; case "DZ": //端口23 bs.group(group); bs.channel(NioSocketChannel.class) .option(ChannelOption.SO_KEEPALIVE, true) // .option(ChannelOption.SO_RCVBUF,10) //测试粘包半包 .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast("decoder", new DZDecoder()); ch.pipeline().addLast(new DZHandler(client)); } }); this.bootstrap = bs; this.loopGroup = group; break; …………
- 心跳机制 :new IdleStateHandler(15,0,0, TimeUnit.SECONDS) 前三个参数是超时的时间设置即多少秒内没有收到操作分别对应 读/写/读写,如何处理看下面的handler
- handler的示例代码
public class DZHandler extends SimpleChannelInboundHandler { @Override public void channelActive(ChannelHandlerContext ctx)//设备加载成功 @Override protected void channelRead0(ChannelHandlerContext ctx, Object msg) //msg为返回的信息 @Override public void channelInactive(ChannelHandlerContext ctx) //可以加入断线重连的机制 //这个方法是用来检测心跳,一般检测一个写超时即可 @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; if (event.state() == IdleState.WRITER_IDLE) { //写超时的操作 } else if (event.state() == IdleState.READER_IDLE) { //读超时的操作 } else if (event.state() == IdleState.ALL_IDLE) { //读写超时的操作 } } } }
- 用decoder处理半包和粘包的问题
比如现在接收到的指令协议是这样规定的 第4个字节表示数据长度,所以程序检测到指令超过了4字节,开始计算指令长度完全包含了内容,则获取指令并解析,否则从头计算。
public class DZDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> out) throws Exception { // 同步码 帧类型 数据长度 数据内容 // 2 1 1 N int totalLength = in.readableBytes(); if(totalLength<4){ return; } in.markReaderIndex(); byte[] bytes = new byte[3]; in.readBytes(bytes); String msg = ClientHelp.byteArrayToHexString(bytes); if(msg.startsWith("AA5530")){ bytes = new byte[1];//读取长度 in.readBytes(bytes); int contentByteLength = Integer.parseInt(ClientHelp.byteArrayToHexString(bytes), 16); int msgByteLength = contentByteLength + 4; in.resetReaderIndex(); if(totalLength>=msgByteLength){ byte[] msgByte = new byte[msgByteLength]; in.readBytes(msgByte); out.add(ClientHelp.byteArrayToHexString(msgByte)); } }else{ in.resetReaderIndex(); } } }
一般的指令会含有 内容长度 + 验证码(CRC16)之类的,如果需要我们给设备传递指令,比如让音响播放,道闸开启/关闭,都需要程序自动去编译成指令(这个可以结合供应商的协议文档)。
一般涉及到的就是16进制解析CRC16BCD码之类的,附件提供了一个编译协议的工具类,有需要的可以瞅瞅
https://download.csdn.net/download/xuchzhi/88774903