Netty實(shí)戰(zhàn)三:Netty處理同一個(gè)端口上來(lái)的多條不同協(xié)議的數(shù)據(jù)

業(yè)務(wù)場(chǎng)景:跟設(shè)備通信玷氏,不同的廠家有不同的協(xié)議,這時(shí)后臺(tái)服務(wù)要兼容腋舌,比如說(shuō)盏触,設(shè)備A使用的是String字符串,設(shè)備B使用的是byte[]块饺,這時(shí)候該怎么處理呢赞辩,使用自定義解碼器,去識(shí)別是String授艰,還是byte[]辨嗽,然后轉(zhuǎn)發(fā)給相應(yīng)的業(yè)務(wù)handler處理

public class server {

    public static void main(String[] args) throws Exception {
        //1 用于接受客戶端連接的線程工作組
        EventLoopGroup boss = new NioEventLoopGroup();
        //ONE:
        //2 用于對(duì)接受客戶端連接讀寫(xiě)操作的線程工作組
        EventLoopGroup work = new NioEventLoopGroup();

        //TWO:
        //3 輔助類(lèi)。用于幫助我們創(chuàng)建NETTY服務(wù)
        ServerBootstrap b = new ServerBootstrap();
        b.group(boss, work)    //綁定兩個(gè)工作線程組
                .channel(NioServerSocketChannel.class)    //設(shè)置NIO的模式
                .option(ChannelOption.SO_BACKLOG, 1024*2)    //設(shè)置TCP緩沖區(qū)
                //.option(ChannelOption.SO_SNDBUF, 32*1024)    // 設(shè)置發(fā)送數(shù)據(jù)的緩存大小
                .option(ChannelOption.SO_RCVBUF, 32 * 1024*2*2)
                .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)    // 設(shè)置保持連接
                .childOption(ChannelOption.SO_SNDBUF, 32 * 1024)
                // 初始化綁定服務(wù)通道
                .childHandler(new initHandler());
        //b.option("receiveBufferSizePredictorFactory", new FixedReceiveBufferSizePredictorFactory(65535));
        ChannelFuture cf = b.bind(8765).sync();

        //釋放連接
        cf.channel().closeFuture().sync();
        work.shutdownGracefully();
        boss.shutdownGracefully();

    }
}

ChannelInitializer

public class initHandler extends ChannelInitializer<SocketChannel> {
    final AcceptorIdleStateTrigger idleStateTrigger=new AcceptorIdleStateTrigger();
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        pipeline.addLast(new StringEncoder());
        pipeline.addLast(new ByteArrayEncoder());

         //自定義StringDecoder淮腾,其余的都是用的netty提供的
        pipeline.addLast(new StringDecoder1());
        pipeline.addLast(new byteArrayDecoder1());
        pipeline.addLast(new ServerHandler());
        pipeline.addLast(new ServerHandler2());

    }
}

自定義StringDecoder

public class StringDecoder1 extends ByteToMessageDecoder {
    private static final Logger logger = LoggerFactory.getLogger(StringDecoder1.class);
    final int length = 2048;
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> list) throws Exception {
        try {
             in.retain();
            byte[] headArray = new byte[3];
            in.readBytes(headArray);
            String head = new String(headArray);
            // 把讀取的起始位置重置
            in.resetReaderIndex();

        
            if (TcnConstant.CMD_HEADER.equals(head)) {
                int strBeginIndex = in.readerIndex();
                int readableBytes = in.readableBytes();

                byte[] tailArray = new byte[3];
                //數(shù)據(jù)末尾
                in.getBytes(readableBytes-3,tailArray);
                String tail = new String(tailArray);
                in.resetReaderIndex();
                //沒(méi)接收完
                if (!TcnConstant.CMD_TAIL.equals(tail)) {
                    logger.info("可讀字節(jié)數(shù)readableBytes is {}",readableBytes);
                    in.readerIndex(strBeginIndex);
                    return;
                }
                ByteBufToBytes reader = new ByteBufToBytes();
                String msg = new String(reader.read(in));
                //in.retain(1);
                list.add(msg);d(in));
                list.add(msg);
            } else {
                channelHandlerContext.fireChannelRead(in);
            }
        }catch (Exception e){
            logger.info("=================");
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        System.err.println("--------數(shù)據(jù)讀異常----------: ");
        cause.printStackTrace();
        ctx.close();
    }
}

自定義byte解碼器1


import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.bangmart.android.util.DataUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * Created by zhangkai on 2018/7/20.
 */
public class byteArrayDecoder1 extends ByteToMessageDecoder {
    private static final Logger logger = LoggerFactory.getLogger(byteArrayDecoder1.class);

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> list) throws Exception {
   try {
             in.retain();
            byte[] headArray = new byte[2];
            in.readBytes(headArray);
            in.resetReaderIndex();
            FactoryEnum fe = FactoryUtil.indentify(headArray,channelHandlerContext.channel());

            if(null!=fe && FactoryEnum.XY.getName().equals(fe.getName())) {

                int readableBytes = in.readableBytes();
                int beginIndex = in.readerIndex();
                boolean heart = false;

                ByteBufToBytes reader = new ByteBufToBytes();
                byte[] data = reader.read(in);

                String hexStr = DataUtil.ByteArrToHexString(data);
                String[] hexArray = hexStr.split(" ");
                if (hexArray.length < XyConstant.headLength) {
                    if (hexArray.length == XyConstant.HEARTBEAT_MSG_FIELD.HEARTBEAT_MSG_LENGTH && hexArray[0].equalsIgnoreCase(XyConstant.heartHead)) {
                        //是心跳
                        heart = true;
                    } else {
                        return;
                    }
                }

                if (!heart) {
                    int dataLength = DataUtil.HexToInt(hexArray[XyConstant.msgLengthIndex]);
                    if (readableBytes < dataLength) {
                        logger.info("可讀字節(jié)數(shù)小于數(shù)據(jù)長(zhǎng)度");
                        in.readerIndex(beginIndex);
                        return;
                    }
                    //粘包
                    if (readableBytes > dataLength) {
                        data = Arrays.copyOf(data, dataLength);
                    }
                }
                /**
                 * 1.每一個(gè)bytebuf都有一個(gè)計(jì)數(shù)器糟需,每次調(diào)用計(jì)數(shù)器減1,當(dāng)計(jì)數(shù)器為0時(shí)則不可用谷朝。
                 * 2.如果當(dāng)前bytebuf中數(shù)據(jù)包含多條消息洲押,本條信息會(huì)通過(guò)list返回被繼續(xù)封裝成一個(gè)新的byte[]返回下一個(gè)hander處理
                 * 3.retain方法是將當(dāng)前的bytebuf計(jì)數(shù)器加1
                 * 4.如果不這樣做,會(huì)報(bào)異常 io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
                 * */
                list.add(data);}
         else {
 //這里是為了讓各自的消息一定能到各自的decoder中處理圆凰,不然會(huì)發(fā)生多次讀半包異常
                //比如說(shuō)杈帐,如果一個(gè)消息因?yàn)榫W(wǎng)絡(luò)的原因,netty需要多次(大于2)讀取才能讀完,那么就一定需要確保各自的消息
                //在各自的decoder中才能正確讀取到挑童。
                //第一次和第二次讀取時(shí)累铅,都是拼接好的全部數(shù)據(jù),當(dāng)?shù)谌巫x取時(shí)站叼,就是單個(gè)數(shù)據(jù)
                if(null == fe){
                if(null == fe){
                    byte[] msg = new byte[in.readableBytes()];
                    in.readBytes(msg);
                    logger.error("解析消息失敗娃兽,未識(shí)別消息所屬?gòu)S家{}",DataUtil.bytesToHexString(msg));
                    return;
                }
                channelHandlerContext.fireChannelRead(in);
            }
        }catch (Exception e){
            logger.info("decoder異常================={}",e);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        System.err.println("--------數(shù)據(jù)讀異常----------: ");
        cause.printStackTrace();
        ctx.close();
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
        System.err.println("--------數(shù)據(jù)讀取完畢----------");
    }
}

自定義byte解碼器2


import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import org.bangmart.android.util.DataUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;

/**
 * Created by zhangkai on 2018/7/20.
 */
public class byteArrayDecoder2 extends ByteToMessageDecoder {
    private static final Logger logger = LoggerFactory.getLogger(byteArrayDecoder1.class);

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf in, List<Object> list) throws Exception {
   try {
           ByteBufToBytes reader = new ByteBufToBytes();
            in.retain();
            int readableBytes = in.readableBytes();
            int beginIndex = in.readerIndex();

            byte[] data = reader.read(in);

            int frameLenHigh=data[2]& FactoryConstant.BYTE_MASK;
            int frameLenLow=data[3]&FactoryConstant.BYTE_MASK;
            int frameLen=frameLenHigh* (FactoryConstant. HEXADECIMAL* FactoryConstant.HEXADECIMAL)+frameLenLow;

            int dataLength =  frameLen;


            if (readableBytes < dataLength) {
                logger.info("可讀字節(jié)數(shù){}===小于===數(shù)據(jù)長(zhǎng)度{}",readableBytes,dataLength);
               // in.resetReaderIndex();
                in.readerIndex(beginIndex);
                return;
            }

            //粘包
            if (readableBytes > dataLength) {
                logger.info("可讀字節(jié)數(shù){}===大于===數(shù)據(jù)長(zhǎng)度{}",readableBytes,dataLength);
                data = Arrays.copyOf(data, dataLength);
            }

            /**
             * 1.每一個(gè)bytebuf都有一個(gè)計(jì)數(shù)器,每次調(diào)用計(jì)數(shù)器減1大年,當(dāng)計(jì)數(shù)器為0時(shí)則不可用换薄。
             * 2.如果當(dāng)前bytebuf中數(shù)據(jù)包含多條消息,本條信息會(huì)通過(guò)list返回被繼續(xù)封裝成一個(gè)新的byte[]返回下一個(gè)hander處理
             * 3.retain方法是將當(dāng)前的bytebuf計(jì)數(shù)器加1
             * 4.如果不這樣做翔试,會(huì)報(bào)異常 io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
             * */

            list.add(data);
        }catch (Exception e){
            logger.info("decoder異常================={}",e);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        System.err.println("--------數(shù)據(jù)讀異常----------: ");
        cause.printStackTrace();
        ctx.close();
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
        System.err.println("--------數(shù)據(jù)讀取完畢----------");
    }
}

業(yè)務(wù)處理Handler


/**
 * Created by zhangkai on 2018/6/11.
 */
public class ServerHandler extends ChannelInboundHandlerAdapter{
    /**
     * 當(dāng)我們通道進(jìn)行激活的時(shí)候 觸發(fā)的監(jiān)聽(tīng)方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        System.err.println("--------通道激活------------");
    }

    /**
     * 當(dāng)我們的通道里有數(shù)據(jù)進(jìn)行讀取的時(shí)候 觸發(fā)的監(jiān)聽(tīng)方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx /*NETTY服務(wù)上下文*/, Object msg /*實(shí)際的傳輸數(shù)據(jù)*/) throws Exception {

        if(msg instanceof String) {
            System.out.println("----------XXX-----"+(String) msg);
            ctx.writeAndFlush("我是XXX服務(wù)端");
        }else {
            ctx.fireChannelRead(msg);
        }

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.err.println("--------數(shù)據(jù)讀取完畢----------");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        System.err.println("--------數(shù)據(jù)讀異常----------: ");
        cause.printStackTrace();
        ctx.close();
    }
}

/**
 * Created by zhangkai on 2018/6/11.
 */
public class ServerHandler2 extends ChannelInboundHandlerAdapter{
    /**
     * 當(dāng)我們通道進(jìn)行激活的時(shí)候 觸發(fā)的監(jiān)聽(tīng)方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        System.err.println("--------通道激活------------");
    }

    /**
     * 當(dāng)我們的通道里有數(shù)據(jù)進(jìn)行讀取的時(shí)候 觸發(fā)的監(jiān)聽(tīng)方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx /*NETTY服務(wù)上下文*/, Object msg /*實(shí)際的傳輸數(shù)據(jù)*/) throws Exception {
        if(msg instanceof byte[]){
            String hexStr = DataUtil.ByteArrToHexString((byte[]) msg);
            System.out.println("----XXX------"+hexStr);
            ctx.writeAndFlush(DataUtil.hexStringToBytes("1D011E"));
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.err.println("--------數(shù)據(jù)讀取完畢----------");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        System.err.println("--------數(shù)據(jù)讀異常----------: ");
        cause.printStackTrace();
        ctx.close();
    }
}

Client測(cè)試,模擬兩個(gè)發(fā)送不同協(xié)議數(shù)據(jù)的客戶端


/**
 * Created by zhangkai on 2018/6/11.
 */
public class client {

    public static void main(String[] args) throws Exception {
        //ONE:
        //1 線程工作組
        EventLoopGroup work = new NioEventLoopGroup();

        //TWO:
        //3 輔助類(lèi)轻要。用于幫助我們創(chuàng)建NETTY服務(wù)
        Bootstrap b = new Bootstrap();
        b.group(work)    //綁定工作線程組
                .channel(NioSocketChannel.class)    //設(shè)置NIO的模式
                // 初始化綁定服務(wù)通道
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 為通道進(jìn)行初始化: 數(shù)據(jù)傳輸過(guò)來(lái)的時(shí)候會(huì)進(jìn)行攔截和執(zhí)行
                        sc.pipeline().addLast( new StringEncoder());
                        sc.pipeline().addLast( new StringDecoder());
                        sc.pipeline().addLast(new ClientHandler());
                    }
                });

        ChannelFuture cf =  b.connect("0.0.0.0", 8765).syncUninterruptibly();

        //        String str = "###3000${\"Mid\":\"1711290001\",“TimeSp”: “1511936017”}$AABBCCDD&&&";
        String str = "###5000${\"Mid\":\"7105000011\",\"MaxSlot\":\"60\",\"SlotInfo\":[{\"SlotNum\":\"1\",\"Status\":\"1\"},{\"SlotNum\":\"2\",\"Status\":\"1\"},\n" +
                "{\"SlotNum\":\"3\",\"Status\":\"1\"},{\"SlotNum\":\"4\",\"Status\":\"1\"},{\"SlotNum\":\"5\",\"Status\":\"1\"},{\"SlotNum\":\"11\",\"Status\":\"1\"},{\"SlotNum\":\"12\",\"Status\":\"1\"},{\"SlotNum\":\"13\",\"Status\":\"1\"},{\"SlotNum\":\"14\",\"Status\":\"1\"},{\"SlotNum\":\"15\",\"Status\":\"1\"},{\"SlotNum\":\"21\",\"Status\":\"1\"},{\"SlotNum\":\"22\",\"Status\":\"1\"},{\"SlotNum\":\"23\",\"Status\":\"1\"},{\"SlotNum\":\"24\",\"Status\":\"1\"},{\"SlotNum\":\"25\",\"Status\":\"1\"},{\"SlotNum\":\"26\",\"Status\":\"1\"},{\"SlotNum\":\"27\",\"Status\":\"1\"},{\"SlotNum\":\"28\",\"Status\":\"1\"},{\"SlotNum\":\"29\",\"Status\":\"1\"},{\"SlotNum\":\"30\",\"Status\":\"1\"},{\"SlotNum\":\"31\",\"Status\":\"1\"},{\"SlotNum\":\"32\",\"Status\":\"1\"},{\"SlotNum\":\"33\",\"Status\":\"1\"},{\"SlotNum\":\"34\",\"Status\":\"1\"},{\"SlotNum\":\"35\",\"Status\":\"1\"},{\"SlotNum\":\"36\",\"Status\":\"1\"},{\"SlotNum\":\"37\",\"Status\":\"1\"},{\"SlotNum\":\"38\",\"Status\":\"1\"},{\"SlotNum\":\"39\",\"Status\":\"1\"},{\"SlotNum\":\"40\",\"Status\":\"1\"},{\"SlotNum\":\"41\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"43\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},\n" +
                "{\"SlotNum\":\"3\",\"Status\":\"1\"},{\"SlotNum\":\"4\",\"Status\":\"1\"},{\"SlotNum\":\"5\",\"Status\":\"1\"},{\"SlotNum\":\"11\",\"Status\":\"1\"},{\"SlotNum\":\"12\",\"Status\":\"1\"},{\"SlotNum\":\"13\",\"Status\":\"1\"},{\"SlotNum\":\"14\",\"Status\":\"1\"},{\"SlotNum\":\"15\",\"Status\":\"1\"},{\"SlotNum\":\"21\",\"Status\":\"1\"},{\"SlotNum\":\"22\",\"Status\":\"1\"},{\"SlotNum\":\"23\",\"Status\":\"1\"},{\"SlotNum\":\"24\",\"Status\":\"1\"},{\"SlotNum\":\"25\",\"Status\":\"1\"},{\"SlotNum\":\"26\",\"Status\":\"1\"},{\"SlotNum\":\"27\",\"Status\":\"1\"},{\"SlotNum\":\"28\",\"Status\":\"1\"},{\"SlotNum\":\"29\",\"Status\":\"1\"},{\"SlotNum\":\"30\",\"Status\":\"1\"},{\"SlotNum\":\"31\",\"Status\":\"1\"},{\"SlotNum\":\"32\",\"Status\":\"1\"},{\"SlotNum\":\"33\",\"Status\":\"1\"},{\"SlotNum\":\"34\",\"Status\":\"1\"},{\"SlotNum\":\"35\",\"Status\":\"1\"},{\"SlotNum\":\"36\",\"Status\":\"1\"},{\"SlotNum\":\"37\",\"Status\":\"1\"},{\"SlotNum\":\"38\",\"Status\":\"1\"},{\"SlotNum\":\"39\",\"Status\":\"1\"},{\"SlotNum\":\"40\",\"Status\":\"1\"},{\"SlotNum\":\"41\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"43\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},\n" +
                "{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"},{\"SlotNum\":\"42\",\"Status\":\"1\"}]}$AABBCCDD&&&";
        System.out.println(str.length());
        cf.channel().writeAndFlush(str);

        //釋放連接
        cf.channel().closeFuture().sync();
        work.shutdownGracefully();
    }
}

/**
 * Created by zhangkai on 2018/6/11.
 */
public class client2 {

    public static void main(String[] args) throws Exception {
        //ONE:
        //1 線程工作組
        EventLoopGroup work = new NioEventLoopGroup();

        //TWO:
        //3 輔助類(lèi)。用于幫助我們創(chuàng)建NETTY服務(wù)
        Bootstrap b = new Bootstrap();
        b.group(work)    //綁定工作線程組
                .channel(NioSocketChannel.class)    //設(shè)置NIO的模式
                // 初始化綁定服務(wù)通道
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        sc.pipeline().addLast( new ByteArrayEncoder());
                        sc.pipeline().addLast(new ByteArrayDecoder());
                        sc.pipeline().addLast(new ClientHandler2());
                    }
                });

        ChannelFuture cf =  b.connect("0.0.0.0", 8765).syncUninterruptibly();

        // 0xFF 0x01 OX00
        byte[] bytes = DataUtil.hexStringToBytes("FF0100");

        cf.channel().writeAndFlush(bytes);

        //釋放連接
        cf.channel().closeFuture().sync();
        work.shutdownGracefully();
    }
}

用到的工具類(lèi)

/**
 *數(shù)據(jù)轉(zhuǎn)換工具
 */
public class DataUtil {
    //-------------------------------------------------------
    // 判斷奇數(shù)或偶數(shù)垦缅,位運(yùn)算冲泥,最后一位是1則為奇數(shù),為0是偶數(shù)
    static public int isOdd(int num)
    {
        return num & 0x1;
    }
    //-------------------------------------------------------
    static public int HexToInt(String inHex)//Hex字符串轉(zhuǎn)int
    {
        return Integer.parseInt(inHex, 16);
    }
    //-------------------------------------------------------
    static public byte HexToByte(String inHex)//Hex字符串轉(zhuǎn)byte
    {
        return (byte)Integer.parseInt(inHex,16);
    }
    //-------------------------------------------------------
    static public String Byte2Hex(Byte inByte)//1字節(jié)轉(zhuǎn)2個(gè)Hex字符
    {
        return String.format("%02x", inByte).toUpperCase();
    }
    //-------------------------------------------------------
    static public String ByteArrToHexString(byte[] inBytArr)//字節(jié)數(shù)組轉(zhuǎn)hex字符串
    {
        StringBuilder strBuilder=new StringBuilder();
        int j=inBytArr.length;
        for (int i = 0; i < j; i++)
        {
            strBuilder.append(Byte2Hex(inBytArr[i]));
            strBuilder.append(" ");
        }
        return strBuilder.toString();
    }
    //-------------------------------------------------------
    static public String ByteArrToHexString(byte[] inBytArr, int offset, int byteCount)//字節(jié)數(shù)組轉(zhuǎn)轉(zhuǎn)hex字符串壁涎,可選長(zhǎng)度
    {
        StringBuilder strBuilder=new StringBuilder();
        int j=byteCount;
        for (int i = offset; i < j; i++)
        {
            strBuilder.append(Byte2Hex(inBytArr[i]));
            strBuilder.append(" ");
        }
        return strBuilder.toString();
    }
    //-------------------------------------------------------
    //hex字符串轉(zhuǎn)字節(jié)數(shù)組
    static public byte[] HexToByteArr(String inHex)//hex字符串轉(zhuǎn)字節(jié)數(shù)組
    {
        int hexlen = inHex.length();
        byte[] result;
        if (isOdd(hexlen)==1)
        {//奇數(shù)
            hexlen++;
            result = new byte[(hexlen/2)];
            inHex="0"+inHex;
        }else {//偶數(shù)
            result = new byte[(hexlen/2)];
        }
        int j=0;
        for (int i = 0; i < hexlen; i+=2)
        {
            result[j]=HexToByte(inHex.substring(i,i+2));
            j++;
        }
        return result;
    }

    /**
     * 獲取無(wú)空格的hexString
     * @param str
     * @return
     */
    static public String getFomattedHexString(String str){

        StringBuilder sb = new StringBuilder();
        String[] strArr = str.split(" ");
        int len = strArr.length;

        for (int i = 0; i < len; i++) {
            sb.append(strArr[i]);
        }
        return sb.toString();
    }

/**********************************************************************/
    /* Convert byte[] to hex string.這里我們可以將byte轉(zhuǎn)換成int凡恍,然后利用Integer.toHexString(int)來(lái)轉(zhuǎn)換成16進(jìn)制字符串。
            * @param src byte[] data
    * @return hex string
    */
    public static String bytesToHexString(byte[] src){
        StringBuilder stringBuilder = new StringBuilder("");
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**
     * Convert hex string to byte[]
     * @param hexString the hex string
     * @return byte[]
     */
    public static byte[] hexStringToBytes(String hexString) {
        if (hexString == null || hexString.equals("")) {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++) {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }

    /**
     * Convert char to byte
     * @param c char
     * @return byte
     */
    private static byte charToByte(char c) {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }

    public static String getCheckXOR(String data) {
        byte[] bytes = DataUtil.HexToByteArr(data);
        byte   bcc     = caluBCC(bytes, 0, bytes.length);
        return Byte2Hex(bcc);
    }

    /*****************************數(shù)據(jù)的bcc校驗(yàn)**************************/
    public static byte caluBCC(byte[] pByte, int start, int length) {
        if(pByte == null || pByte.length <= 0 || length <= 0 || start < 0){
            return -1;
        }

        byte checkSum = 0;
        for (int i = start; i < start+length; i++) {
            checkSum ^= pByte[i];
        }
        return checkSum;
    }
    /**
     * int型轉(zhuǎn)化為byte數(shù)組
     * @param i
     * @return
     */
    public static byte[] intToByteArray1(int i) {
        byte[] result = new byte[4];
        result[0] = (byte)((i >> 24) & 0xFF);
        result[1] = (byte)((i >> 16) & 0xFF);
        result[2] = (byte)((i >> 8) & 0xFF);
        result[3] = (byte)(i & 0xFF);
        return result;
    }
}

下面是整個(gè)過(guò)程中的數(shù)據(jù)流向


image.png

注:在服務(wù)端檢測(cè)到客戶端斷線后怔球,服務(wù)端主動(dòng)關(guān)閉連接嚼酝,這時(shí)候會(huì)報(bào)這個(gè)錯(cuò)誤,
io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:101) ~[netty-all-4.0.28.Final.jar:4.0.28.Final]竟坛;

解決辦法:
1闽巩、添加ByteBuf.retain();生產(chǎn)上handler繼承的是SimpleChannelInboundHandler,解決辦法是下面這樣處理


image.png

原因參考:
https://emacsist.github.io/2018/04/28/netty%E4%B8%AD%E5%B8%B8%E8%A7%81%E7%9A%84illegalreferencecountexception%E5%BC%82%E5%B8%B8%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末担汤,一起剝皮案震驚了整個(gè)濱河市涎跨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崭歧,老刑警劉巖隅很,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異率碾,居然都是意外死亡叔营,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)播掷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)审编,“玉大人,你說(shuō)我怎么就攤上這事歧匈。” “怎么了砰嘁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵件炉,是天一觀的道長(zhǎng)勘究。 經(jīng)常有香客問(wèn)我,道長(zhǎng)斟冕,這世上最難降的妖魔是什么口糕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮磕蛇,結(jié)果婚禮上景描,老公的妹妹穿的比我還像新娘。我一直安慰自己秀撇,他們只是感情好超棺,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著呵燕,像睡著了一般棠绘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上再扭,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天氧苍,我揣著相機(jī)與錄音,去河邊找鬼泛范。 笑死让虐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的罢荡。 我是一名探鬼主播赡突,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柠傍!你這毒婦竟也來(lái)了麸俘?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤惧笛,失蹤者是張志新(化名)和其女友劉穎从媚,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體患整,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拜效,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了各谚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片紧憾。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖昌渤,靈堂內(nèi)的尸體忽然破棺而出赴穗,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布般眉,位于F島的核電站了赵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏甸赃。R本人自食惡果不足惜柿汛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望埠对。 院中可真熱鬧络断,春花似錦、人聲如沸项玛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)稍计。三九已至躁绸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間臣嚣,已是汗流浹背净刮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留硅则,地道東北人淹父。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像怎虫,于是被迫代替她去往敵國(guó)和親暑认。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法大审,類(lèi)相關(guān)的語(yǔ)法蘸际,內(nèi)部類(lèi)的語(yǔ)法,繼承相關(guān)的語(yǔ)法徒扶,異常的語(yǔ)法粮彤,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,581評(píng)論 18 399
  • netty常用API學(xué)習(xí) netty簡(jiǎn)介 Netty是基于Java NIO的網(wǎng)絡(luò)應(yīng)用框架. Netty是一個(gè)NIO...
    花丶小偉閱讀 5,990評(píng)論 0 20
  • 零、寫(xiě)在前面 本文雖然是講Netty姜骡,但實(shí)際更關(guān)注的是Netty中的NIO的實(shí)現(xiàn)导坟,所以對(duì)于Netty中的OIO(O...
    TheAlchemist閱讀 3,284評(píng)論 1 34
  • 兩個(gè)人結(jié)伴穿越一個(gè)大沙漠。途中圈澈,因?yàn)橐恍┬∈卤怪埽瑑扇顺沉似饋?lái),激動(dòng)之下康栈,一人打了另一個(gè)人一耳光递递。同時(shí)喷橙,這一耳光...
    錦影閱讀 293評(píng)論 5 3
  • 自學(xué)編程是一個(gè)艱苦的過(guò)程,同時(shí)也是一個(gè)勵(lì)志的過(guò)程漾狼。編程不是純技術(shù)重慢,而是一門(mén)藝術(shù)饥臂,編程教會(huì)人如何思考逊躁。語(yǔ)言只是工具,...
    人可工作室閱讀 372評(píng)論 0 4