首先在项目中引入依赖
<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进制解析\CRC16\BCD码之类的,附件提供了一个编译协议的工具类,有需要的可以瞅瞅