Netty解锁接收物联网16进制数据的办法

因为和物联网设备对接的时候,16进制数据传输是普遍的传输方式,在物联网实际的项目中会频繁遇到,因此总结一份详细的攻略给大家,希望大家会喜欢

一般情况下物联网设备给服务器发送数据都会有一个开始符号和结束符号,我这边以开头字符为10 02 结束字符为 10 03的样例(开头和结束都是16进制),因为考虑到设备在断网后再接入可能会突然激增一大堆数据,如果不对数据进行分包拆包处理会导致业务数据不全,业务数据不全的情况下会导致真实数据出现乱码的情况,所以需要作出分包拆包逻辑

分包拆包逻辑如下:


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class BootNettyChannelInitializer<SocketChannel> extends ChannelInitializer<Channel> {
    @Override
    protected void initChannel(Channel ch) throws Exception {
        //分割符
        ByteBuf delimiter = Unpooled.copiedBuffer(new byte[]{16,3});
        //打印分隔符
        ch.pipeline().addLast("frameDecoder", new DelimiterBasedFrameDecoder(1024,false, delimiter));
        // ChannelOutboundHandler,依照逆序执行
        ch.pipeline().addLast("encoder", new StringEncoder());
        // 属于ChannelInboundHandler,依照顺序执行
        ch.pipeline().addLast("decoder", new StringDecoder());
        /*ch.pipeline().addLast("decoder", new MyDecoder());*/
        /**
        * 自定义ChannelInboundHandlerAdapter
        */
        ch.pipeline().addLast(new BootNettyChannelInboundHandlerAdapter());
    }
}

如果大家的业务不需要做分包拆包则可把这两行去掉,这两行代码的意思是 ,以10 03为结尾的数据单独分出一个包让后台进行接收

      //分割符
        ByteBuf delimiter = Unpooled.copiedBuffer(new byte[]{16,3});
        //打印分隔符
        ch.pipeline().addLast("frameDecoder", new DelimiterBasedFrameDecoder(1024,false, delimiter));

BootNettyChannelInboundHandlerAdapter 类如下所示,在这里我做了一个很关键的处理,因为byte最多存储8位,当接收数字大于十进制的125(也就是16进制的79的时候,数据会出现乱码,如果要解决这个乱码的问题可以采用我下面这一个机制):


        byte[] bytebuf=((String) msg).getBytes();
        // 此解码为解决 16进制在超过79的情况乱码问题
        for(int i = 0, len = bytebuf.length; i < len; i ++) {
            String hex =  Integer.toHexString(bytebuf[i] & 0xff).toUpperCase();
            message += hex.length()  == 1 ? "0" + hex : hex;
        } 


import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.dao.CarDeviceRepository;
import com.dao.CarParkRepository;
import com.dao.DeviceToParkRepository;
import com.dto.ParkDto;
import com.entity.CarDevice;
import com.entity.CarPark;
import com.entity.DeviceToPark;
import com.netty.encrypt.HexConvert;
import com.util.HttpRequestUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
public class BootNettyChannelInboundHandlerAdapter extends ChannelInboundHandlerAdapter{
    public final Logger log = LoggerFactory.getLogger(BootNettyChannelInboundHandlerAdapter.class);
    private static final int TIMEOUT = 10 * 1000; //超时时间 10s
    /**
    * 从客户端收到新的数据时,这个方法会在收到消息时被调用
    *
    * @param ctx
    * @param msg
    */
    @Override
    public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception, IOException
    {
        // 解决16进制编解码问题
        String message = "";
        byte[] bytebuf=((String) msg).getBytes();
        // 此解码为解决 16进制在超过79的情况乱码问题
        for(int i = 0, len = bytebuf.length; i < len; i ++) {
            String hex =  Integer.toHexString(bytebuf[i] & 0xff).toUpperCase();
            message += hex.length()  == 1 ? "0" + hex : hex;
        }       
    }
    /**
    * 从客户端收到新的数据、读取完成时调用
    *
    * @param ctx
    */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws IOException
    {
        //System.out.println("channelReadComplete");
        ctx.flush();
    }

    /**
    * 当出现 Throwable 对象才会被调用,即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时
    *
    * @param ctx

    * @param cause
    */

    @Override

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException
    {
        log.info("exceptionCaught");
        cause.printStackTrace();
        ctx.close();//抛出异常,断开与客户端的连接
    }

    /**
    * 客户端与服务端第一次建立连接时 执行
    *
    * @param ctx
    * @throws Exception
    */

    @Override

    public void channelActive(ChannelHandlerContext ctx) throws Exception, IOException
    {
        super.channelActive(ctx);
        ctx.channel().read();
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        //此处不能使用ctx.close(),否则客户端始终无法与服务端建立连接
        log.info("channelActive:"+clientIp+ctx.name());
    }

    /**
    * 客户端与服务端 断连时 执行
    *
    * @param ctx
    * @throws Exception
    */

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception, IOException
    {
        super.channelInactive(ctx);
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        ctx.close(); //断开连接时,必须关闭,否则造成资源浪费,并发量很大情况下可能造成宕机
        log.info("channelInactive:"+clientIp);
    }

    /**
    * 服务端当read超时, 会调用这个方法
    *
    * @param ctx
    * @param evt
    * @throws Exception
    */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception, IOException
    {
        super.userEventTriggered(ctx, evt);
        InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String clientIp = insocket.getAddress().getHostAddress();
        ctx.close();//超时时断开连接
        log.info("userEventTriggered:"+clientIp);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception{
        log.info("channelRegistered");
    }
    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception{
        log.info("channelUnregistered");
    }
    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception{
        log.info("channelWritabilityChanged");
    }
}

BootNettyServer启动类如下所示:


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
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.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class BootNettyServer {
    public void bind(int port) throws Exception {
        /**

        * 配置服务端的NIO线程组

        * NioEventLoopGroup 是用来处理I/O操作的Reactor线程组

        * bossGroup:用来接收进来的连接,workerGroup:用来处理已经被接收的连接,进行socketChannel的网络读写,

        * bossGroup接收到连接后就会把连接信息注册到workerGroup

        * workerGroup的EventLoopGroup默认的线程数是CPU核数的二倍

        */

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);

        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {

            /**

            * ServerBootstrap 是一个启动NIO服务的辅助启动类

            */

            ServerBootstrap serverBootstrap = new ServerBootstrap();

            /**

            * 设置group,将bossGroup, workerGroup线程组传递到ServerBootstrap

            */

            serverBootstrap = serverBootstrap.group(bossGroup, workerGroup);

            /**
            * ServerSocketChannel是以NIO的selector为基础进行实现的,用来接收新的连接,这里告诉Channel通过NioServerSocketChannel获取新的连接
            */
            serverBootstrap = serverBootstrap.channel(NioServerSocketChannel.class);
            /**
            * option是设置 bossGroup,childOption是设置workerGroup
            * netty 默认数据包传输大小为1024字节, 设置它可以自动调整下一次缓冲区建立时分配的空间大小,避免内存的浪费    最小  初始化  最大 (根据生产环境实际情况来定)
            * 使用对象池,重用缓冲区
            */
            // 2097152
            //serverBootstrap = serverBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64, 10496, 1048576));
            //serverBootstrap = serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64, 10496, 1048576));
            serverBootstrap = serverBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64, 10496, 20971520));
            serverBootstrap = serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(64, 10496, 20971520));
            /**
            * 设置 I/O处理类,主要用于网络I/O事件,记录日志,编码、解码消息
            */
            serverBootstrap = serverBootstrap.childHandler(new BootNettyChannelInitializer<SocketChannel>());
            System.out.println("netty server start success!");
            /**
            * 绑定端口,同步等待成功
            */
            ChannelFuture f = serverBootstrap.bind(port).sync();
            /**
            * 等待服务器监听端口关闭
            */
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
        } finally {
            /**
            * 退出,释放线程池资源
            */
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

最后只需要在Springboot的启动类里面加上


new BootNettyServer().bind(6099);

一个基于Netty搭建的TCP长连接接收16进制数据的服务器程序就搭建成功啦,就是这么简单,如果觉得实用欢迎打赏哈

微信图片_20200605205729.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352