• Netty学习笔记(一)


    NIO介绍

    字节流

    入门案例

    import lombok.extern.slf4j.Slf4j;
    
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    @Slf4j
    public class TestByteBuffer {
        public static void main(String[] args) {
            try (FileChannel channel=new FileInputStream("D:\\Code\\Netty\\src\\data.txt").getChannel()){
                //准备缓冲区
                ByteBuffer buffer=ByteBuffer.allocate(10);
                while (true){
                    //从 channel读取数据,向buffer写入
                    int len = channel.read(buffer);
                    log.debug("读取到的字节数{}",len);
                    if(len == -1){
                        break;
                    }
                    //打印buffer内容
                    buffer.flip();  //切换至只读模式
                    while(buffer.hasRemaining()){  //是否还有剩余未读数据
                        byte b=buffer.get();
                        log.debug("实际字节{}",(char)b);
                    }
                    //切换为写模式
                    buffer.clear();
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    工具类

    import io.netty.util.internal.StringUtil;
    import java.nio.ByteBuffer;
    import static io.netty.util.internal.MathUtil.isOutOfBounds;
    import static io.netty.util.internal.StringUtil.NEWLINE;
    
    public class ByteBufferUtil {
        private static final char[] BYTE2CHAR = new char[256];
        private static final char[] HEXDUMP_TABLE = new char[256 * 4];
        private static final String[] HEXPADDING = new String[16];
        private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4];
        private static final String[] BYTE2HEX = new String[256];
        private static final String[] BYTEPADDING = new String[16];
    
        static {
            final char[] DIGITS = "0123456789abcdef".toCharArray();
            for (int i = 0; i < 256; i++) {
                HEXDUMP_TABLE[i << 1] = DIGITS[i >>> 4 & 0x0F];
                HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F];
            }
    
            int i;
    
            // Generate the lookup table for hex dump paddings
            for (i = 0; i < HEXPADDING.length; i++) {
                int padding = HEXPADDING.length - i;
                StringBuilder buf = new StringBuilder(padding * 3);
                for (int j = 0; j < padding; j++) {
                    buf.append("   ");
                }
                HEXPADDING[i] = buf.toString();
            }
    
            // Generate the lookup table for the start-offset header in each row (up to 64KiB).
            for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i++) {
                StringBuilder buf = new StringBuilder(12);
                buf.append(NEWLINE);
                buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L));
                buf.setCharAt(buf.length() - 9, '|');
                buf.append('|');
                HEXDUMP_ROWPREFIXES[i] = buf.toString();
            }
    
            // Generate the lookup table for byte-to-hex-dump conversion
            for (i = 0; i < BYTE2HEX.length; i++) {
                BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i);
            }
    
            // Generate the lookup table for byte dump paddings
            for (i = 0; i < BYTEPADDING.length; i++) {
                int padding = BYTEPADDING.length - i;
                StringBuilder buf = new StringBuilder(padding);
                for (int j = 0; j < padding; j++) {
                    buf.append(' ');
                }
                BYTEPADDING[i] = buf.toString();
            }
    
            // Generate the lookup table for byte-to-char conversion
            for (i = 0; i < BYTE2CHAR.length; i++) {
                if (i <= 0x1f || i >= 0x7f) {
                    BYTE2CHAR[i] = '.';
                } else {
                    BYTE2CHAR[i] = (char) i;
                }
            }
        }
    
        /**
         * 打印所有内容
         * @param buffer
         */
        public static void debugAll(ByteBuffer buffer) {
            int oldlimit = buffer.limit();
            buffer.limit(buffer.capacity());
            StringBuilder origin = new StringBuilder(256);
            appendPrettyHexDump(origin, buffer, 0, buffer.capacity());
            System.out.println("+--------+-------------------- all ------------------------+----------------+");
            System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), oldlimit);
            System.out.println(origin);
            buffer.limit(oldlimit);
        }
    
        /**
         * 打印可读取内容
         * @param buffer
         */
        public static void debugRead(ByteBuffer buffer) {
            StringBuilder builder = new StringBuilder(256);
            appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position());
            System.out.println("+--------+-------------------- read -----------------------+----------------+");
            System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), buffer.limit());
            System.out.println(builder);
        }
    
        private static void appendPrettyHexDump(StringBuilder dump, ByteBuffer buf, int offset, int length) {
            if (isOutOfBounds(offset, length, buf.capacity())) {
                throw new IndexOutOfBoundsException(
                        "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length
                                + ") <= " + "buf.capacity(" + buf.capacity() + ')');
            }
            if (length == 0) {
                return;
            }
            dump.append(
                    "         +-------------------------------------------------+" +
                            NEWLINE + "         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |" +
                            NEWLINE + "+--------+-------------------------------------------------+----------------+");
    
            final int startIndex = offset;
            final int fullRows = length >>> 4;
            final int remainder = length & 0xF;
    
            // Dump the rows which have 16 bytes.
            for (int row = 0; row < fullRows; row++) {
                int rowStartIndex = (row << 4) + startIndex;
    
                // Per-row prefix.
                appendHexDumpRowPrefix(dump, row, rowStartIndex);
    
                // Hex dump
                int rowEndIndex = rowStartIndex + 16;
                for (int j = rowStartIndex; j < rowEndIndex; j++) {
                    dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
                }
                dump.append(" |");
    
                // ASCII dump
                for (int j = rowStartIndex; j < rowEndIndex; j++) {
                    dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
                }
                dump.append('|');
            }
    
            // Dump the last row which has less than 16 bytes.
            if (remainder != 0) {
                int rowStartIndex = (fullRows << 4) + startIndex;
                appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);
    
                // Hex dump
                int rowEndIndex = rowStartIndex + remainder;
                for (int j = rowStartIndex; j < rowEndIndex; j++) {
                    dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
                }
                dump.append(HEXPADDING[remainder]);
                dump.append(" |");
    
                // Ascii dump
                for (int j = rowStartIndex; j < rowEndIndex; j++) {
                    dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
                }
                dump.append(BYTEPADDING[remainder]);
                dump.append('|');
            }
    
            dump.append(NEWLINE +
                    "+--------+-------------------------------------------------+----------------+");
        }
    
        private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {
            if (row < HEXDUMP_ROWPREFIXES.length) {
                dump.append(HEXDUMP_ROWPREFIXES[row]);
            } else {
                dump.append(NEWLINE);
                dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L));
                dump.setCharAt(dump.length() - 9, '|');
                dump.append('|');
            }
        }
    
        public static short getUnsignedByte(ByteBuffer buffer, int index) {
            return (short) (buffer.get(index) & 0xFF);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173

    方法演示1

    package cn.itcast.netty.c1;
    
    import java.nio.ByteBuffer;
    import static  cn.itcast.netty.c1.ByteBufferUtil.debugAll;
    
    public class TestByteBufferReadWrite {
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(10);
            buffer.put((byte)0x61); //'a'
            debugAll(buffer);
    
            buffer.put(new byte[]{0x62,0x63,0x64}); //b c d
    
            debugAll(buffer);
    
            buffer.flip();
            System.out.println(buffer.get());
            debugAll(buffer);
            buffer.compact();
            debugAll(buffer);
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    System.out.println(ByteBuffer.allocate(16).getClass());  
    System.out.println(ByteBuffer.allocateDirect(16).getClass());
    class java.nio.HeapByteBuffer  //堆内存
    class java.nio.DirectByteBuffer //系统内存
    
    • 1
    • 2
    • 3
    • 4

    向 buffer 写入数据

    有两种办法

    • 调用 channel 的 read 方法
    • 调用 buffer 自己的 put 方法
    int readBytes = channel.read(buf);
    
    • 1

    buf.put((byte)127);
    
    • 1

    从 buffer 读取数据

    同样有两种办法

    • 调用 channel 的 write 方法
    • 调用 buffer 自己的 get 方法
    int writeBytes = channel.write(buf);
    
    • 1

    byte b = buf.get();
    
    • 1

    get 方法会让 position 读指针向后走,如果想重复读取数据

    • 可以调用 rewind 方法将 position 重新置为 0
    • 或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针

    mark 和 reset

    mark 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置

    注意

    rewind 和 flip 都会清除 mark 位置

    字符串与 ByteBuffer 互转

    ByteBuffer buffer1 = StandardCharsets.UTF_8.encode("你好");
    ByteBuffer buffer2 = Charset.forName("utf-8").encode("你好");
    
    debug(buffer1);
    debug(buffer2);
    
    CharBuffer buffer3 = StandardCharsets.UTF_8.decode(buffer1);
    System.out.println(buffer3.getClass());
    System.out.println(buffer3.toString());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
     // 1. 字符串转为 ByteBuffer
            ByteBuffer buffer1 = ByteBuffer.allocate(16);
            buffer1.put("hello".getBytes());
            debugAll(buffer1);
    
            // 2. Charset
            ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
            debugAll(buffer2);
    
            // 3. wrap
            ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
            debugAll(buffer3);
    
            // 4. 转为字符串
            String str1 = StandardCharsets.UTF_8.decode(buffer2).toString();
            System.out.println(str1);
    
            buffer1.flip();
            String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();
            System.out.println(str2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    ByteBuffer buffer = ByteBuffer.allocate(10);
            buffer.put(new byte[]{'a', 'b', 'c', 'd'});
            buffer.flip();
    
            // rewind 从头开始读
            /*buffer.get(new byte[4]);
            debugAll(buffer);
            buffer.rewind();
            System.out.println((char)buffer.get());*/
    
            // mark & reset
            // mark 做一个标记,记录 position 位置, reset 是将 position 重置到 mark 的位置
            /*System.out.println((char) buffer.get());
            System.out.println((char) buffer.get());
            buffer.mark(); // 加标记,索引2 的位置
            System.out.println((char) buffer.get());
            System.out.println((char) buffer.get());
            buffer.reset(); // 将 position 重置到索引 2
            System.out.println((char) buffer.get());
            System.out.println((char) buffer.get());*/
    
            // get(i) 不会改变读索引的位置
            System.out.println((char) buffer.get(3));
            debugAll(buffer);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    分批读

    try (RandomAccessFile file = new RandomAccessFile("helloword/3parts.txt", "rw")) {
        FileChannel channel = file.getChannel();
        ByteBuffer a = ByteBuffer.allocate(3);
        ByteBuffer b = ByteBuffer.allocate(3);
        ByteBuffer c = ByteBuffer.allocate(5);
        channel.read(new ByteBuffer[]{a, b, c});
        a.flip();
        b.flip();
        c.flip();
        debug(a);
        debug(b);
        debug(c);
    } catch (IOException e) {
        e.printStackTrace();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
            ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");
            ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");
    
            try (FileChannel channel = new RandomAccessFile("words2.txt", "rw").getChannel()) {
                channel.write(new ByteBuffer[]{b1, b2, b3});
            } catch (IOException e) {
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    黏包半包

    黏包将消息合并发送,产生黏包问题,半包,一次传输数据有限制,

             网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔
             但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为
                 Hello,world\n
                 I'm zhangsan\n
                 How are you?\n
             变成了下面的两个 byteBuffer (黏包,半包)
                 Hello,world\nI'm zhangsan\nHo
                 w are you?\n
             现在要求你编写程序,将错乱的数据恢复成原始的按 \n 分隔的数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
     ByteBuffer source = ByteBuffer.allocate(32);
            source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
            split(source);
            source.put("w are you?\n".getBytes());
            split(source);
    
    private static void split(ByteBuffer source) {
            source.flip();
            for (int i = 0; i < source.limit(); i++) {
                // 找到一条完整消息
                if (source.get(i) == '\n') {
                    int length = i + 1 - source.position();
                    // 把这条完整消息存入新的 ByteBuffer
                    ByteBuffer target = ByteBuffer.allocate(length);
                    // 从 source 读,向 target 写
                    for (int j = 0; j < length; j++) {
                        target.put(source.get());
                    }
                    debugAll(target);
                }
            }
            source.compact();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    FileChannel 工作模式

    FileChannel 只能工作在阻塞模式下

    获取

    不能直接打开 FileChannel,必须通过 FileInputStream、FileOutputStream 或者 RandomAccessFile 来获取 FileChannel,它们都有 getChannel 方法

    • 通过 FileInputStream 获取的 channel 只能读
    • 通过 FileOutputStream 获取的 channel 只能写
    • 通过 RandomAccessFile 是否能读写根据构造 RandomAccessFile 时的读写模式决定

    读取

    会从 channel 读取数据填充 ByteBuffer,返回值表示读到了多少字节,-1 表示到达了文件的末尾

    int readBytes = channel.read(buffer);
    
    • 1

    写入

    写入的正确姿势如下, SocketChannel

    ByteBuffer buffer = ...;
    buffer.put(...); // 存入数据
    buffer.flip();   // 切换读模式
    
    while(buffer.hasRemaining()) {
        channel.write(buffer);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在 while 中调用 channel.write 是因为 write 方法并不能保证一次将 buffer 中的内容全部写入 channel

    关闭

    channel 必须关闭,不过调用了 FileInputStream、FileOutputStream 或者 RandomAccessFile 的 close 方法会间接地调用 channel 的 close 方法

    位置

    获取当前位置
    
    ```java
    long pos = channel.position();
    
    • 1
    • 2
    • 3
    • 4

    设置当前位置

    long newPos = ...;
    channel.position(newPos);
    
    • 1
    • 2

    设置当前位置时,如果设置为文件的末尾

    • 这时读取会返回 -1
    • 这时写入,会追加内容,但要注意如果 position 超过了文件末尾,再写入时在新内容和原末尾之间会有空洞(00)

    强制写入

    操作系统出于性能的考虑,会将数据缓存,不是立刻写入磁盘。可以调用 force(true) 方法将文件内容和元数据(文件的权限等信息)立刻写入磁盘

    两个 Channel 传输数据

    String FROM = "helloword/data.txt";
    String TO = "helloword/to.txt";
    long start = System.nanoTime();
    try (FileChannel from = new FileInputStream(FROM).getChannel();
         FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
        from.transferTo(0, from.size(), to);
    } catch (IOException e) {
        e.printStackTrace();
    }
    long end = System.nanoTime();
    System.out.println("transferTo 用时:" + (end - start) / 1000_000.0);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    大文件拷贝

    public class TestFileChannelTransferTo {
        public static void main(String[] args) {
            try (
                    FileChannel from = new FileInputStream("data.txt").getChannel();
                    FileChannel to = new FileOutputStream("to.txt").getChannel();
            ) {
                // 效率高,底层会利用操作系统的零拷贝进行优化
                long size = from.size();
                // left 变量代表还剩余多少字节
                for (long left = size; left > 0; ) {
                    System.out.println("position:" + (size - left) + " left:" + left);
                    left -= from.transferTo((size - left), left, to);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    PATHAPI

    检查文件是否存在

    Path path = Paths.get("helloword/data.txt");
    System.out.println(Files.exists(path));
    
    • 1
    • 2

    创建一级目录

    Path path = Paths.get("helloword/d1");
    Files.createDirectory(path);
    
    • 1
    • 2
    • 如果目录已存在,会抛异常 FileAlreadyExistsException
    • 不能一次创建多级目录,否则会抛异常 NoSuchFileException

    创建多级目录用

    Path path = Paths.get("helloword/d1/d2");
    Files.createDirectories(path);
    
    • 1
    • 2

    拷贝文件

    Path source = Paths.get("helloword/data.txt");
    Path target = Paths.get("helloword/target.txt");
    
    Files.copy(source, target);
    
    • 1
    • 2
    • 3
    • 4
    • 如果文件已存在,会抛异常 FileAlreadyExistsException

    如果希望用 source 覆盖掉 target,需要用 StandardCopyOption 来控制

    Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
    
    • 1

    移动文件

    Path source = Paths.get("helloword/data.txt");
    Path target = Paths.get("helloword/data.txt");
    
    Files.move(source, target, StandardCopyOption.ATOMIC_MOVE);
    
    • 1
    • 2
    • 3
    • 4
    • StandardCopyOption.ATOMIC_MOVE 保证文件移动的原子性

    删除文件

    Path target = Paths.get("helloword/target.txt");
    
    Files.delete(target);
    
    • 1
    • 2
    • 3
    • 如果文件不存在,会抛异常 NoSuchFileException

    删除目录

    Path target = Paths.get("helloword/d1");
    
    Files.delete(target);
    
    • 1
    • 2
    • 3
    • 如果目录还有内容,会抛异常 DirectoryNotEmptyException

    遍历目录文件

    public static void main(String[] args) throws IOException {
        Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91");
        AtomicInteger dirCount = new AtomicInteger();
        AtomicInteger fileCount = new AtomicInteger();
        Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) 
                throws IOException {
                System.out.println(dir);
                dirCount.incrementAndGet();
                return super.preVisitDirectory(dir, attrs);
            }
    
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
                throws IOException {
                System.out.println(file);
                fileCount.incrementAndGet();
                return super.visitFile(file, attrs);
            }
        });
        System.out.println(dirCount); // 133
        System.out.println(fileCount); // 1479
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    删除多级目录

    Path path = Paths.get("d:\\a");
    Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
            throws IOException {
            Files.delete(file);
            return super.visitFile(file, attrs);
        }
    
        @Override
        public FileVisitResult postVisitDirectory(Path dir, IOException exc) 
            throws IOException {
            Files.delete(dir);
            return super.postVisitDirectory(dir, exc);
        }
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    统计jar结尾的文件数

    Path path = Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91");
    AtomicInteger fileCount = new AtomicInteger();
    Files.walkFileTree(path, new SimpleFileVisitor<Path>(){
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 
            throws IOException {
            if (file.toFile().getName().endsWith(".jar")) {
                fileCount.incrementAndGet();
            }
            return super.visitFile(file, attrs);
        }
    });
    System.out.println(fileCount); // 724
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    拷贝多级目录

    long start = System.currentTimeMillis();
    String source = "D:\\Snipaste-1.16.2-x64";
    String target = "D:\\Snipaste-1.16.2-x64aaa";
    
    Files.walk(Paths.get(source)).forEach(path -> {
        try {
            String targetName = path.toString().replace(source, target);
            // 是目录
            if (Files.isDirectory(path)) {
                Files.createDirectory(Paths.get(targetName));
            }
            // 是普通文件
            else if (Files.isRegularFile(path)) {
                Files.copy(path, Paths.get(targetName));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
    long end = System.currentTimeMillis();
    System.out.println(end - start);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    网络编程

    阻塞调试
    • 阻塞模式下,相关方法都会导致线程暂停
      • ServerSocketChannel.accept 会在没有连接建立时让线程暂停
      • SocketChannel.read 会在没有数据可读时让线程暂停
      • 阻塞的表现其实就是线程暂停了,暂停期间不会占用 cpu,但线程相当于闲置
    • 单线程下,阻塞方法之间相互影响,几乎不能正常工作,需要多线程支持
    • 但多线程下,有新的问题,体现在以下方面
      • 32 位 jvm 一个线程 320k,64 位 jvm 一个线程 1024k,如果连接数过多,必然导致 OOM,并且线程太多,反而会因为频繁上下文切换导致性能降低
      • 可以采用线程池技术来减少线程数和线程上下文切换,但治标不治本,如果有很多连接建立,但长时间 inactive,会阻塞线程池中所有线程,因此不适合长连接,只适合短连接
    package cn.itcast.netty.c5;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.ArrayList;
    import java.util.List;
    import lombok.extern.slf4j.Slf4j;
    import static cn.itcast.netty.c1.ByteBufferUtil.debugRead;
    
    @Slf4j
    public class Server {
        public static void main(String[] args) throws IOException {
            //使用nio理解阻塞模式
    
    
    
            ByteBuffer buffer = ByteBuffer.allocate(16);
    
            //1.创建服务器
            ServerSocketChannel ssc = ServerSocketChannel.open();
    
            //2.绑定监听端口
            ssc.bind(new InetSocketAddress (8080));
    
    
            //3.连接集合
            List<SocketChannel> channels =new ArrayList<>();
    
            while (true) {
                //4.服务器.accept 建立客户端连接,SocketChannel 用于与客户端通信
                log.debug("connetcting...");
                SocketChannel sc = ssc.accept();
                log.debug("connetcted...{}",sc);
                channels.add(sc);
                for (SocketChannel channel:channels){
                    //5.接受客户端发送的数据
                    //切换至只读模式
                    log.debug("before read ...{}",channel);
                    channel.read(buffer); //阻塞方法,线程停止运行
                    buffer.flip();
                    debugRead(buffer);
                    //切换为写模式
                    buffer.clear();
    
                    log.debug("after read ...");
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    package cn.itcast.netty.c5;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.channels.SocketChannel;
    
    public class Client {
        public static void main(String[] args) throws IOException{
            SocketChannel sc = SocketChannel.open();
            sc.connect(new InetSocketAddress("localhost", 8080));
            System.out.println("waiting...");
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    非阻塞调试
    • 非阻塞模式下,相关方法都会不会让线程暂停
      • 在 ServerSocketChannel.accept 在没有连接建立时,会返回 null,继续运行
      • SocketChannel.read 在没有数据可读时,会返回 0,但线程不必阻塞,可以去执行其它 SocketChannel 的 read 或是去执行 ServerSocketChannel.accept
      • 写数据时,线程只是等待数据写入 Channel 即可,无需等 Channel 通过网络把数据发送出去
    • 但非阻塞模式下,即使没有连接建立,和可读数据,线程仍然在不断运行,白白浪费了 cpu
    • 数据复制过程中,线程实际还是阻塞的(AIO 改进的地方)
    package cn.itcast.netty.c5;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.ArrayList;
    import java.util.List;
    
    import lombok.extern.slf4j.Slf4j;
    
    import static cn.itcast.netty.c1.ByteBufferUtil.debugRead;
    
    @Slf4j
    public class Server {
        public static void main(String[] args) throws IOException {
    
            ByteBuffer buffer = ByteBuffer.allocate(16);
    
            //1.创建服务器
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false); //非堵塞
    
            //2.绑定监听端口
            ssc.bind(new InetSocketAddress(8089));
    
    
            //3.连接集合
            List<SocketChannel> channels = new ArrayList<>();
    
            while (true) {
                //4.服务器.accept 建立客户端连接,SocketChannel 用于与客户端通信
                log.debug("connetcting...");
                SocketChannel sc = ssc.accept(); //非堵塞,线程还会继续运行,如果没有连接建立,sc为null
                if (sc != null) {
                    log.debug("connetcted...{}", sc);
                    sc.configureBlocking(false);
                    channels.add(sc);
    
                }
    
                for (SocketChannel channel : channels) {
                    //5.接受客户端发送的数据
                    //切换至只读模式
                    log.debug("before read ...{}", channel);
                    int read = channel.read(buffer);//非阻塞方法,线程继续运行,如果没有读取到数据 read返回 0
                    if (read > 0) {
                        buffer.flip();
                        debugRead(buffer);
                        //切换为写模式
                        buffer.clear();
    
                        log.debug("after read ...");
                    }
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    selector(多路复用)

    单线程可以配合 Selector 完成对多个 Channel 可读写事件的监控,这称之为多路复用

    • 多路复用仅针对网络 IO、普通文件 IO 没法利用多路复用
    • 如果不用 Selector 的非阻塞模式,线程大部分时间都在做无用功,而 Selector 能够保证
      • 有可连接事件时才去连接
      • 有可读事件才去读取
      • 有可写事件才去写入
        • 限于网络传输能力,Channel 未必时时可写,一旦 Channel 可写,会触发 Selector 的可写事件

    好处

    • 一个线程配合 selector 就可以监控多个 channel 的事件,事件发生线程才去处理。避免非阻塞模式下所做无用功
    • 让这个线程能够被充分利用
    • 节约了线程的数量
    • 减少了线程上下文切换

    总结:本质上包一层,兼顾阻塞和非阻塞优缺点。

    package cn.itcast.netty.c6;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    import static cn.itcast.netty.c1.ByteBufferUtil.debugRead;
    
    @Slf4j
    public class Server {
        public static void main(String[] args) throws IOException {
    
            //1.创建selector,管理多个chanel
            Selector selector = Selector.open();
    
            ServerSocketChannel ssc = ServerSocketChannel.open(); //创建服务器
            ssc.configureBlocking(false); //非堵塞
            ssc.bind(new InetSocketAddress(8080)); //绑定监听端口
    
            //2.创建selector 和 ServerSocketChannel、SocketChannel的联系 (注册)
            //SelectionKey 事件发生后,通过它,得知事件类型(1.accept 2.connect 3.read 4.write)和事件被分配到那个Channel
            SelectionKey sscKey = ssc.register(selector, 0, null);
            log.debug("\n\nregister key:{}\n\n",sscKey);
            //key 关注accept的事件
            sscKey.interestOps(SelectionKey.OP_ACCEPT);
    
    
            while(true) {
                //3.调用select,还是阻塞的,有任务再分配给线程
                selector.select();
                //4.处理事件,selectedKeys会包含所有发生的事情
                //使用迭代器可以删除
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey key=iterator.next();
                    log.debug("\n\n key:{}\n\n",key);
                    //拿到事件channel
                    ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                    SocketChannel sc = channel.accept();
                    log.debug("\n\n{}\n\n",sc);
                }
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    cancel和remove

    package cn.itcast.netty.c6;
    
    import com.google.common.collect.Iterators;
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    import static cn.itcast.netty.c1.ByteBufferUtil.debugRead;
    
    @Slf4j
    public class Server {
        public static void main(String[] args) throws IOException {
    
            //1.创建selector,管理多个chanel
            Selector selector = Selector.open();
    
            ServerSocketChannel ssc = ServerSocketChannel.open(); //创建服务器
            ssc.configureBlocking(false); //非堵塞
            ssc.bind(new InetSocketAddress(22000)); //绑定监听端口
    
            //2.创建selector 和 ServerSocketChannel、SocketChannel的联系 (注册)
            //SelectionKey 事件发生后,通过它,得知事件类型(1.accept 2.connect 3.read 4.write)和事件被分配到那个Channel
            SelectionKey sscKey = ssc.register(selector, 0, null);
            sscKey.interestOps(SelectionKey.OP_ACCEPT);
            log.debug("register key:{}\n\n", sscKey);
            //key 关注accept的事件
    
    
            while (true) {
                //3.调用select查看是否有事件,要求事件发生要么取消,要么处理
                // 没有事情是阻塞的,有任务再分配给线程
                // 有事情会像非阻塞一样一直循环。
                selector.select();
                //4.处理事件,selectedKeys会包含所有发生的事情,迭代器可以删除
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                List list = Lists.newArrayList(iterator);
                int count = list.size();
    //            log.debug("Iterators集合大小:{}", count);
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    log.debug("key:{}\n\n", key);
                    if (key.isAcceptable()) {
                        ServerSocketChannel chanel = (ServerSocketChannel) key.channel();
                        SocketChannel sc = chanel.accept();
                        sc.configureBlocking(false);
                        SelectionKey scKey = sc.register(selector, 0, null);
                        scKey.interestOps(SelectionKey.OP_READ);
                        log.debug("sc:{}\n\n", sc);
    
                    } else if (key.isReadable()) {
                        try {
                            SocketChannel channel = (SocketChannel) key.channel();
                            ByteBuffer buffer = ByteBuffer.allocate(16);
                            int read = channel.read(buffer);
                            if(read ==-1){
                                key.cancel();
                            }
                            buffer.flip();
                            debugRead(buffer);
                        } catch (IOException e) {
                            e.printStackTrace();
                            key.cancel();
                        }
                    }
                }
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    处理消息的边界
    • 1.固定消息长度,数据包大小一样,服务器按预定长度读取,缺点是浪费带宽
    • 2.按分隔符拆分,缺点是效率低
    • 3.TLV 格式,即 Type 类型、Length 长度、Value 数据,类型和长度已知的情况下,就可以方便获取消息大小,分配合适的 buffer,缺点是 buffer 需要提前分配,如果内容过大,则影响 server 吞吐量
      • Http 1.1 是 TLV 格式
      • Http 2.0 是 LTV 格式

    动态分配buffer大小,利用select 循环不堵塞 二次分配buffer

    • 每个 channel 都需要记录可能被切分的消息,因为 ByteBuffer 不能被多个 channel 共同使用,因此需要为每个 channel 维护一个独立的 ByteBuffer
    • ByteBuffer 不能太大,比如一个 ByteBuffer 1Mb 的话,要支持百万连接就要 1Tb 内存,因此需要设计大小可变的 ByteBuffer
      • 一种思路是首先分配一个较小的 buffer,例如 4k,如果发现数据不够,再分配 8k 的 buffer,将 4k buffer 内容拷贝至 8k buffer,优点是消息连续容易处理,缺点是数据拷贝耗费性能,参考实现 http://tutorials.jenkov.com/java-performance/resizable-array.html
      • 另一种思路是用多个数组组成 buffer,一个数组不够,把多出来的内容写入新的数组,与前面的区别是消息存储不连续解析复杂,优点是避免了拷贝引起的性能损耗 ,即链表
    package cn.itcast.netty.c6;
    
    import com.google.common.collect.Iterators;
    import com.google.common.collect.Lists;
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.nio.charset.Charset;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    import static cn.itcast.netty.c6.ByteBufferUtil.*;
    
    @Slf4j
    public class Server {
    
        private static void split(ByteBuffer source) {
            source.flip();
            for (int i = 0; i < source.limit(); i++) {
                // 找到一条完整消息
                if (source.get(i) == '\n') {
                    int length = i + 1 - source.position();
                    // 把这条完整消息存入新的 ByteBuffer
                    ByteBuffer target = ByteBuffer.allocate(length);
                    // 从 source 读,向 target 写
                    for (int j = 0; j < length; j++) {
                        target.put(source.get());
                    }
                    debugAll(target);
                }
            }
            source.compact();
        }
    
    
        public static void main(String[] args) throws IOException {
    
            //1.创建selector,管理多个chanel
            Selector selector = Selector.open();
    
            ServerSocketChannel ssc = ServerSocketChannel.open(); //创建服务器
            ssc.configureBlocking(false); //非堵塞
            ssc.bind(new InetSocketAddress(8099)); //绑定监听端口
    
            //2.创建selector 和 ServerSocketChannel、SocketChannel的联系 (注册)
            //SelectionKey 事件发生后,通过它,得知事件类型(1.accept 2.connect 3.read 4.write)和事件被分配到那个Channel
            SelectionKey sscKey = ssc.register(selector, 0, null);
            sscKey.interestOps(SelectionKey.OP_ACCEPT);
            log.debug("register key:{}\n\n", sscKey);
            //key 关注accept的事件
    
    
            while (true) {
                //3.调用select查看是否有事件,要求事件发生要么取消,要么处理
                // 没有事情是阻塞的,有任务再分配给线程
                // 有事情会像非阻塞一样一直循环。
                selector.select();
                //4.处理事件,selectedKeys会包含所有发生的事情,迭代器可以删除
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    log.debug("key:{}\n\n", key);
    
    
                    if (key.isAcceptable()) {
                        ServerSocketChannel chanel = (ServerSocketChannel) key.channel();
                        SocketChannel sc = chanel.accept();
                        sc.configureBlocking(false);
                        ByteBuffer buffer = ByteBuffer.allocate(16);
                        // attachment 附件
                        SelectionKey scKey = sc.register(selector, 0, buffer);
                        scKey.interestOps(SelectionKey.OP_READ);
                        log.debug("sc:{}\n\n", sc);
                        log.debug("scKey:{}\n\n", scKey);
                    } else if (key.isReadable()) {
                        try {
                            SocketChannel channel = (SocketChannel) key.channel();
                            // 获取buffer
                            ByteBuffer buffer = (ByteBuffer) key.attachment();
                            int read = channel.read(buffer);
                            if (read == -1) {
                                key.cancel();
                            } else {
                                split(buffer);
                                if (buffer.position()==buffer.limit()){
                                    ByteBuffer newbuffer = ByteBuffer.allocate(buffer.capacity()*2);
                                    buffer.flip();
                                    newbuffer.put(buffer);
                                    key.attach(newbuffer);
                                }
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                            key.cancel();
                        }
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    处理可写事件
    package cn.itcast.netty.c6;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.nio.charset.Charset;
    import java.util.Iterator;
    
    public class WriteServer {
        public static void main(String[] args) throws IOException {
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
    
            Selector selector = Selector.open();
            ssc.register(selector, SelectionKey.OP_ACCEPT);
            ssc.bind(new InetSocketAddress(8099));
    
            while (true) {
                selector.select();
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
    
                while (iterator.hasNext()) {
    
                    SelectionKey key = iterator.next();
                    iterator.remove();
    
                    if (key.isAcceptable()) {
                        SocketChannel sc = ssc.accept();
                        sc.configureBlocking(false);
                        SelectionKey sckey = sc.register(selector, 0, null);
                        sckey.interestOps(SelectionKey.OP_READ);
    //                    1.需要向客户端发现大量数据
                        StringBuilder sb = new StringBuilder();
                        for (int i = 0; i < 5000000; i++) {
                            sb.append("a");
                        }
                        ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());
                        // 2.返回值代表实际写入的字节数
                        int write = sc.write(buffer);
                        System.out.println(write);
                        // 3.是否还有剩余字节
                        if (buffer.hasRemaining()) {
                            //4. 关注可写事件    拿到原来关注的事件 1是读 4是写 5是关注读写
                            sckey.interestOps(sckey.interestOps() + SelectionKey.OP_WRITE);
                            //5. 把未写完的数据挂到scKey上
                            sckey.attach(buffer);
                        }
    
                    } else if (key.isWritable()) { //下一次可写了,不阻塞
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        SocketChannel sc = (SocketChannel) key.channel();
                        int write = sc.write(buffer);
                        System.out.println(write);
    
                        // 6.清理操作
                        if (!buffer.hasRemaining()) {
                            key.attach(null);
                            //不需要关注可写操作
                            key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);
                        }
                    }
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    package cn.itcast.netty.c6;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.SocketChannel;
    
    public class WriteClient {
        public static void main(String[] args) throws IOException {
            SocketChannel sc =SocketChannel.open();
            sc.connect(new InetSocketAddress("localhost",8099));
    
            // 客户端接受数据
    
            int count =0;
            while (true) {
                ByteBuffer buffer=ByteBuffer.allocate(1024*1024);
                count+=sc.read(buffer);
                System.out.println(count);
                buffer.clear();
            }
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    网络编程总结

    监听 Channel 事件

    可以通过下面三种方法来监听是否有事件发生,方法的返回值代表有多少 channel 发生了事件

    方法1,阻塞直到绑定事件发生

    int count = selector.select();
    
    • 1

    方法2,阻塞直到绑定事件发生,或是超时(时间单位为 ms)

    int count = selector.select(long timeout);
    
    • 1

    方法3,不会阻塞,也就是不管有没有事件,立刻返回,自己根据返回值检查是否有事件

    int count = selector.selectNow();
    
    • 1

    💡 select 何时不阻塞

    • 事件发生时
      • 客户端发起连接请求,会触发 accept 事件
      • 客户端发送数据过来,客户端正常、异常关闭时,都会触发 read 事件,另外如果发送的数据大于 buffer 缓冲区,会触发多次读取事件
      • channel 可写,会触发 write 事件
      • 在 linux 下 nio bug 发生时
    • 调用 selector.wakeup()
    • 调用 selector.close()
    • selector 所在线程 interrupt

    多线程,使用队列处理任务

    package cn.itcast.netty.c6;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.util.Iterator;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.atomic.AtomicInteger;
    
    import static cn.itcast.netty.c6.ByteBufferUtil.*;
    
    @Slf4j
    public class MultiThreadServer {
        public static void main(String[] args) throws IOException {
    
            Thread.currentThread().setName("boss");
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            Selector boss = Selector.open();
            SelectionKey bossKey = ssc.register(boss, 0, null);
            bossKey.interestOps(SelectionKey.OP_ACCEPT);
            ssc.bind(new InetSocketAddress(8088));
    
            //创建固定数量worker
            Worker worker=new Worker("worker-0");
    
            while (true) {
                boss.select();//boss.select() 在这里阻塞
                Iterator<SelectionKey> iter = boss.selectedKeys().iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    iter.remove();
                    if (key.isAcceptable()) {
                        SocketChannel sc = ssc.accept();
                        sc.configureBlocking(false);
                        log.debug("connected...{}",sc.getRemoteAddress());
                        log.debug("before register...{}",sc.getRemoteAddress());
                        worker.register(sc);
                        log.debug("after register...{}",sc.getRemoteAddress());
                    }
                }
            }
        }
    
        static class Worker implements Runnable {
            private Thread thread;
            private Selector selector;
            private String name;
            private volatile boolean start = false;
            private  ConcurrentLinkedQueue<Runnable> queue=new ConcurrentLinkedQueue<>();
    
            public Worker(String name) {
                this.name = name;
            }
    
            // 初始化线程和selector
            public void register(SocketChannel sc)throws IOException {
                if (!start) {
                    selector = Selector.open();
                    thread = new Thread(this, name);
                    thread.start();
                    start = true;
                }
                //向队列添加任务
                queue.add(()->{
                    try {
                        sc.register(selector,SelectionKey.OP_READ,null); //关联worker
                    } catch (ClosedChannelException e) {
                        e.printStackTrace();
                    }
                });
                selector.wakeup();  //就会立即返回结果,不让selector阻塞
            }
    
            @Override
            public void run() {
                System.out.println("run运行了");
                while (true) {
                    try {
                        selector.select(); //阻塞wakeup
                        Runnable task=queue.poll();
                        if(task!=null) {
                            task.run();
                        }
                        Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                        while (iter.hasNext()) {
                            SelectionKey key = iter.next();
                            iter.remove();
                            if (key.isReadable()) {
                                ByteBuffer buffer = ByteBuffer.allocate(16);
                                SocketChannel channel = (SocketChannel) key.channel();
                                log.debug("read...{}",channel.getRemoteAddress());
                                channel.read(buffer);
                                buffer.flip();
                                debugAll(buffer);
                            }
                        }
                    } catch (IOException e) {
    
                    }
    
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109

    改进

    package cn.itcast.netty.c6;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.IOException;
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.*;
    import java.util.Iterator;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.atomic.AtomicInteger;
    
    import static cn.itcast.netty.c6.ByteBufferUtil.*;
    
    @Slf4j
    public class MultiThreadServer {
        public static void main(String[] args) throws IOException {
    
            Thread.currentThread().setName("boss");
            ServerSocketChannel ssc = ServerSocketChannel.open();
            ssc.configureBlocking(false);
            Selector boss = Selector.open();
            SelectionKey bossKey = ssc.register(boss, 0, null);
            bossKey.interestOps(SelectionKey.OP_ACCEPT);
            ssc.bind(new InetSocketAddress(8088));
    
            Worker[] workers = new Worker[Runtime.getRuntime().availableProcessors()];
    
            for (int i = 0; i < workers.length; i++) {
                workers[i]=new Worker("worker-"+i);
            }
    
            AtomicInteger index=new AtomicInteger();
            while (true) {
                boss.select();//boss.select() 在这里阻塞
                Iterator<SelectionKey> iter = boss.selectedKeys().iterator();
                while (iter.hasNext()) {
                    SelectionKey key = iter.next();
                    iter.remove();
                    if (key.isAcceptable()) {
                        SocketChannel sc = ssc.accept();
                        sc.configureBlocking(false);
                        log.debug("connected...{}", sc.getRemoteAddress());
                        log.debug("before register...{}", sc.getRemoteAddress());
                        workers[index.getAndIncrement()%workers.length].register(sc);
                        log.debug("after register...{}", sc.getRemoteAddress());
                    }
                }
            }
        }
    
        static class Worker implements Runnable {
            private Thread thread;
            private Selector selector;
            private String name;
            private volatile boolean start = false;
            private ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();
    
            public Worker(String name) {
                this.name = name;
            }
    
            // 初始化线程和selector
            public void register(SocketChannel sc) throws IOException {
                if (!start) {
                    selector = Selector.open();
                    thread = new Thread(this, name);
                    thread.start();
                    start = true;
                }
                selector.wakeup();  //就会立即返回结果,不让selector阻塞
                sc.register(selector, SelectionKey.OP_READ, null);
            }
    
            @Override
            public void run() {
                System.out.println("run运行了");
                while (true) {
                    try {
                        selector.select(); //阻塞wakeup
                        Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                        while (iter.hasNext()) {
                            SelectionKey key = iter.next();
                            iter.remove();
                            if (key.isReadable()) {
                                ByteBuffer buffer = ByteBuffer.allocate(16);
                                SocketChannel channel = (SocketChannel) key.channel();
                                log.debug("read...{}", channel.getRemoteAddress());
                                channel.read(buffer);
                                buffer.flip();
                                debugAll(buffer);
                            }
                        }
                    } catch (IOException e) {
    
                    }
    
                }
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    NIO vs BIO

    stream vs channel
    • stream 不会自动缓冲数据,channel 会利用系统提供的发送缓冲区、接收缓冲区(更为底层)
    • stream 仅支持阻塞 API,channel 同时支持阻塞、非阻塞 API,网络 channel 可配合 selector 实现多路复用
    • 二者均为全双工,即读写可以同时进行
    IO 模型

    同步阻塞、同步非阻塞、同步多路复用、异步阻塞(没有此情况)、异步非阻塞

    • 同步:线程自己去获取结果(一个线程)
    • 异步:线程自己不去获取结果,而是由其它线程送结果(至少两个线程)

    当调用一次 channel.read 或 stream.read 后,会切换至操作系统内核态来完成真正数据读取,而读取又分为两个阶段,分别为:

    • 等待数据阶段

    • 复制数据阶段
      在这里插入图片描述
      阻塞 IO
      在这里插入图片描述
      非阻塞 IO
      在这里插入图片描述
      多路复用
      在这里插入图片描述

    • 信号驱动

    • 异步 IO
      在这里插入图片描述

    阻塞 IO vs 多路复用
    在这里插入图片描述
    在这里插入图片描述

    零拷贝

    传统 IO 问题

    传统的 IO 将一个文件通过 socket 写出

    File f = new File("helloword/data.txt");
    RandomAccessFile file = new RandomAccessFile(file, "r");
    
    byte[] buf = new byte[(int)f.length()];
    file.read(buf);
    
    Socket socket = ...;
    socket.getOutputStream().write(buf);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. java 本身并不具备 IO 读写能力,因此 read 方法调用后,要从 java 程序的用户态切换至内核态,去调用操作系统(Kernel)的读能力,将数据读入内核缓冲区。这期间用户线程阻塞,操作系统使用 DMA(Direct Memory Access)来实现文件读,其间也不会使用 cpu

      DMA 也可以理解为硬件单元,用来解放 cpu 完成文件 IO

    2. 内核态切换回用户态,将数据从内核缓冲区读入用户缓冲区(即 byte[] buf),这期间 cpu 会参与拷贝,无法利用 DMA

    3. 调用 write 方法,这时将数据从用户缓冲区(byte[] buf)写入 socket 缓冲区,cpu 会参与拷贝

    4. 接下来要向网卡写数据,这项能力 java 又不具备,因此又得从用户态切换至内核态,调用操作系统的写能力,使用 DMA 将 socket 缓冲区的数据写入网卡,不会使用 cpu

    可以看到中间环节较多,java 的 IO 实际不是物理设备级别的读写,而是缓存的复制,底层的真正读写是操作系统来完成的

    • 用户态与内核态的切换发生了 3 次,这个操作比较重量级
    • 数据拷贝了共 4 次

    NIO 优化

    通过 DirectByteBuf

    • ByteBuffer.allocate(10) HeapByteBuffer 使用的还是 java 内存
    • ByteBuffer.allocateDirect(10) DirectByteBuffer 使用的是操作系统内存

    大部分步骤与优化前相同,不再赘述。唯有一点:java 可以使用 DirectByteBuf 将堆外内存映射到 jvm 内存中来直接访问使用

    • 这块内存不受 jvm 垃圾回收的影响,因此内存地址固定,有助于 IO 读写
    • java 中的 DirectByteBuf 对象仅维护了此内存的虚引用,内存回收分成两步
      • DirectByteBuf 对象被垃圾回收,将虚引用加入引用队列
      • 通过专门线程访问引用队列,根据虚引用释放堆外内存
    • 减少了一次数据拷贝,用户态与内核态的切换次数没有减少
      在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    逐渐优化,最后直接从磁盘到网卡

    Aio
    package cn.itcast.netty.c6;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.io.IOException;
    import java.nio.ByteBuffer;
    import java.nio.channels.AsynchronousFileChannel;
    import java.nio.channels.CompletionHandler;
    import java.nio.file.Paths;
    import java.nio.file.StandardOpenOption;
    import static cn.itcast.netty.c6.ByteBufferUtil.*;
    
    
    @Slf4j
    public class AioFileChannel {
        public static void main(String[] args) throws IOException {
            try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("D:\\Code\\Netty\\src\\data.txt"), StandardOpenOption.READ)) {
                // 参数1 ByteBuffer
                // 参数2 读取的起始位置
                // 参数3 附件
                // 参数4 回调对象
                ByteBuffer buffer = ByteBuffer.allocate(16);
                log.debug("read begin");
                channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    
                    @Override  //读取完毕
                    public void completed(Integer result, ByteBuffer attachment) {
                        log.debug("read completed...");
                        attachment.flip();
                        debugAll(attachment);
                    }
    
                    @Override //读取失败
                    public void failed(Throwable exc, ByteBuffer attachment) {
                    }
                });
                log.debug("read end...");
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.in.read();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    如果不加System.in.read(),主线程结束,回调的线程也跟着结束。

    10:05:36.482 [main] DEBUG cn.itcast.netty.c6.AioFileChannel - read begin
    10:05:36.485 [main] DEBUG cn.itcast.netty.c6.AioFileChannel - read end...
    10:05:36.485 [Thread-16] DEBUG cn.itcast.netty.c6.AioFileChannel - read completed...
    10:05:36.489 [Thread-16] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework
    +--------+-------------------- all ------------------------+----------------+
    position: [0], limit: [12]
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 31 32 33 34 35 36 37 38 39 61 62 63 00 00 00 00 |123456789abc....|
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Netty入门

    package cn.itcast.netty.hello;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.string.StringDecoder;
    
    public class HelloServer {
        public static void main(String[] args) {
            //1.服务器启动,负责组装netty组件,启动服务器
            new ServerBootstrap()
                    // 1.添加BossEventLoop,WorkerEventLoop (selector、thread)
                    .group(new NioEventLoopGroup())
                    // 2.OIO BIO
                    .channel(NioServerSocketChannel.class)
                    // 3.boos负责连接处理 worker(child) ,负责处理读写,决定了worker(child)能执行那些操作(handler)
                    .childHandler(
                            //4.channel 代表和客户端进行数据读写的通道 Initializer初始化,负责添加别的handle了
                            new ChannelInitializer<NioSocketChannel>() {
                                @Override
                                protected void initChannel(NioSocketChannel ch) {
                                    // 6.添加具体的handler
                                    ch.pipeline().addLast(new StringDecoder());  //将ByteBuf转换为字符串
                                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                        @Override
                                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                            //打印上一步转换好的字符串
                                            System.out.println(msg);
                                        } // 自定义handler
                                    });
                                }
                            })
                    .bind(8099);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    package cn.itcast.netty.hello;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoop;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.sctp.nio.NioSctpChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.string.StringEncoder;
    
    import java.net.InetSocketAddress;
    
    public class HelloClient {
        public static void main(String[] args) throws InterruptedException {
            //1.启动类
            new Bootstrap()
                    //2.添加EventLoop
                    .group(new NioEventLoopGroup())
                    //3.选择客户端 channel 实现
                    .channel(NioSocketChannel.class)
                    //4. 添加处理器
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override //在连接建立后被调用
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringEncoder());
                        }
                    })
                    .connect(new InetSocketAddress("localhost",8099))
                    .sync()
                    .channel()
                    .writeAndFlush("hello,world");
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    Eventloop

    EventLoop 本质是一个单线程执行器(同时维护了一个 Selector),里面有 run 方法处理 Channel 上源源不断的 io 事件。

    它的继承关系比较复杂

    • 一条线是继承自 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法
    • 另一条线是继承自 netty 自己的 OrderedEventExecutor,
      • 提供了 boolean inEventLoop(Thread thread) 方法判断一个线程是否属于此 EventLoop
      • 提供了 parent 方法来看看自己属于哪个 EventLoopGroup

    事件循环组

    EventLoopGroup 是一组 EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)

    • 继承自 netty 自己的 EventExecutorGroup
      • 实现了 Iterable 接口提供遍历 EventLoop 的能力
      • 另有 next 方法获取集合中下一个 EventLoop

    普通任务和定时任务

    package cn.itcast.netty.eventloop;
    
    import io.netty.channel.DefaultEventLoopGroup;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.util.NettyRuntime;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.TimeUnit;
    
    @Slf4j
    public class TestEventLoop {
        public static void main(String[] args) {
            //1.创建事件循环组
            EventLoopGroup group = new NioEventLoopGroup(2); //io,普通任务,定时任务
    //        EventLoopGroup group =new DefaultEventLoopGroup();//普通任务,定时任务
            //2.获取下一个事件循环对象
            System.out.println(group.next());
            System.out.println(group.next());
            System.out.println(group.next());
    
            //3.提交普通任务
    //        group.next().submit(() -> {
    //
    //            try {
    //                Thread.sleep(1000);
    //
    //            }catch (Exception e){
    //                e.printStackTrace();
    //            }
    //            log.debug("ok");
    //        });
    
    
            //4.定时任务
            group.next().scheduleAtFixedRate(() -> {
                log.debug("ok");
            },0,1, TimeUnit.SECONDS);//一秒打印一次
            log.debug("main");
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    EventLoopGroup 细分处理

    package cn.itcast.netty.eventloop;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import lombok.extern.slf4j.Slf4j;
    
    import java.nio.charset.Charset;
    
    @Slf4j
    public class EventLoopServer {
        public static void main(String[] args) {
            //细分2:创建一个独立的EventGroup
            EventLoopGroup group =new DefaultEventLoopGroup();
            new ServerBootstrap()
                    // 细分1:boss 和 worker
                    .group(new NioEventLoopGroup(),new NioEventLoopGroup(2))
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast("handler1",new ChannelInboundHandlerAdapter() {
    
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    ByteBuf buf = (ByteBuf) msg;
                                    log.debug(buf.toString(Charset.defaultCharset()));
                                    ctx.fireChannelRead(msg);//将消息传递给下一个handler
                                }
                            }).addLast(group,"handler2",new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    ByteBuf buf = (ByteBuf) msg;
                                    log.debug(buf.toString(Charset.defaultCharset()));
                                }
                            });
                        }
                    })
            .bind(8099);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    package cn.itcast.netty.eventloop;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.string.StringEncoder;
    
    import java.net.InetSocketAddress;
    
    public class EventLoopClient {
        public static void main(String[] args) throws InterruptedException {
            //1.启动类
            Channel channel = new Bootstrap()
                    //2.添加EventLoop
                    .group(new NioEventLoopGroup())
                    //3.选择客户端 channel 实现
                    .channel(NioSocketChannel.class)
                    //4. 添加处理器
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override //在连接建立后被调用
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringEncoder());
                        }
                    })
                    .connect(new InetSocketAddress("localhost", 8099))
                    .sync()
                    .channel();
    //                .writeAndFlush("hello,world");
            System.out.println(channel);
            System.out.println("");
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    Channel

    channel 的主要作用

    • close() 可以用来关闭 channel
    • closeFuture() 用来处理 channel 的关闭
      • sync 方法作用是同步等待 channel 关闭
      • 而 addListener 方法是异步等待 channel 关闭
    • pipeline() 方法添加处理器
    • write() 方法将数据写入
    • writeAndFlush() 方法将数据写入并刷出
    package cn.itcast.netty.eventloop;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.string.StringEncoder;
    import lombok.extern.slf4j.Slf4j;
    
    import java.net.InetSocketAddress;
    
    @Slf4j
    public class ChannelClient {
        public static void main(String[] args) throws InterruptedException {
            //带有Future,Promise的类型都是异步方法配套使用,用来处理结果
            ChannelFuture channelFuture = new Bootstrap()
                    //2.添加EventLoop
                    .group(new NioEventLoopGroup())
                    //3.选择客户端 channel 实现
                    .channel(NioSocketChannel.class)
                    //4. 添加处理器
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override //在连接建立后被调用
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringEncoder());
                        }
                    })
                    .connect(new InetSocketAddress("localhost", 8099));
    //        方法一:channelFuture.sync(); 阻塞住当前线程,直到nio线程连接建立完毕 无阻塞向下执行获取channel
    //        Channel channel = channelFuture.channel();
    //        log.debug("{}", channel);
    //        channel.writeAndFlush("hello,world");
    
    
    //        方法二:使用addListener(回调对象) 方法异步处理结果
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                //在nio 线程连接建立好之后,会调用operationComplete
                public void operationComplete(ChannelFuture future) throws Exception {
                    Channel channel = channelFuture.channel();
                    log.debug("{}", channel);
                    channel.writeAndFlush("hello,world");
                }
            });
    
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    channel 关闭执行操作

    package cn.itcast.netty.eventloop;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.string.StringEncoder;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import lombok.extern.slf4j.Slf4j;
    
    import java.net.InetSocketAddress;
    import java.util.Scanner;
    
    
    @Slf4j
    public class CloseFutureClient {
        public static void main(String[] args) throws InterruptedException {
            NioEventLoopGroup group = new NioEventLoopGroup();
            ChannelFuture channelFuture = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                            ch.pipeline().addLast(new StringEncoder());
                        }
                    })
                    .connect(new InetSocketAddress("localhost", 8099));
            Channel channel = channelFuture.sync().channel();
            log.debug("{}", channel);
            new Thread(() -> {
                Scanner scanner = new Scanner(System.in);
                while (true) {
                    String line = scanner.nextLine();
                    if ("q".equals(line)) {
                        channel.close();
    //                    log.debug("处理关闭之后的操作");这个代码不对,因为线程是异步的,
                        break;
                    }
                    channel.writeAndFlush(line);
                }
            }, "input").start();
    
    //        log.debug("处理关闭之后的操作"); 这个代码不对,因为线程是异步的,先结束了
    
    //        方案一:
    //        ChannelFuture closeFuture = channel.closeFuture();
    //        System.out.println("waiting close...");
    //        closeFuture.sync();
    //        log.debug("处理关闭之后的操作");
    
            //方案二
            ChannelFuture closeFuture = channel.closeFuture();
            closeFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    log.debug("处理关闭之后的操作");
                    group.shutdownGracefully();
                }
            });
        }
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    waiting close...
    q
    16:37:44.411 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xf275ba6b, L:/127.0.0.1:1820 - R:localhost/127.0.0.1:8099] CLOSE
    16:37:44.411 [main] DEBUG cn.itcast.netty.eventloop.CloseFutureClient - 处理关闭之后的操作
    16:37:44.411 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xf275ba6b, L:/127.0.0.1:1820 ! R:localhost/127.0.0.1:8099] INACTIVE
    16:37:44.411 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xf275ba6b, L:/127.0.0.1:1820 ! R:localhost/127.0.0.1:8099] UNREGISTERED
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Future & Promise

    在异步处理时,经常用到这两个接口

    首先要说明 netty 中的 Future 与 jdk 中的 Future 同名,但是是两个接口,netty 的 Future 继承自 jdk 的 Future,而 Promise 又对 netty Future 进行了扩展

    jdk Future -> netty Future -> Promise

    • jdk Future 只能同步等待任务结束(或成功、或失败)才能得到结果
    • netty Future 可以同步等待任务结束得到结果,也可以异步方式得到结果,但都是要等任务结束
    • netty Promise 不仅有 netty Future 的功能,而且脱离了任务独立存在,只作为两个线程间传递结果的容器
    功能/名称jdk Futurenetty FuturePromise
    cancel取消任务--
    isCanceled任务是否取消--
    isDone任务是否完成,不能区分成功失败--
    get获取任务结果,阻塞等待--
    getNow-获取任务结果,非阻塞,还未产生结果时返回 null-
    await-等待任务结束,如果任务失败,不会抛异常,而是通过 isSuccess 判断-
    sync-等待任务结束,如果任务失败,抛出异常-
    isSuccess-判断任务是否成功-
    cause-获取失败信息,非阻塞,如果没有失败,返回null-
    addLinstener-添加回调,异步接收结果-
    setSuccess--设置成功结果
    setFailure--设置失败结果

    get 获取任务结果,阻塞等待

    package cn.itcast.netty.eventloop;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.*;
    
    @Slf4j
    public class TestJdkFuture {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            //1.线程池
            ExecutorService service = Executors.newFixedThreadPool(2);
    
            //2.提交任务
            Future<Integer> future = service.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    log.debug("执行计算");
                    Thread.sleep(10000);
                    return 50;
                }
            });
    
            //3.主线程通过future 来获取结果 阻塞的
            future.get();
            log.debug("等待结果");
            log.debug("结果是:{}",future.get());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    06:12:55.440 [pool-1-thread-1] DEBUG cn.itcast.netty.eventloop.TestJdkFuture - 执行计算
    06:13:05.451 [main] DEBUG cn.itcast.netty.eventloop.TestJdkFuture - 等待结果
    06:13:05.451 [main] DEBUG cn.itcast.netty.eventloop.TestJdkFuture - 结果是:50
    
    • 1
    • 2
    • 3
    package cn.itcast.netty.eventloop;
    
    import io.netty.channel.EventLoop;
    import io.netty.channel.nio.NioEventLoop;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.util.concurrent.Future;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    
    @Slf4j
    public class TestNettyFuture {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            NioEventLoopGroup group = new NioEventLoopGroup();
            EventLoop eventLoop = group.next();
    
            Future<Integer> future = eventLoop.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    log.debug("执行计算");
                    Thread.sleep(10000);
                    return 70;
                }
            });
            //获取结果 非阻塞的
            log.debug("等待结果");
            log.debug("结果是:{}",future.get());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    06:22:39.838 [main] DEBUG cn.itcast.netty.eventloop.TestNettyFuture - 看看那个线程
    06:22:39.838 [main] DEBUG cn.itcast.netty.eventloop.TestNettyFuture - 等待结果
    06:22:39.838 [nioEventLoopGroup-2-1] DEBUG cn.itcast.netty.eventloop.TestNettyFuture - 执行计算
    06:22:39.854 [main] DEBUG cn.itcast.netty.eventloop.TestNettyFuture - 结果是:70
    
    • 1
    • 2
    • 3
    • 4
    package cn.itcast.netty.eventloop;
    
    import io.netty.channel.EventLoop;
    import io.netty.channel.nio.NioEventLoop;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.util.concurrent.Future;
    import io.netty.util.concurrent.GenericFutureListener;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    
    @Slf4j
    public class TestNettyFuture {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            NioEventLoopGroup group = new NioEventLoopGroup();
            EventLoop eventLoop = group.next();
            log.debug("看看那个线程");
            Future<Integer> future = eventLoop.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    log.debug("执行计算");
                    Thread.sleep(10);
                    return 70;
                }
            });
    //        //获取结果 非阻塞的
    //        log.debug("等待结果");
    //        log.debug("结果是:{}",future.get());
    
    
    //        变成异步方式
            future.addListener(new GenericFutureListener<Future<? super Integer>>() {
                @Override
                public void operationComplete(Future<? super Integer> future) throws Exception {
                    log.debug("接受结果:{}",future.getNow());
                }
            });
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    06:21:53.979 [main] DEBUG cn.itcast.netty.eventloop.TestNettyFuture - 看看那个线程
    06:21:53.979 [nioEventLoopGroup-2-1] DEBUG cn.itcast.netty.eventloop.TestNettyFuture - 执行计算
    06:21:54.003 [nioEventLoopGroup-2-1] DEBUG cn.itcast.netty.eventloop.TestNettyFuture - 接受结果:70
    
    • 1
    • 2
    • 3

    promise

    package cn.itcast.netty.eventloop;
    
    import io.netty.channel.EventLoop;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.util.concurrent.DefaultPromise;
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.ExecutionException;
    
    @Slf4j
    public class TestNettyPromise {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
    
            //1.准备EventLoop对象
    
            EventLoop eventLoop = new NioEventLoopGroup().next();
    
            //2.可以主动创建promise,结果容器    传入eventLoop
            DefaultPromise<Integer> promise = new DefaultPromise<Integer>(eventLoop);
    
            new Thread(() -> {
                log.debug("开始计算");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    promise.setFailure(e);
                }
                promise.setSuccess(80);
            }).start();
    
    //        4.接受结果的线程  唤醒正在等待的主线程
            log.debug("等待结果");
            log.debug("结果是:{}",promise.get());
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    07:08:51.551 [main] DEBUG cn.itcast.netty.eventloop.TestNettyPromise - 等待结果
    07:08:51.551 [Thread-0] DEBUG cn.itcast.netty.eventloop.TestNettyPromise - 开始计算
    07:08:52.552 [main] DEBUG cn.itcast.netty.eventloop.TestNettyPromise - 结果是:80
    
    • 1
    • 2
    • 3

    Handler & Pipeline

    ChannelHandler 用来处理 Channel 上的各种事件,分为入站、出站两种。所有 ChannelHandler 被连成一串,就是 Pipeline

    • 入站处理器通常是 ChannelInboundHandlerAdapter 的子类,主要用来读取客户端数据,写回结果
    • 出站处理器通常是 ChannelOutboundHandlerAdapter 的子类,主要对写回结果进行加工

    打个比喻,每个 Channel 是一个产品的加工车间,Pipeline 是车间中的流水线,ChannelHandler 就是流水线上的各道工序,而后面要讲的 ByteBuf 是原材料,经过很多工序的加工:先经过一道道入站工序,再经过一道道出站工序最终变成产品
    简单了解Pipeline

    package cn.itcast.netty.eventloop;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import lombok.extern.slf4j.Slf4j;
    
    import java.nio.charset.Charset;
    
    @Slf4j
    public class TestPipeLine {
        public static void main(String[] args) {
            new ServerBootstrap()
                    .group(new NioEventLoopGroup())
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            //1.通过channel拿到pipeline
                            ChannelPipeline pipeline = ch.pipeline();
                            //2.input head -> h1 ->h2 ->h3 ->h4 ->h5 ->tail  双向链表  output  h5 -> h4
                            pipeline.addLast("h1", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    log.debug("1");
                                    super.channelRead(ctx, msg);
                                }
                            });
    
                            pipeline.addLast("h2", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    log.debug("2");
                                    super.channelRead(ctx, msg);
                                }
                            });
    
                            pipeline.addLast("h3", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    log.debug("3");
                                    super.channelRead(ctx, msg);
                                    ch.writeAndFlush(ctx.alloc().buffer().writeBytes("server".getBytes()));
                                }
                            });
    
                            //只有写入数据才会触发
                            pipeline.addLast("h4", new ChannelOutboundHandlerAdapter() {
                                @Override
                                public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                    log.debug("4");
                                    super.write(ctx, msg, promise);
                                }
                            });
                            pipeline.addLast("h5", new ChannelOutboundHandlerAdapter() {
                                @Override
                                public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                    log.debug("5");
                                    super.write(ctx, msg, promise);
                                }
                            });
                        }
                    })
                    .bind(8099);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    管道处理

    package cn.itcast.netty.eventloop;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    
    import java.nio.charset.Charset;
    
    @Slf4j
    public class TestPipeLine {
        public static void main(String[] args) {
            new ServerBootstrap()
                    .group(new NioEventLoopGroup())
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            //1.通过channel拿到pipeline
                            ChannelPipeline pipeline = ch.pipeline();
                            //2.input head -> h1 ->h2 ->h3 ->h4 ->h5 ->tail  双向链表  output  h5 -> h4
                            pipeline.addLast("h1", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    log.debug("1");
                                    ByteBuf buf = (ByteBuf) msg;
                                    String name = buf.toString(Charset.defaultCharset());
                                    super.channelRead(ctx, name);
                                }
                            });
    
                            pipeline.addLast("h2", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object name) throws Exception {
                                    log.debug("2");
                                    Student student = new Student(name.toString());
                                    super.channelRead(ctx, student);
                                }
                            });
    
                            pipeline.addLast("h3", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    log.debug("3,结果{},class:{}",msg,msg.getClass());
                                    super.channelRead(ctx, msg);
                                    ch.writeAndFlush(ctx.alloc().buffer().writeBytes("server".getBytes()));
                                }
                            });
    
                            //只有写入数据才会触发
                            pipeline.addLast("h4", new ChannelOutboundHandlerAdapter() {
                                @Override
                                public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                    log.debug("4");
                                    super.write(ctx, msg, promise);
                                }
                            });
                            pipeline.addLast("h5", new ChannelOutboundHandlerAdapter() {
                                @Override
                                public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                    log.debug("5");
                                    super.write(ctx, msg, promise);
                                }
                            });
                        }
                    })
                    .bind(8099);
        }
    
        @Data
        @AllArgsConstructor
        static class Student {
            private String name;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    package cn.itcast.netty.eventloop;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    
    import java.nio.charset.Charset;
    
    @Slf4j
    public class TestPipeLine {
        public static void main(String[] args) {
            new ServerBootstrap()
                    .group(new NioEventLoopGroup())
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            //1.通过channel拿到pipeline
                            ChannelPipeline pipeline = ch.pipeline();
                            //2.input head -> h1 ->h2 ->h3 ->h4 ->h5 ->tail  双向链表  output  h5 -> h4
                            pipeline.addLast("h1", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    log.debug("1");
                                    ByteBuf buf = (ByteBuf) msg;
                                    String name = buf.toString(Charset.defaultCharset());
                                    super.channelRead(ctx, name);
                                }
                            });
    
                            pipeline.addLast("h2", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object name) throws Exception {
                                    log.debug("2");
                                    Student student = new Student(name.toString());
                                    super.channelRead(ctx, student);
                                }
                            });
    
                            pipeline.addLast("h3", new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    log.debug("3,结果{},class:{}",msg,msg.getClass());
                                    super.channelRead(ctx, msg);
    //                                ch.writeAndFlush(ctx.alloc().buffer().writeBytes("server".getBytes()));
                                    ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("server".getBytes()));
                                    //往前找
                                }
                            });
    
                            //只有写入数据才会触发
                            pipeline.addLast("h4", new ChannelOutboundHandlerAdapter() {
                                @Override
                                public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                    log.debug("4");
                                    super.write(ctx, msg, promise);
                                }
                            });
                            pipeline.addLast("h5", new ChannelOutboundHandlerAdapter() {
                                @Override
                                public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                                    log.debug("5");
                                    super.write(ctx, msg, promise);
                                }
                            });
                        }
                    })
                    .bind(8099);
        }
    
        @Data
        @AllArgsConstructor
        static class Student {
            private String name;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    通过channel模拟input 和ouput数据

    package cn.itcast.netty.eventloop;
    
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufAllocator;
    import io.netty.channel.*;
    import io.netty.channel.embedded.EmbeddedChannel;
    import lombok.extern.slf4j.Slf4j;
    
    import java.nio.charset.Charset;
    
    @Slf4j
    public class TestEmbeddedChannel {
        public static void main(String[] args) {
            ChannelInboundHandlerAdapter h1 = new ChannelInboundHandlerAdapter() {
                @Override
                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                    log.debug("1");
                    super.channelRead(ctx, msg);
                }
            };
            ChannelInboundHandlerAdapter h2 = new ChannelInboundHandlerAdapter() {
                @Override
                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                    log.debug("2");
                    super.channelRead(ctx, msg);
                }
            };
            ChannelOutboundHandlerAdapter h3 = new ChannelOutboundHandlerAdapter() {
                @Override
                public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                    log.debug("3");
                    super.write(ctx, msg, promise);
                }
            };
            ChannelOutboundHandlerAdapter h4 = new ChannelOutboundHandlerAdapter() {
                @Override
                public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                    log.debug("4");
                    super.write(ctx, msg, promise);
                }
            };
            EmbeddedChannel channel = new EmbeddedChannel(h1, h2, h3, h4);
            // 模拟入站操作
    //        channel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("hello".getBytes()));
            // 模拟出站操作
            channel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("world".getBytes()));
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    ByteBuf

    ByteBuf调试与查看动态扩容
    package cn.itcast.netty.eventloop;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufAllocator;
    
    import static io.netty.buffer.ByteBufUtil.appendPrettyHexDump;
    import static io.netty.util.internal.StringUtil.NEWLINE;
    
    public class TestByteBuf {
        public static void main(String[] args) {
    
    
            //PooledUnsafeDirectByteBuf(ridx: 0, widx: 0, cap: 256)
            //PooledUnsafeDirectByteBuf(ridx: 0, widx: 300, cap: 512)
            //动态扩容
            ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
            System.out.println(buf);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 300; i++) {
                sb.append("a");
            }
            buf.writeBytes(sb.toString().getBytes());
            System.out.println(buf);
    
        }
    
        public static void log(ByteBuf buffer) {
            int length = buffer.readableBytes();
            int rows = length / 16 + (length % 15 == 0 ? 0 : 1) + 4;
            StringBuilder buf = new StringBuilder(rows * 80 * 2)
                    .append("read index:").append(buffer.readerIndex())
                    .append(" write index:").append(buffer.writerIndex())
                    .append(" capacity:").append(buffer.capacity())
                    .append(NEWLINE);
            appendPrettyHexDump(buf, buffer);
            System.out.println(buf.toString());
        }
    
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    直接内存 vs 堆内存

    可以使用下面的代码来创建池化基于堆的 ByteBuf

    ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);
    
    • 1

    也可以使用下面的代码来创建池化基于直接内存的 ByteBuf

    ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
    
    • 1
    • 直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
    • 直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收的管理,但也要注意及时主动释放
    池化 vs 非池化

    池化的最大意义在于可以重用 ByteBuf,优点有

    • 没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力
    • 有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率
    • 高并发时,池化功能更节约内存,减少内存溢出的可能

    池化功能是否开启,可以通过下面的系统环境变量来设置

    -Dio.netty.allocator.type={unpooled|pooled}
    
    • 1
    • 4.1 以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现
    • 4.1 之前,池化功能还不成熟,默认是非池化实现

    方法列表,省略一些不重要的方法

    方法签名含义备注
    writeBoolean(boolean value)写入 boolean 值用一字节 01|00 代表 true|false
    writeByte(int value)写入 byte 值
    writeShort(int value)写入 short 值
    writeInt(int value)写入 int 值Big Endian,即 0x250,写入后 00 00 02 50
    writeIntLE(int value)写入 int 值Little Endian,即 0x250,写入后 50 02 00 00
    writeLong(long value)写入 long 值
    writeChar(int value)写入 char 值
    writeFloat(float value)写入 float 值
    writeDouble(double value)写入 double 值
    writeBytes(ByteBuf src)写入 netty 的 ByteBuf
    writeBytes(byte[] src)写入 byte[]
    writeBytes(ByteBuffer src)写入 nio 的 ByteBuffer
    int writeCharSequence(CharSequence sequence, Charset charset)写入字符串

    注意

    • 这些方法的未指明返回值的,其返回值都是 ByteBuf,意味着可以链式调用
    • 网络传输,默认习惯是 Big Endian
    扩容

    再写入一个 int 整数时,容量不够了(初始容量是 10),这时会引发扩容

    buffer.writeInt(6);
    log(buffer);
    
    • 1
    • 2

    扩容规则是

    • 如何写入后数据大小未超过 512,则选择下一个 16 的整数倍,例如写入后大小为 12 ,则扩容后 capacity 是 16
    • 如果写入后数据大小超过 512,则选择下一个 2^n,例如写入后大小为 513,则扩容后 capacity 是 210=1024(29=512 已经不够了)
    • 扩容不能超过 max capacity 会报错
    读取

    例如读了 4 次,每次一个字节

    System.out.println(buffer.readByte());
    System.out.println(buffer.readByte());
    System.out.println(buffer.readByte());
    System.out.println(buffer.readByte());
    log(buffer);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    读过的内容,就属于废弃部分了,再读只能读那些尚未读取的部分

    1
    2
    3
    4
    read index:4 write index:12 capacity:16
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 00 00 05 00 00 00 06                         |........        |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如果需要重复读取 int 整数 5,怎么办?

    可以在 read 前先做个标记 mark

    buffer.markReaderIndex();
    System.out.println(buffer.readInt());
    log(buffer);
    
    • 1
    • 2
    • 3

    结果

    5
    read index:8 write index:12 capacity:16
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 00 00 06                                     |....            |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这时要重复读取的话,重置到标记位置 reset

    buffer.resetReaderIndex();
    log(buffer);
    
    • 1
    • 2

    这时

    read index:4 write index:12 capacity:16
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 00 00 05 00 00 00 06                         |........        |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    还有种办法是采用 get 开头的一系列方法,这些方法不会改变 read index

    retain & release

    由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。

    • UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可
    • UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
    • PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存

    回收内存的源码实现,请关注下面方法的不同实现

    protected abstract void deallocate()

    Netty 这里采用了引用计数法来控制回收内存,每个 ByteBuf 都实现了 ReferenceCounted 接口

    • 每个 ByteBuf 对象的初始计数为 1
    • 调用 release 方法计数减 1,如果计数为 0,ByteBuf 内存被回收
    • 调用 retain 方法计数加 1,表示调用者没用完之前,其它 handler 即使调用了 release 也不会造成回收
    • 当计数为 0 时,底层内存会被回收,这时即使 ByteBuf 对象还在,其各个方法均无法正常使用

    谁来负责 release 呢?

    不是我们想象的(一般情况下)

    ByteBuf buf = ...
    try {
        ...
    } finally {
        buf.release();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    请思考,因为 pipeline 的存在,一般需要将 ByteBuf 传递给下一个 ChannelHandler,如果在 finally 中 release 了,就失去了传递性(当然,如果在这个 ChannelHandler 内这个 ByteBuf 已完成了它的使命,那么便无须再传递)

    基本规则是,谁是最后使用者,谁负责 release,详细分析如下

    • 起点,对于 NIO 实现来讲,在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read 方法中首次创建 ByteBuf 放入 pipeline(line 163 pipeline.fireChannelRead(byteBuf))
    • 入站 ByteBuf 处理原则
      • 对原始 ByteBuf 不做处理,调用 ctx.fireChannelRead(msg) 向后传递,这时无须 release
      • 将原始 ByteBuf 转换为其它类型的 Java 对象,这时 ByteBuf 就没用了,必须 release
      • 如果不调用 ctx.fireChannelRead(msg) 向后传递,那么也必须 release
      • 注意各种异常,如果 ByteBuf 没有成功传递到下一个 ChannelHandler,必须 release
      • 假设消息一直向后传,那么 TailContext 会负责释放未处理消息(原始的 ByteBuf)
    • 出站 ByteBuf 处理原则
      • 出站消息最终都会转为 ByteBuf 输出,一直向前传,由 HeadContext flush 后 release
    • 异常处理原则
      • 有时候不清楚 ByteBuf 被引用了多少次,但又必须彻底释放,可以循环调用 release 直到返回 true

    TailContext 释放未处理消息逻辑

    // io.netty.channel.DefaultChannelPipeline#onUnhandledInboundMessage(java.lang.Object)
    protected void onUnhandledInboundMessage(Object msg) {
        try {
            logger.debug(
                "Discarded inbound message {} that reached at the tail of the pipeline. " +
                "Please check your pipeline configuration.", msg);
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    具体代码

    // io.netty.util.ReferenceCountUtil#release(java.lang.Object)
    public static boolean release(Object msg) {
        if (msg instanceof ReferenceCounted) {
            return ((ReferenceCounted) msg).release();
        }
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    零拷贝

    【零拷贝】的体现之一,对原始 ByteBuf 进行切片成多个 ByteBuf,切片后的 ByteBuf 并没有发生内存复制,还是使用原始 ByteBuf 的内存,切片后的 ByteBuf 维护独立的 read,write 指针

    在这里插入图片描述
    silice

    package cn.itcast.netty.eventloop;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufAllocator;
    
    import static cn.itcast.netty.eventloop.TestByteBuf.log;
    
    public class TestSlice {
        public static void main(String[] args) {
            ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10);
            buf.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'});
            log(buf);
    
    
            //调用切片,没用发生数据复制
            ByteBuf slice1 = buf.slice(0, 5);
            ByteBuf slice2 = buf.slice(5, 5);
    
            log(slice1);
            log(slice2);
    
    
            System.out.println("================================");
            slice1.setByte(0,'b');
            log(slice1);
            log(buf);
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    read index:0 write index:10 capacity:10
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 61 62 63 64 65 66 67 68 69 6a                   |abcdefghij      |
    +--------+-------------------------------------------------+----------------+
    read index:0 write index:5 capacity:5
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 61 62 63 64 65                                  |abcde           |
    +--------+-------------------------------------------------+----------------+
    read index:0 write index:5 capacity:5
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 66 67 68 69 6a                                  |fghij           |
    +--------+-------------------------------------------------+----------------+
    ================================
    read index:0 write index:5 capacity:5
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 62 62 63 64 65                                  |bbcde           |
    +--------+-------------------------------------------------+----------------+
    read index:0 write index:10 capacity:10
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 62 62 63 64 65 66 67 68 69 6a                   |bbcdefghij      |
    +--------+-------------------------------------------------+----------------+
    
    Process finished with exit code 0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    切片后不允许添加

    package cn.itcast.netty.eventloop;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufAllocator;
    
    import static cn.itcast.netty.eventloop.TestByteBuf.log;
    
    public class TestSlice {
        public static void main(String[] args) {
            ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10);
            buf.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'});
            log(buf);
    
    
            //调用切片,没用发生数据复制
            ByteBuf slice1 = buf.slice(0, 5);
    
            ByteBuf slice2 = buf.slice(5, 5);
            log(slice1);
            log(slice2);
    
            //        'a', 'b', 'c', 'd', 'e','x'
            // 直接操作内存 这个x去那写?
            slice1.writeByte('x');
    
            System.out.println("================================");
            slice1.setByte(0,'b');
            log(slice1);
            log(buf);
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    使用计数器为buffer+1 -1

    package cn.itcast.netty.eventloop;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufAllocator;
    
    import static cn.itcast.netty.eventloop.TestByteBuf.log;
    
    public class TestSlice {
        public static void main(String[] args) {
            ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(10);
            buf.writeBytes(new byte[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'});
            log(buf);
    
    
            //调用切片,没用发生数据复制
            ByteBuf slice1 = buf.slice(0, 5);
            System.out.println("================================");
            System.out.println("给计数器+1");
            slice1.retain();
            ByteBuf slice2 = buf.slice(5, 5);
            log(slice1);
            log(slice2);
    
            System.out.println("================================");
            System.out.println("释放原有byteBuf内存");
            buf.release();
            log(slice1);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    duplicate

    【零拷贝】的体现之一,就好比截取了原始 ByteBuf 所有内容,并且没有 max capacity 的限制,也是与原始 ByteBuf 使用同一块底层内存,只是读写指针是独立的

    在这里插入图片描述

    package cn.itcast.netty.eventloop;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufAllocator;
    import io.netty.buffer.CompositeByteBuf;
    import lombok.extern.slf4j.Slf4j;
    
    import static cn.itcast.netty.eventloop.TestByteBuf.log;
    
    
    @Slf4j
    public class TestComposite {
        public static void main(String[] args) {
            ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer();
            buf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
    
            ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer();
            buf2.writeBytes(new byte[]{6, 7, 8, 9, 10});
    
    
    //        ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
    //        //性能不好
    //        buffer.writeBytes(buf1).writeBytes(buf2);
    //        log(buffer);
    
            CompositeByteBuf buffer = ByteBufAllocator.DEFAULT.compositeBuffer();
            buffer.addComponents(true, buf1, buf2);
            log(buffer);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    Unpooled

    Unpooled 是一个工具类,类如其名,提供了非池化的 ByteBuf 创建、组合、复制等操作

    这里仅介绍其跟【零拷贝】相关的 wrappedBuffer 方法,可以用来包装 ByteBuf

    ByteBuf buf1 = ByteBufAllocator.DEFAULT.buffer(5);
    buf1.writeBytes(new byte[]{1, 2, 3, 4, 5});
    ByteBuf buf2 = ByteBufAllocator.DEFAULT.buffer(5);
    buf2.writeBytes(new byte[]{6, 7, 8, 9, 10});
    
    // 当包装 ByteBuf 个数超过一个时, 底层使用了 CompositeByteBuf
    ByteBuf buf3 = Unpooled.wrappedBuffer(buf1, buf2);
    System.out.println(ByteBufUtil.prettyHexDump(buf3));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    输出

             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 01 02 03 04 05 06 07 08 09 0a                   |..........      |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5

    也可以用来包装普通字节数组,底层也不会有拷贝操作

    ByteBuf buf4 = Unpooled.wrappedBuffer(new byte[]{1, 2, 3}, new byte[]{4, 5, 6});
    System.out.println(buf4.getClass());
    System.out.println(ByteBufUtil.prettyHexDump(buf4));
    
    • 1
    • 2
    • 3

    输出

    class io.netty.buffer.CompositeByteBuf
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 01 02 03 04 05 06                               |......          |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    💡 ByteBuf 优势

    • 池化 - 可以重用池中 ByteBuf 实例,更节约内存,减少内存溢出的可能
    • 读写指针分离,不需要像 ByteBuffer 一样切换读写模式
    • 可以自动扩容
    • 支持链式调用,使用更流畅
    • 很多地方体现零拷贝,例如 slice、duplicate、CompositeByteBuf

    双向通信

    package cn.itcast.netty.tow;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    
    import java.nio.charset.Charset;
    
    public class EchoServer {
        public static void main(String[] args) {
            new ServerBootstrap()
                    .group(new NioEventLoopGroup())
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) {
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) {
                                    ByteBuf buffer = (ByteBuf) msg;
                                    System.out.println(buffer.toString(Charset.defaultCharset()));
    
                                    // 建议使用 ctx.alloc() 创建 ByteBuf
                                    ByteBuf response = ctx.alloc().buffer();
                                    response.writeBytes(buffer);
                                    ctx.writeAndFlush(response);
    
                                    // 思考:需要释放 buffer 吗  答:需要 结果:不需要,因为有head 和tail 两个处理
    //                                buffer.release();
                                    // 思考:需要释放 response 吗 答:需要  结果:不需要,因为有head 和tail 两个处理
    //                                response.release();
                                }
                            });
                        }
                    }).bind(8099);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    package cn.itcast.netty.tow;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.string.StringDecoder;
    import io.netty.handler.codec.string.StringEncoder;
    
    import java.nio.charset.Charset;
    import java.util.Scanner;
    
    public class EchoClient {
        public static void main(String[] args) throws InterruptedException {
            NioEventLoopGroup group = new NioEventLoopGroup();
            Channel channel = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<NioSocketChannel>() {
                        @Override
                        protected void initChannel(NioSocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new StringEncoder());
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    ByteBuf buffer = (ByteBuf) msg;
                                    System.out.println(buffer.toString(Charset.defaultCharset()));
    
                                    // 思考:需要释放 buffer 吗
                                }
                            });
                        }
                    }).connect("127.0.0.1", 8099).sync().channel();
            channel.closeFuture().addListener(future -> {
                group.shutdownGracefully();
            });
            new Thread(() -> {
                Scanner scanner = new Scanner(System.in);
                while (true) {
                    String line = scanner.nextLine();
                    if ("q".equals(line)) {
                        channel.close();
                        break;
                    }
                    channel.writeAndFlush(line);
                }
            }).start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    netty黏包半包

    package cn.itcast.netty.StickPack;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class HelloWorldServer {
        void start() {
            NioEventLoopGroup boss = new NioEventLoopGroup();
            NioEventLoopGroup worker = new NioEventLoopGroup();
    
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.channel(NioServerSocketChannel.class);
                serverBootstrap.group(boss, worker);
                serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //打印进入的数据
                       ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    }
                });
                ChannelFuture channelFuture =serverBootstrap.bind(8099).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e) {
                log.error("Server error", e);
            }
            finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            new HelloWorldServer().start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    package cn.itcast.netty.StickPack;
    
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class HelloWorldClient {
        public static void main(String[] args) {
            final NioEventLoopGroup worker = new NioEventLoopGroup();
    
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.group(worker);
                bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            //会在连接channel建立成功后,会触发active事件
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) {
                                for (int i = 0; i < 10; i++) {
                                    final ByteBuf buf = ctx.alloc().buffer(16);
                                    buf.writeBytes(new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15});
                                    ctx.writeAndFlush(buf);
                                }
                            }
                        });
                    }
                });
                final ChannelFuture channelFuture = bootstrap.connect("localhost", 8099).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e) {
                log.error("Server error", e);
            }
            finally {
                worker.shutdownGracefully();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    应该分十次发送,黏在一起

    io.netty.handler.logging.LoggingHandler - [id: 0x3a8f023e, L:/127.0.0.1:8099 - R:/127.0.0.1:6269] READ: 160B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000020| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000030| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000040| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000050| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000060| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000070| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000080| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000090| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    重新编译代码,多实验几次才可能出现半包。

    package cn.itcast.netty.StickPack;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.*;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class HelloWorldServer {
        void start() {
            NioEventLoopGroup boss = new NioEventLoopGroup();
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.channel(NioServerSocketChannel.class);
                //设置缓冲区,形成半包
                serverBootstrap.option(ChannelOption.SO_RCVBUF,10);
                serverBootstrap.group(boss, worker);
                serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //打印进入的数据
                       ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    }
                });
                ChannelFuture channelFuture =serverBootstrap.bind(8099).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e) {
                log.error("Server error", e);
            }
            finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            new HelloWorldServer().start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    package cn.itcast.netty.StickPack;
    
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class HelloWorldClient {
        public static void main(String[] args) {
            final NioEventLoopGroup worker = new NioEventLoopGroup();
    
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.group(worker);
                bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            //会在连接channel建立成功后,会触发active事件
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) {
                                for (int i = 0; i < 10; i++) {
                                    final ByteBuf buf = ctx.alloc().buffer(16);
                                    buf.writeBytes(new byte[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15});
                                    ctx.writeAndFlush(buf);
                                }
                            }
                        });
                    }
                });
                final ChannelFuture channelFuture = bootstrap.connect("localhost", 8099).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e) {
                log.error("Server error", e);
            }
            finally {
                worker.shutdownGracefully();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
          +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    +--------+-------------------------------------------------+----------------+
    07:15:41.299 [nioEventLoopGroup-3-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 16, cap: 1024) that reached at the tail of the pipeline. Please check your pipeline configuration.
    07:15:41.299 [nioEventLoopGroup-3-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0xc3a1ce81, L:/127.0.0.1:8099 - R:/127.0.0.1:9128].
    07:15:41.299 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xc3a1ce81, L:/127.0.0.1:8099 - R:/127.0.0.1:9128] READ COMPLETE
    07:15:41.300 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xc3a1ce81, L:/127.0.0.1:8099 - R:/127.0.0.1:9128] READ: 36B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|
    |00000020| 00 01 02 03                                     |....            |
    +--------+-------------------------------------------------+----------------+
    07:15:41.300 [nioEventLoopGroup-3-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledUnsafeDirectByteBuf(ridx: 0, widx: 36, cap: 1024) that reached at the tail of the pipeline. Please check your pipeline configuration.
    07:15:41.300 [nioEventLoopGroup-3-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0xc3a1ce81, L:/127.0.0.1:8099 - R:/127.0.0.1:9128].
    07:15:41.300 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xc3a1ce81, L:/127.0.0.1:8099 - R:/127.0.0.1:9128] READ COMPLETE
    07:15:41.300 [nioEventLoopGroup-3-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xc3a1ce81, L:/127.0.0.1:8099 - R:/127.0.0.1:9128] READ: 108B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000020| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000030| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000040| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000050| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|
    |00000060| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f             |............    |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    现象分析

    粘包

    • 现象,发送 abc def,接收 abcdef
    • 原因
      • 应用层:接收方 ByteBuf 设置太大(Netty 默认 1024)
      • 滑动窗口:假设发送方 256 bytes 表示一个完整报文,但由于接收方处理不及时且窗口大小足够大,这 256 bytes 字节就会缓冲在接收方的滑动窗口中,当滑动窗口中缓冲了多个报文就会粘包
      • Nagle 算法:会造成粘包

    半包

    • 现象,发送 abcdef,接收 abc def
    • 原因
      • 应用层:接收方 ByteBuf 小于实际发送数据量
      • 滑动窗口:假设接收方的窗口只剩了 128 bytes,发送方的报文大小是 256 bytes,这时放不下了,只能先发送前 128 bytes,等待 ack 后才能发送剩余部分,这就造成了半包
      • MSS 限制:当发送的数据超过 MSS 限制后,会将数据切分发送,就会造成半包

    黏包半包解决方案

    1.短连接
    package cn.itcast.netty.StickPack;
    
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class HelloWorldClient {
        public static void main(String[] args) {
            for (int i = 0; i <10 ; i++) {
                send();
            }
        }
    
        private static void send() {
            final NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.group(worker);
                bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            //会在连接channel建立成功后,会触发active事件
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) {
                                for (int i = 0; i < 10; i++) {
                                    final ByteBuf buf = ctx.alloc().buffer(16);
                                    buf.writeBytes(new byte[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15});
                                    ctx.writeAndFlush(buf);
                                    //每次发送断开连接
                                    ctx.channel().close();
                                }
                            }
                        });
                    }
                });
                final ChannelFuture channelFuture = bootstrap.connect("localhost", 8099).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e) {
                log.error("Server error", e);
            } finally {
                worker.shutdownGracefully();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    2.定长解码器

    客户端代码

    package cn.itcast.netty.fixlenth;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import lombok.extern.slf4j.Slf4j;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.Arrays;
    import java.util.Random;
    
    @Slf4j
    public class Client {
        static final Logger log = LoggerFactory.getLogger(Client.class);
    
        public static void main(String[] args) {
    
            fill10Bytes('1', 5);
            fill10Bytes('2', 2);
            fill10Bytes('3', 10);
    
            /**
             *    11111_____
             *   22________
             *    3333333333
             * */
            send();
            System.out.println("finish");
        }
    
        public static byte[] fill10Bytes(char c, int len) {
            byte[] bytes = new byte[10];
            Arrays.fill(bytes, (byte) '_');
            for (int i = 0; i < len; i++) {
                bytes[i] = (byte) c;
            }
            System.out.println(new String(bytes));
            return bytes;
        }
    
        private static void send() {
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.group(worker);
                bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            // 会在连接 channel 建立成功后,会触发 active 事件
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) {
                                ByteBuf buf = ctx.alloc().buffer();
                                char c = '0';
                                Random r = new Random();
                                //发十条 fill10Bytes 上面的消息
                                for (int i = 0; i < 10; i++) {
                                    byte[] bytes = fill10Bytes(c, r.nextInt(10) + 1);
                                    c++;
                                    buf.writeBytes(bytes);
                                }
                                ctx.writeAndFlush(buf);
                            }
                        });
                    }
                });
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8099).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.error("client error", e);
            } finally {
                worker.shutdownGracefully();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    客户端发送的消息已黏包

    11:47:56.740 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x46335422, L:/127.0.0.1:7553 - R:/127.0.0.1:8099] WRITE: 100B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 30 30 30 30 30 30 30 5f 5f 5f 31 31 5f 5f 5f 5f |0000000___11____|
    |00000010| 5f 5f 5f 5f 32 5f 5f 5f 5f 5f 5f 5f 5f 5f 33 33 |____2_________33|
    |00000020| 33 33 33 5f 5f 5f 5f 5f 34 34 34 34 34 34 34 34 |333_____44444444|
    |00000030| 5f 5f 35 35 35 35 35 35 35 5f 5f 5f 36 36 36 36 |__5555555___6666|
    |00000040| 36 36 5f 5f 5f 5f 37 37 37 37 37 5f 5f 5f 5f 5f |66____77777_____|
    |00000050| 38 38 38 5f 5f 5f 5f 5f 5f 5f 39 39 39 5f 5f 5f |888_______999___|
    |00000060| 5f 5f 5f 5f                                     |____            |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    服务器代码

    package cn.itcast.netty.fixlenth;
    
    import lombok.extern.slf4j.Slf4j;
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.AdaptiveRecvByteBufAllocator;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelOption;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.FixedLengthFrameDecoder;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    
    @Slf4j
    public class Server {
        void start() {
            NioEventLoopGroup boss = new NioEventLoopGroup();
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.channel(NioServerSocketChannel.class);
                // 调整系统的接收缓冲区(滑动窗口)
    //            serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
                // 调整 netty 的接收缓冲区(byteBuf)
                serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));
                serverBootstrap.group(boss, worker);
                serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new FixedLengthFrameDecoder(10));
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    }
                });
                ChannelFuture channelFuture = serverBootstrap.bind(8099).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.error("server error", e);
            } finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            new Server().start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    服务器指定的消息大小

             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 36 36 36 36 36 36 5f 5f 5f 5f                   |666666____      |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    3.行解密码器(等待换行符)

    客户端

    package cn.itcast.netty.linehandle;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.Random;
    
    public class Client {
        static final Logger log = LoggerFactory.getLogger(Client.class);
    
        public static void main(String[] args) {
            send();
            System.out.println("finish");
        }
    
        public static StringBuilder makeString(char c, int len) {
            StringBuilder sb = new StringBuilder(len + 2);
            for (int i = 0; i < len; i++) {
                sb.append(c);
            }
            sb.append("\n");
            return sb;
        }
    
        private static void send() {
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.channel(NioSocketChannel.class);
                bootstrap.group(worker);
                bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                        ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                            // 会在连接 channel 建立成功后,会触发 active 事件
                            @Override
                            public void channelActive(ChannelHandlerContext ctx) {
                                ByteBuf buf = ctx.alloc().buffer();
                                char c = '0';
                                Random r = new Random();
                                for (int i = 0; i < 10; i++) {
                                    StringBuilder sb = makeString(c, r.nextInt(256) + 1);
                                    c++;
                                    buf.writeBytes(sb.toString().getBytes());
                                }
                                ctx.writeAndFlush(buf);
                            }
                        });
                    }
                });
                ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8099).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.error("client error", e);
            } finally {
                worker.shutdownGracefully();
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
            +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
    |00000010| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
    |00000020| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
    |00000030| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
    |00000040| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
    |00000050| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
    |00000060| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
    |00000070| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000|
    |00000080| 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 0a |000000000000000.|
    |00000090| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
    |000000a0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
    |000000b0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
    |000000c0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
    |000000d0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
    |000000e0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
    |000000f0| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
    |00000100| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
    |00000110| 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111|
    |00000120| 31 31 31 31 31 31 31 31 31 31 31 31 0a 32 32 32 |111111111111.222|
    |00000130| 32 32 32 32 32 32 32 32 32 32 32 0a 33 33 33 33 |22222222222.3333|
    |00000140| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |00000150| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |00000160| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |00000170| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |00000180| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |00000190| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |000001a0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |000001b0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |000001c0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |000001d0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |000001e0| 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333|
    |000001f0| 33 33 33 0a 34 34 34 34 34 34 34 34 34 34 34 34 |333.444444444444|
    |00000200| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |00000210| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |00000220| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |00000230| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |00000240| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |00000250| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |00000260| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |00000270| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |00000280| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |00000290| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |000002a0| 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 34 |4444444444444444|
    |000002b0| 34 34 34 0a 35 35 35 35 35 35 35 35 35 35 35 35 |444.555555555555|
    |000002c0| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |000002d0| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |000002e0| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |000002f0| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000300| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000310| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000320| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000330| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000340| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000350| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000360| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000370| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000380| 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 35 |5555555555555555|
    |00000390| 35 35 35 35 35 0a 36 36 36 36 36 36 36 36 36 36 |55555.6666666666|
    |000003a0| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |000003b0| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |000003c0| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |000003d0| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |000003e0| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |000003f0| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000400| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000410| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000420| 0a 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |.777777777777777|
    |00000430| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
    |00000440| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
    |00000450| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
    |00000460| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 37 |7777777777777777|
    |00000470| 37 37 37 37 37 37 37 37 37 37 37 37 37 37 0a 38 |77777777777777.8|
    |00000480| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
    |00000490| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
    |000004a0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
    |000004b0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
    |000004c0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
    |000004d0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
    |000004e0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
    |000004f0| 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 38 |8888888888888888|
    |00000500| 38 38 38 38 38 38 38 38 38 38 38 38 38 0a 39 39 |8888888888888.99|
    |00000510| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |00000520| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |00000530| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |00000540| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |00000550| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |00000560| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |00000570| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |00000580| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |00000590| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |000005a0| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 39 |9999999999999999|
    |000005b0| 39 39 39 39 39 39 39 39 39 39 39 39 39 39 0a    |99999999999999. |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96

    服务器

    package cn.itcast.netty.linehandle;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.AdaptiveRecvByteBufAllocator;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelOption;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.FixedLengthFrameDecoder;
    import io.netty.handler.codec.LineBasedFrameDecoder;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import lombok.extern.slf4j.Slf4j;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class Server {
        static final Logger log = LoggerFactory.getLogger(Server.class);
        void start() {
            NioEventLoopGroup boss = new NioEventLoopGroup();
            NioEventLoopGroup worker = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.channel(NioServerSocketChannel.class);
                // 调整系统的接收缓冲区(滑动窗口)
    //            serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
                // 调整 netty 的接收缓冲区(byteBuf)
                serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(16, 16, 16));
                serverBootstrap.group(boss, worker);
                serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    }
                });
                ChannelFuture channelFuture = serverBootstrap.bind(8099).sync();
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.error("server error", e);
            } finally {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            new Server().start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
          +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000010| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000020| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000030| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000040| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000050| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000060| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000070| 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 36 |6666666666666666|
    |00000080| 36 36 36 36 36 36 36 36 36 36                   |6666666666      |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.LTC解密码器(基于长度字段解码器)

    五个参数

    public LengthFieldBasedFrameDecoder(
        int maxFrameLength,
        int lengthFieldOffset, int lengthFieldLength,
        int lengthAdjustment, int initialBytesToStrip)
    
    • 1
    • 2
    • 3
    • 4
    maxFrameLength 表示数据的最大长度(如果还没有发现分割失败)
    
    
    lengthFieldOffset 数据长度标识的起始位置偏移量
    用于指明数据第几个字节开始是用于标识有用字节长度的,因为前面可能还有其他附加信息
    
    lengthFieldLength 数据长度标识所占字节数(记录数据的长度)
    除了长度字段,还有多少个长度代表附加信息
    lengthAdjustment 
    
    initialBytesToStrip (用于剥离长度字段)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    package cn.itcast.netty.TCL;
    
    import cn.itcast.netty.linehandle.Server;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.ByteBufAllocator;
    import io.netty.channel.embedded.EmbeddedChannel;
    import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
    import io.netty.handler.logging.LogLevel;
    import io.netty.handler.logging.LoggingHandler;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class TCL {
        static final Logger log = LoggerFactory.getLogger(TCL.class);
    
        public static void main(String[] args) {
            EmbeddedChannel channel = new EmbeddedChannel(
                    new LengthFieldBasedFrameDecoder(
                            1024, 0, 4, 1,4),
                    new LoggingHandler(LogLevel.DEBUG)
            );
    
            //  4 个字节的内容长度, 实际内容
            ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
            send(buffer, "Hello, world");
            send(buffer, "Hi!");
            channel.writeInbound(buffer);
        }
    
        private static void send(ByteBuf buffer, String content) {
            byte[] bytes = content.getBytes(); // 实际内容
            int length = bytes.length; // 实际内容长度
            log.info("content:{},length:{}",content,length);
            buffer.writeInt(length);
            buffer.writeByte(1);
            buffer.writeBytes(bytes);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 01 48 65 6c 6c 6f 2c 20 77 6f 72 6c 64          |.Hello, world   |
    +--------+-------------------------------------------------+----------------+
    15:07:53.093 [main] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0xembedded, L:embedded - R:embedded] READ: 4B
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 01 48 69 21                                     |.Hi!            |
    +--------+-------------------------------------------------+----------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 相关阅读:
    CAS:1445723-73-8 DSPE-PEG-NHS 磷脂-聚乙二醇-活性酯脂质PEG共轭物供应
    Vue教学19:Element UI组件深入探索,构建精致的Vue应用界面
    activiti 通过xml上传 直接部署模型
    Container is running beyond memory limits
    音视频领域的未来发展方向展望
    【操作系统】段式存储中 逻辑地址转物理地址详细分析
    Elastic:使用 Grafana 监视 Elasticsearch
    计算机毕业设计JavaSneaker’sHome设计网站(源码+系统+mysql数据库+lw文档)
    mysql 高性能搭建2-2: mysql5.7.29 主主复制+keepalived实现高可用
    Python | 执行shell 命令的几种方法
  • 原文地址:https://blog.csdn.net/qq_43751489/article/details/125677717