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

業(yè)務(wù)場景:跟設(shè)備通信,不同的廠家有不同的協(xié)議,這時后臺服務(wù)要兼容,比如說,設(shè)備A使用的是String字符串,設(shè)備B使用的是byte[],這時候該怎么處理呢,使用自定義解碼器,去識別是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 用于對接受客戶端連接讀寫操作的線程工作組
        EventLoopGroup work = new NioEventLoopGroup();

        //TWO:
        //3 輔助類。用于幫助我們創(chuàng)建NETTY服務(wù)
        ServerBootstrap b = new ServerBootstrap();
        b.group(boss, work)    //綁定兩個工作線程組
                .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();
                //沒接收完
                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ù)長度");
                        in.readerIndex(beginIndex);
                        return;
                    }
                    //粘包
                    if (readableBytes > dataLength) {
                        data = Arrays.copyOf(data, dataLength);
                    }
                }
                /**
                 * 1.每一個bytebuf都有一個計數(shù)器,每次調(diào)用計數(shù)器減1,當(dāng)計數(shù)器為0時則不可用。
                 * 2.如果當(dāng)前bytebuf中數(shù)據(jù)包含多條消息,本條信息會通過list返回被繼續(xù)封裝成一個新的byte[]返回下一個hander處理
                 * 3.retain方法是將當(dāng)前的bytebuf計數(shù)器加1
                 * 4.如果不這樣做,會報異常 io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
                 * */
                list.add(data);}
         else {
 //這里是為了讓各自的消息一定能到各自的decoder中處理,不然會發(fā)生多次讀半包異常
                //比如說,如果一個消息因為網(wǎng)絡(luò)的原因,netty需要多次(大于2)讀取才能讀完,那么就一定需要確保各自的消息
                //在各自的decoder中才能正確讀取到。
                //第一次和第二次讀取時,都是拼接好的全部數(shù)據(jù),當(dāng)?shù)谌巫x取時,就是單個數(shù)據(jù)
                if(null == fe){
                if(null == fe){
                    byte[] msg = new byte[in.readableBytes()];
                    in.readBytes(msg);
                    logger.error("解析消息失敗,未識別消息所屬廠家{}",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ù)長度{}",readableBytes,dataLength);
               // in.resetReaderIndex();
                in.readerIndex(beginIndex);
                return;
            }

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

            /**
             * 1.每一個bytebuf都有一個計數(shù)器,每次調(diào)用計數(shù)器減1,當(dāng)計數(shù)器為0時則不可用。
             * 2.如果當(dāng)前bytebuf中數(shù)據(jù)包含多條消息,本條信息會通過list返回被繼續(xù)封裝成一個新的byte[]返回下一個hander處理
             * 3.retain方法是將當(dāng)前的bytebuf計數(shù)器加1
             * 4.如果不這樣做,會報異常 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)我們通道進行激活的時候 觸發(fā)的監(jiān)聽方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

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

    /**
     * 當(dāng)我們的通道里有數(shù)據(jù)進行讀取的時候 觸發(fā)的監(jiān)聽方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx /*NETTY服務(wù)上下文*/, Object msg /*實際的傳輸數(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)我們通道進行激活的時候 觸發(fā)的監(jiān)聽方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

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

    /**
     * 當(dāng)我們的通道里有數(shù)據(jù)進行讀取的時候 觸發(fā)的監(jiān)聽方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx /*NETTY服務(wù)上下文*/, Object msg /*實際的傳輸數(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測試,模擬兩個發(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 輔助類。用于幫助我們創(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 {
                        // 為通道進行初始化: 數(shù)據(jù)傳輸過來的時候會進行攔截和執(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 輔助類。用于幫助我們創(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();
    }
}

用到的工具類

/**
 *數(shù)據(jù)轉(zhuǎn)換工具
 */
public class DataUtil {
    //-------------------------------------------------------
    // 判斷奇數(shù)或偶數(shù),位運算,最后一位是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個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字符串,可選長度
    {
        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;
    }

    /**
     * 獲取無空格的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)來轉(zhuǎn)換成16進制字符串。
            * @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校驗**************************/
    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;
    }
}

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


image.png

注:在服務(wù)端檢測到客戶端斷線后,服務(wù)端主動關(guān)閉連接,這時候會報這個錯誤,
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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,679評論 18 399
  • netty常用API學(xué)習(xí) netty簡介 Netty是基于Java NIO的網(wǎng)絡(luò)應(yīng)用框架. Netty是一個NIO...
    花丶小偉閱讀 6,120評論 0 20
  • 零、寫在前面 本文雖然是講Netty,但實際更關(guān)注的是Netty中的NIO的實現(xiàn),所以對于Netty中的OIO(O...
    TheAlchemist閱讀 3,388評論 1 34
  • 兩個人結(jié)伴穿越一個大沙漠。途中,因為一些小事,兩人吵了起來,激動之下,一人打了另一個人一耳光。同時,這一耳光...
    錦影閱讀 386評論 5 3
  • 自學(xué)編程是一個艱苦的過程,同時也是一個勵志的過程。編程不是純技術(shù),而是一門藝術(shù),編程教會人如何思考。語言只是工具,...
    人可工作室閱讀 423評論 0 4

友情鏈接更多精彩內(nèi)容