Netty实战三:Netty处理同一个端口上来的多条不同协议的数据

业务场景:跟设备通信,不同的厂家有不同的协议,这时后台服务要兼容,比如说,设备A使用的是String字符串,设备B使用的是byte[],这时候该怎么处理呢,使用自定义解码器,去识别是String,还是byte[],然后转发给相应的业务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 辅助类。用于帮助我们创建NETTY服务
        ServerBootstrap b = new ServerBootstrap();
        b.group(boss, work)    //绑定两个工作线程组
                .channel(NioServerSocketChannel.class)    //设置NIO的模式
                .option(ChannelOption.SO_BACKLOG, 1024*2)    //设置TCP缓冲区
                //.option(ChannelOption.SO_SNDBUF, 32*1024)    // 设置发送数据的缓存大小
                .option(ChannelOption.SO_RCVBUF, 32 * 1024*2*2)
                .childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)    // 设置保持连接
                .childOption(ChannelOption.SO_SNDBUF, 32 * 1024)
                // 初始化绑定服务通道
                .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];
                //数据末尾
                in.getBytes(readableBytes-3,tailArray);
                String tail = new String(tailArray);
                in.resetReaderIndex();
                //没接收完
                if (!TcnConstant.CMD_TAIL.equals(tail)) {
                    logger.info("可读字节数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("--------数据读异常----------: ");
        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("可读字节数小于数据长度");
                        in.readerIndex(beginIndex);
                        return;
                    }
                    //粘包
                    if (readableBytes > dataLength) {
                        data = Arrays.copyOf(data, dataLength);
                    }
                }
                /**
                 * 1.每一个bytebuf都有一个计数器,每次调用计数器减1,当计数器为0时则不可用。
                 * 2.如果当前bytebuf中数据包含多条消息,本条信息会通过list返回被继续封装成一个新的byte[]返回下一个hander处理
                 * 3.retain方法是将当前的bytebuf计数器加1
                 * 4.如果不这样做,会报异常 io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
                 * */
                list.add(data);}
         else {
 //这里是为了让各自的消息一定能到各自的decoder中处理,不然会发生多次读半包异常
                //比如说,如果一个消息因为网络的原因,netty需要多次(大于2)读取才能读完,那么就一定需要确保各自的消息
                //在各自的decoder中才能正确读取到。
                //第一次和第二次读取时,都是拼接好的全部数据,当第三次读取时,就是单个数据
                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("--------数据读异常----------: ");
        cause.printStackTrace();
        ctx.close();
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
        System.err.println("--------数据读取完毕----------");
    }
}

自定义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("可读字节数{}===小于===数据长度{}",readableBytes,dataLength);
               // in.resetReaderIndex();
                in.readerIndex(beginIndex);
                return;
            }

            //粘包
            if (readableBytes > dataLength) {
                logger.info("可读字节数{}===大于===数据长度{}",readableBytes,dataLength);
                data = Arrays.copyOf(data, dataLength);
            }

            /**
             * 1.每一个bytebuf都有一个计数器,每次调用计数器减1,当计数器为0时则不可用。
             * 2.如果当前bytebuf中数据包含多条消息,本条信息会通过list返回被继续封装成一个新的byte[]返回下一个hander处理
             * 3.retain方法是将当前的bytebuf计数器加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("--------数据读异常----------: ");
        cause.printStackTrace();
        ctx.close();
    }
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
        System.err.println("--------数据读取完毕----------");
    }
}

业务处理Handler


/**
 * Created by zhangkai on 2018/6/11.
 */
public class ServerHandler extends ChannelInboundHandlerAdapter{
    /**
     * 当我们通道进行激活的时候 触发的监听方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

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

    /**
     * 当我们的通道里有数据进行读取的时候 触发的监听方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx /*NETTY服务上下文*/, Object msg /*实际的传输数据*/) throws Exception {

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

    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.err.println("--------数据读取完毕----------");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        System.err.println("--------数据读异常----------: ");
        cause.printStackTrace();
        ctx.close();
    }
}

/**
 * Created by zhangkai on 2018/6/11.
 */
public class ServerHandler2 extends ChannelInboundHandlerAdapter{
    /**
     * 当我们通道进行激活的时候 触发的监听方法
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

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

    /**
     * 当我们的通道里有数据进行读取的时候 触发的监听方法
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx /*NETTY服务上下文*/, Object msg /*实际的传输数据*/) 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("--------数据读取完毕----------");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        System.err.println("--------数据读异常----------: ");
        cause.printStackTrace();
        ctx.close();
    }
}

Client测试,模拟两个发送不同协议数据的客户端


/**
 * 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 辅助类。用于帮助我们创建NETTY服务
        Bootstrap b = new Bootstrap();
        b.group(work)    //绑定工作线程组
                .channel(NioSocketChannel.class)    //设置NIO的模式
                // 初始化绑定服务通道
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 为通道进行初始化: 数据传输过来的时候会进行拦截和执行
                        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 辅助类。用于帮助我们创建NETTY服务
        Bootstrap b = new Bootstrap();
        b.group(work)    //绑定工作线程组
                .channel(NioSocketChannel.class)    //设置NIO的模式
                // 初始化绑定服务通道
                .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();
    }
}

用到的工具类

/**
 *数据转换工具
 */
public class DataUtil {
    //-------------------------------------------------------
    // 判断奇数或偶数,位运算,最后一位是1则为奇数,为0是偶数
    static public int isOdd(int num)
    {
        return num & 0x1;
    }
    //-------------------------------------------------------
    static public int HexToInt(String inHex)//Hex字符串转int
    {
        return Integer.parseInt(inHex, 16);
    }
    //-------------------------------------------------------
    static public byte HexToByte(String inHex)//Hex字符串转byte
    {
        return (byte)Integer.parseInt(inHex,16);
    }
    //-------------------------------------------------------
    static public String Byte2Hex(Byte inByte)//1字节转2个Hex字符
    {
        return String.format("%02x", inByte).toUpperCase();
    }
    //-------------------------------------------------------
    static public String ByteArrToHexString(byte[] inBytArr)//字节数组转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)//字节数组转转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字符串转字节数组
    static public byte[] HexToByteArr(String inHex)//hex字符串转字节数组
    {
        int hexlen = inHex.length();
        byte[] result;
        if (isOdd(hexlen)==1)
        {//奇数
            hexlen++;
            result = new byte[(hexlen/2)];
            inHex="0"+inHex;
        }else {//偶数
            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转换成int,然后利用Integer.toHexString(int)来转换成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);
    }

    /*****************************数据的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型转化为byte数组
     * @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;
    }
}

下面是整个过程中的数据流向


image.png

注:在服务端检测到客户端断线后,服务端主动关闭连接,这时候会报这个错误,
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();生产上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/

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,577评论 18 399
  • netty常用API学习 netty简介 Netty是基于Java NIO的网络应用框架. Netty是一个NIO...
    花丶小伟阅读 5,988评论 0 20
  • 零、写在前面 本文虽然是讲Netty,但实际更关注的是Netty中的NIO的实现,所以对于Netty中的OIO(O...
    TheAlchemist阅读 3,284评论 1 34
  • 两个人结伴穿越一个大沙漠。途中,因为一些小事,两人吵了起来,激动之下,一人打了另一个人一耳光。同时,这一耳光...
    锦影阅读 293评论 5 3
  • 自学编程是一个艰苦的过程,同时也是一个励志的过程。编程不是纯技术,而是一门艺术,编程教会人如何思考。语言只是工具,...
    人可工作室阅读 372评论 0 4