当Netty变成凶残练习曲

Test: 

package com.zhao.io;

import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamDemo01 {
    public static void main(String[] args) throws IOException {
       
        // hello.txt 内容:返回
        FileInputStream fis = new FileInputStream("/Users/apple/IdeaProjects/Netty-Demo/hello.txt");

        int by;

        while ((by=fis.read())!=-1) {
            System.out.print(by+" ");  // 232 191 148 229 155 158
        }

        //释放资源
        fis.close();
    }
}

hello.txt 文件内容 

返回

打印的结果是  232 191 148 229 155 158,可有对这个结果感到不解?

我们平时存储数据都是以二进制文件存储的(包括汉字),但是一堆 0 和 1,普通人都看不懂那是什么,所以我们定制了 ASCLL 码表,例如 单词字母 a,对应的就是 97,数字 1 对应的 49,以此类推。但是光是中文汉字就高达十万个,更别说别国文字了。于是 UTF8 规定 英文字母占一个字节,汉字占用三个字节,例如 “返” 字就用  232 191 148 这三个数字对应的二进制表示。但是三个字节会耗内存。可以粗略的算一下,与GBK相比一个汉字多占一个字节,1024 个汉字就要多占 1m 内存,1024 * 1024 个汉字就多占用一个 G 的内存。具体选用哪种编码还是需要权衡 

public class InPutStreamTest {

    public static void main(String[] args) throws IOException {

        // 文件输入流
        FileInputStream inputStream = new FileInputStream(new File("/Users/apple/IdeaProjects/Netty-Demo/hello.txt"));

        // 文件输出流
        FileOutputStream outputStream = new FileOutputStream("/Users/apple/IdeaProjects/Netty-Demo/hello2.txt",true);

        // 每次读取的元素容量
        byte[] bytes = new byte[1024];
        int len;

        // 把每次读取的元素个数赋值给变量 len。输入流理论上每次读取(1024)个元素
        // 判断是否读到末尾
        while((len = inputStream.read(bytes)) != -1 ){

             // 输出流理论上每次写入 1024 个元素,从 0 开始到 len 结束
             outputStream.write(bytes,0,len);
        }

        // 关闭流
        inputStream.close();
        outputStream.close();

    }

}

NIO

三大组件:Channel & Buffer & Selector

ByteBuffer 的简单使用

package com.zhao.io;

import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/26 21:23
 * @Description: ByteBuffer 的简单使用
 */
public class ChannelDemo {

    private static Logger LOGGER = LoggerFactory.getLogger(ChannelDemo.class);

    public static void main(String[] args) {

        try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {
            // 准备缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(10);
            do {
                // 从 channel 读取数据,向 buffer 写入
                int len = channel.read(buffer);
                LOGGER.debug("读取到的字节数 {}", len);
                if (len == -1) {
                    break;
                }

                // 打印 buffer 的内容
                buffer.flip(); // 切换至读模式
                while (buffer.hasRemaining()) {
                    byte b = buffer.get();
                    LOGGER.debug("实际字节 {}", (char)b);
                }

                buffer.clear(); // 切换至写模式

            } while (true);

        } catch (IOException e) {

        }
    }

}

ByteBuffer 的读写模式 

package com.zhao.io;

import java.nio.ByteBuffer;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/27 15:51
 * @Description: ByteBuffer 的读写模式 
 */
public class TestByteBufferReadWrite {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);

        // 写入
        buffer.put((byte) 97);
        ByteBufferUtil.debugAll(buffer);  // 'a'

        buffer.put(new byte[]{98,99,100});  // 'b','c','d'
        ByteBufferUtil.debugAll(buffer);

        // 获取
        // System.out.println( buffer.get());  // 0,原因是未切换至读模式,position: [4] 位于索引 4

        // 读模式
        buffer.flip();
        System.out.println( (char) buffer.get());
        ByteBufferUtil.debugAll(buffer);  // 获取一个元素后,position 指针移动至 索引1 的位置
        buffer.compact();
        ByteBufferUtil.debugAll(buffer); // 清除已读过的内存,把元素向前移动,原有的元素不会清除,后续写入会覆盖

    }

}

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

  • 可以调用 rewind 方法将 position 重新置为 0

  • 或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针

 

ByteBuffer 的内存位置

package com.zhao.io;

import java.nio.ByteBuffer;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/27 16:10
 * @Description: ByteBuffer 的内存位置
 */
public class TestByteBufferAllocate {

    public static void main(String[] args) {
        System.out.println(ByteBuffer.allocate(16).getClass());       // class java.nio.HeapByteBuffer
        System.out.println(ByteBuffer.allocateDirect(16).getClass()); // class java.nio.DirectByteBuffer

        /**
         * class java.nio.HeapByteBuffer  - java 堆内存,读写效率低,受到 GC 影响
         * class java.nio.DirectByteBuffer - 直接内存,读写效率高(少一次拷贝),不会受 GC 影响,分配的效率低
         */

    }

}

ByteBuffer 的标记与回归 

package com.zhao.io;

import java.nio.ByteBuffer;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/27 16:21
 * @Description: ByteBuffer 的标记与回归
 */
public class TestByteBufferRead {

    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put(new byte[]{'a','b','c','d'});  // 写模式
        buffer.flip();  // 读模式

        //  rewind 从头开始读
        buffer.get(new byte[4]);
        ByteBufferUtil.debugAll(buffer);

        buffer.rewind();
        System.out.println((char) buffer.get());  // 'a',本来读取的应该为空, rewind 方法重置了指针的位置

        // mark & reset
        // mark 做一个标记,记录 position 的位置,reset 是将 position 重置到 mark 的位置
        System.out.println((char) buffer.get()); // 'b'

        // 加标记,索引为2 的位置
        buffer.mark();

        System.out.println((char) buffer.get()); // 'c'
        System.out.println((char) buffer.get()); // 'd'

        // 将position 重置到索引2 的位置
        buffer.reset();

        System.out.println((char) buffer.get()); // 'c'
        System.out.println((char) buffer.get()); // 'd'

        // get(i) 不会改变索引的位置
        System.out.println((char) buffer.get(3)); // 'c'
        ByteBufferUtil.debugAll(buffer);
    }

}

ByteBuffer 与字符串的转换

package com.zhao.io;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/27 16:45
 * @Description: ByteBuffer 与字符串的转换
 */
public class TestByteBufferString {

    public static void main(String[] args) {
        ByteBuffer buffer1 = ByteBuffer.allocate(16);

                    // 字符串 转 Buffer
        // 方式1:字符串 转 Byte 数组 转 Buffer,position 指针不会回归
        buffer1.put("hello".getBytes());
        ByteBufferUtil.debugAll(buffer1);

        // 方式2:字符串 转 Charsets 转 Buffer,position 指针会自动切换至读模式
        ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
        ByteBufferUtil.debugAll(buffer2);

        // 方式3:字符串 转 Byte 数组 转 Buffer,position 指针会自动切换至读模式
        ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
        ByteBufferUtil.debugAll(buffer3);

                    // Buffer 转 字符串
        String str1 = StandardCharsets.UTF_8.decode(buffer2).toString();
        System.out.println(str1);

        // 方式1 转换成字符串需要先切换成 读模式
        buffer1.flip();
        String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();
        System.out.println(str2);

    }

}

ByteBuffer 的分散读   集中写

package com.zhao.io;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/27 17:17
 * @Description: 分散读
 */
public class TestScatteringReads {

    public static void main(String[] args) {

        try (RandomAccessFile file = new RandomAccessFile("word1.txt", "r")) {
            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();
            ByteBufferUtil.debugAll(a);
            ByteBufferUtil.debugAll(b);
            ByteBufferUtil.debugAll(c);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}

 ByteBuffer 的分散读  

package com.zhao.io;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/27 17:25
 * @Description: 集中写
 */
public class TestGatheringWrites {

    public static void main(String[] args) {

        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("word2.txt", "rw").getChannel()) {

            channel.write(new ByteBuffer[]{b1, b2, b3});

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

黏包 & 半包

原始数据

Hello,world\n

I'm zhangsan\n

How are you?\n

黏包:将多条数据整合为一条,Hello,world\nI'm zhangsan\nHo

半包:整合为一条后,分开读取,变成不完整的数据。w are you?\n

解决方式

package com.zhao.io;

import io.netty.buffer.ByteBuf;

import java.nio.ByteBuffer;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 13:53
 * @Description: 黏包 & 半包
 */
public class TestByteBufferExam {

    public static void main(String[] args) {
        // 创建缓冲区
        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);

    }

    // 进行字符串的切割
    public static void split(ByteBuffer source){

        // 切换至读模式
        source.flip();

        // 遍历
        for (int i = 0; i < source.limit(); i++) {

            // 如果到换行符,就停止
            if (source.get(i) == '\n'){
                int len = i + 1 - source.position();

                // 创建写入 Buffer
                ByteBuffer target = ByteBuffer.allocate(len);

                // 由 source 读,向 target 写入
                for (int j = 0; j < len; j++) {

                    // 切换至写入模式,移动指针,向source 写入
                    target.put(source.get());
                }

                // 打印结果
                ByteBufferUtil.debugAll(target);
            }
        }

        // 不进行元素覆盖,写到 Ho 时,进行写 w are you? 时,不会对 Ho 进行覆盖
        source.compact();
    }

}

FileChannel 的复制

package com.zhao.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 15:13
 * @Description: FileChannel 的复制
 */
public class TestFileChannelTransferTo {

    public static void main(String[] args) {
        try (
                FileChannel from = new FileInputStream("data.txt").getChannel();
                FileChannel to = new FileOutputStream("to.txt").getChannel()
        ) {

            // 效率高,底层利用操作系统的零拷贝进行优化
            from.transferTo(0, from.size(), to);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

改进版

package com.zhao.io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 15:13
 * @Description: FileChannel 的复制
 */
public class TestFileChannelTransferTo {

    public static void main(String[] args) {
        try (
                FileChannel from = new FileInputStream("data.txt").getChannel();
                FileChannel to = new FileOutputStream("to.txt").getChannel()
        ) {

            // 效率高,底层利用操作系统的零拷贝进行优化
            // 遗憾的是,transferTo 最多只能传输 2G 大小的内容
            // 弥补措施,可以进行多次传输
            long size = from.size();  // 总数量

            // left 变量代表还剩多少字节
            for (long left = size; left > 0; ) {
                left -= from.transferTo(size - left, left, to);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

遍历文件夹文件的数目 

package com.zhao.io;

import java.io.File;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 16:14
 * @Description: 递归得到文件夹和文件的数量
 */
public class TestFilesWalkFileTree1 {

    private static int fileCount = 0;
    private static int directoryCount = 0;

    public static void main(String[] args) {

        File srcFile = new File("/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk");
        getAllFilePath(srcFile);
        System.out.println("文件的数量:"+fileCount+",文件夹的数量:"+directoryCount);  // 文件的数量:923,文件夹的数量:72
    }

    public static void getAllFilePath(File srcFile) {
        File[] files = srcFile.listFiles();
        // 遍历
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                if (files[i].isDirectory()) {
                    directoryCount++;
                    getAllFilePath(files[i]);
                    System.out.println(files[i].getName());
                } else if (files[i].isFile()) {
                    fileCount++;
                    getAllFilePath(files[i]);
                    System.out.println(files[i].getName());
                } else {
                    //不是:获取绝对路径输出在控制台
                    System.out.println(files[i].getAbsolutePath());
                }
            }
        }
    }
}
package com.zhao.io;

import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 16:27
 * @Description:
 */
public class TestFilesWalkFileTree2 {

    public static void main(String[] args) throws IOException {

        m1();
        m2();
        m3();

    }

    /**
     * 删除含有文件的文件夹
     */
    public static void m3() throws IOException {
        AtomicInteger fileCount = new AtomicInteger();
        AtomicInteger directoryCount = new AtomicInteger();

        Files.walkFileTree(Paths.get("/Volumes/Transcend/jdk1.8.0_261.jdk/Contents/Home/bin"), new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                    throws IOException {
                Files.delete(file);
                fileCount.incrementAndGet();
                return super.visitFile(file, attrs);
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc)
                    throws IOException {
                Files.delete(dir);
                directoryCount.incrementAndGet();
                return super.postVisitDirectory(dir, exc);
            }
        });

        System.out.println("删除含有文件的文件夹:" + directoryCount + " 个,删除含有文件的文件:" + fileCount + " 个");
    }


    /**
     * 打印文件夹和文件的数量
     */
    public static void m2() throws IOException {
        AtomicInteger fileCount = new AtomicInteger();

        Files.walkFileTree(Paths.get("/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk"), new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

                if (file.toString().endsWith(".jar")) {
                    fileCount.incrementAndGet();
                }
                return super.visitFile(file, attrs);
            }
        });

        System.out.println("以 .jar结尾的文件的数量:" + fileCount);
    }


    /**
     * 打印文件夹和文件的数量
     */
    public static void m1() throws IOException {
        AtomicInteger fileCount = new AtomicInteger();
        AtomicInteger directoryCount = new AtomicInteger();


        Files.walkFileTree(Paths.get("/Library/Java/JavaVirtualMachines/jdk1.8.0_261.jdk"), new SimpleFileVisitor<Path>() {

            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                System.out.println("===== > " + dir);
                directoryCount.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("文件的数量:" + fileCount + ",文件夹的数量:" + directoryCount);
    }
}

多级文件的拷贝

package com.zhao.io;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 17:58
 * @Description: 拷贝多级文件
 */
public class TestFilesCopy {

    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        String source = "/Users/apple/Desktop/jdk1.8.0_261.jdk";
        String target = "/Users/apple/Desktop/jdk8.jdk";

        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);
    }

}


我是一名食客,大家都叫我食神,叫的我挺不好意思。

今天我来到一家餐馆就餐,这家餐馆叫 ‘阻塞饭店’。

食神拿了菜单看得入了神,迟迟没有说话。

这时,憨憨赵来了,“小二,一碗凉水”。憨憨赵看见没人应他,抬起头看向小二。

小二此时,一句话也不敢说,目不转睛的看着食神,弯着腰,这个动作似乎保持了很久,态度谦卑极了。

食神开口了,“一壶酒,两斤牛肉,三斤烤鸭,四斤米饭!今天胃口不太好,等不够了再叫你。”

小二如负重托,“好嘞,客官,您稍等!”,对憨憨赵投了一个歉意的眼神,去后厨报菜去了。

憨憨赵: 。。。。。。

没过多久小二回来了,手里端着一些饭菜,端向食神。接着迈着哈吧步跑到憨憨赵面前,“客官需要点什么?”

憨憨赵陷入了沉思。

此时,门外传来了二货马 的声音,“五斤麻花,六斤酒,七斤米饭,八两肉”

小二目不转睛的看着我,弯着腰,这个动作似乎保持了很久,态度谦卑极了!

阻塞餐馆

package com.zhao.c1;

import com.zhao.io.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 20:10
 * @Description: 阻塞餐馆
 */
public class Server {

    private static Logger LOGGER = LoggerFactory.getLogger(Server.class);

    public static void main(String[] args) throws IOException {

        // 使用nio来理解阻塞模式
        // 0. ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(16);

        // 1. 创建服务器(饭店开门)
        ServerSocketChannel ssc = ServerSocketChannel.open();

        // 2. 绑定监听端口(饭店地址)
        ssc.bind(new InetSocketAddress(8080));

        // 3. 连接集合(吃饭又岂是食神一人,集合装的都是来吃饭的)
        List<SocketChannel> channes = new ArrayList<>();
        while (true) {

            LOGGER.debug("路人迈着八爷步,来到饭店,找了个位置,坐下");

            // 4. accept 建立与客户端连接,SocketChannel 用来与客户端之间通信(菜单)
            SocketChannel sc = ssc.accept();  // 阻塞方法,线程停止运行

            LOGGER.debug("路人拿起菜单,深思了许久,{}",sc);
            channes.add(sc);

            // 5. 遍历连接(依次处理请求)
            for (SocketChannel channe : channes) {

                // 6. 接受客户端发送的数据(路人缓缓开口了)
                LOGGER.debug("小二弯着腰,等待路人点餐。这个动作似乎保持了很久,态度谦卑极了!{}",channe);
                channe.read(buffer);  // 阻塞方法,线程停止运行

                // 7. 切换至读模式,把每位顾客的订单信息报给后厨
                buffer.flip();

                // 8. 后厨处理订单信息
                ByteBufferUtil.debugAll(buffer);

                // 9. 清零小黑板信息
                buffer.clear();
                LOGGER.debug("这位顾客的菜已经做完了,小黑板要掉信息了。。。{}",channe);
            }

        }
    }

}

食神

package com.zhao.c1;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 20:32
 * @Description: 食神
 */
public class Client {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost",8080));
        System.out.println("食神正在思考吃什么");
    }

}

顾客在未点完餐前,小二只为他一人服务,别人来了只能干等!

非阻塞模式

第二天来了一家非阻塞饭店。

憨憨赵前脚刚踏进饭店,

店小二:“呦,客官来了,快请快请!”

食神也紧随其后。

后面传来了:“呦,客官来了,快请快请!”

憨憨赵:“点酒。一碗凉水。”

店小二:“好嘞!”

食神:“点菜,一盘猪头肉”

店小二:“好嘞!”,“呦,客官来了,快请快请!”,不用看,二货马来了

二货马:“点菜,两碗凉水”

店小二:“好嘞!”

憨憨赵发现不管谁来,店小二都会及时发现,并且喊上自己的口头禅。店小二现在的路线像是一个环,前宾,服务员,后厨,他一个人全干了。速度奇快,在我的感知里,从这个环的开始到结束也就不到 1 秒,而且从未停歇过。但是看到小二的满头大汗,我知道这样下去肯定要把他累趴下的。

package com.zhao.c1;

import com.zhao.io.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 20:10
 * @Description: 阻塞餐馆
 */
public class Server {

    private static Logger LOGGER = LoggerFactory.getLogger(Server.class);

    public static void main(String[] args) throws IOException {

        // 使用nio来理解阻塞模式
        // 0. ByteBuffer
        ByteBuffer buffer = ByteBuffer.allocate(16);

        // 1. 创建服务器(饭店开门)
        ServerSocketChannel ssc = ServerSocketChannel.open();

        // 非阻塞模式,线程还会继续运行,如果没有连接建立,那 ssc 是 null
        ssc.configureBlocking(false);   // 放开的是 ssc.accept(); 方法,不会在该方法上面停留

        // 2. 绑定监听端口(饭店地址)
        ssc.bind(new InetSocketAddress(8080));

        // 3. 连接集合(吃饭又岂是食神一人,集合装的都是来吃饭的)
        List<SocketChannel> channes = new ArrayList<>();
        while (true) {

            // 4. accept 建立与客户端连接,SocketChannel 用来与客户端之间通信(菜单)
            SocketChannel sc = ssc.accept();  // 阻塞方法,线程停止运行

            if (sc != null) {
                sc.configureBlocking(false);  // 非阻塞模式,放开的是 channe.read(buffer); 方法,不会在该方法上停留

                LOGGER.debug("呦,客官来了,快请快请!{}", sc);
                channes.add(sc);
            }

            // 5. 遍历连接(依次处理请求)
            for (SocketChannel channe : channes) {

                // 6. 接受客户端发送的数据(路人缓缓开口了)
                int read = channe.read(buffer);// 非阻塞模式,线程仍会继续运行,如果没有读到数据,read 返回 0

                if (read > 0) {
                    // 7. 切换至读模式,把每位顾客的订单信息报给后厨
                    buffer.flip();

                    // 8. 后厨处理订单信息
                    ByteBufferUtil.debugAll(buffer);

                    // 9. 清零小黑板信息
                    buffer.clear();
                    LOGGER.debug("这位顾客的菜已经做完了,小黑板要掉信息了。。。{}", channe);
                }
            }

        }
    }

}

Selector模式

第三天来了一家选择器饭店

憨憨赵前脚刚踏进饭店,

这次的店小二就比较懒了,但是憨憨赵看到了门外有一个硕大无比的摄像头。

此时,正在葛优瘫的店小二通过摄像头看到了憨憨赵,连忙起身

店小二:“呦,客官来了,快请快请!”

憨憨赵走进饭店既没点水,也没点饭。走到一旁坐了下去,憨憨赵注意到桌子上面有个按铃。

店小二又去葛优瘫了。

没过多久,食神也来了。店小二再次通过摄像头看到了,连忙起身

店小二:“呦,客官来了,快请快请!”

食神走进饭店既没点水,也没点饭。也是走到一旁坐了下去,食神也注意到桌子上面有个按铃。

店小二?又去葛优瘫了。

没过一会,憨憨赵按了响铃,把写好的菜单交给了店小二

店小二接过单子,上面赫然写着“一壶水”

随后,店小二让服务员按照单子去做了。

“客官,你的水”,随后在这张菜单后面打了一个 ✅,表示已经处理过了

食神也点好菜了,把菜单递给店小二,“一壶酒,两斤牛肉,三斤烤鸭,四斤米饭”

店小二连忙接过单子,但是并没有把单子交给服务员,似乎是忘记了。

这样没过一会,食神就询问后厨菜好了没有,着急呢!

但是不管怎么催后厨都没有用,单子在小二手里呢,催后厨,他也只能干瞪眼!

后厨无奈,因为他始终没见到单子,只好单向的取消了订单,表示让食神重新点菜。

经过以上教训,店长决定要分工明确一些,比如店小二的工作就是看大门的,服务员就是接单子的,后厨就是按照单子做菜的。每当发生什么事件,要向老板汇报,例如,来客人了,小二向老板发送来的是谁。客人下单了,服务员就要向老板汇报点了什么菜。后厨要做菜就要向老板申请菜单。

第二天

店小二负责接待客人,葛优瘫的店小二通过摄像头看到有人来了,起身迎接,并记录了这一事件,发送给老板。服务员把客人的单子发送给老板,后厨向老板申请单子(只是申请做菜的单子,饭店来了什么人,后厨并不关心)

一切看似正常,但是大家都忽略了一个致命的问题,就是做完菜并没有做标记,表示这道菜已经做完了。于是,出现了以下的情况

后厨做完了一道菜后,向老板申请了一个新单子,发现还是这道菜,当做好后,找不到对应的客人了。(上个客人吃完后,已经走了)

解决办法很简单,做完一道菜后,老板把单子删掉,再次申请时就不会重复了。(摄像头会监控这一切,不用担心客人会赖账的问题)

好景不长,食神因为今天吃了太多的东西,又在餐馆点了十斤肉,终于撑到了。老板急了,立马打 120 送食神去医院,老板,店小二和服务员也一同去了,饭店此时就剩后厨一人,因为老板没有把订单取消,于是后厨在不断的炒菜,累的头上直冒汗。

等到老板回来,后厨说了这件事,老板表示,如果下次还有这种情况,那就把订单取消。

package com.zhao.c1;

import com.zhao.io.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 20:10
 * @Description: 阻塞餐馆
 */
public class Server {

    private static Logger LOGGER = LoggerFactory.getLogger(Server.class);

    public static void main(String[] args) throws IOException {

        // 创建 Selector,管理多个 Channel
        Selector selector = Selector.open();

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);

        // 建立 Selector 和 Channel 的联系(注册)
        // SelectionKey 将来事件发生后,通过它可以知道时哪个 Channel 发生的事件
        /**
         * 事件的类型
         * accept:会在有连接请求时触发
         * connect:是客户端,连接建立后触发
         * read:可读事件
         * write:可写事件
         */
        SelectionKey sscKey = ssc.register(selector, 0, null);

        // 关注的是哪种事件,现在 sscKey 只关注 ACCEPT 事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        LOGGER.debug("register key:{}", sscKey);

        ssc.bind(new InetSocketAddress(8080));
        while (true) {
            // 判断是否有事件发生,如果四种事件其中一种发生了,会向下运行。没有发生,则阻塞
            // select 在事件未处理时,它不会阻塞,事件发生后要么处理,要么取消,不能置之不理
            selector.select();
            // 处理事件,selectedKeys 集合内部包含了所有发生的事件(set集合)
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();

                // 处理 key 时,要从 selectedKeys 集合中删除,否则下次处理就会有问题
                iterator.remove();
                LOGGER.debug("register key:{}", key);

                if (key.isAcceptable()) {

                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    LOGGER.debug("{}", sc);
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);
                    scKey.interestOps(SelectionKey.OP_READ);
                    LOGGER.debug("{}", sc);
                    LOGGER.debug("scKey:{}", scKey);

                } else if (key.isReadable()) {  // 如果是 read

                    try {
                        SocketChannel channel = (SocketChannel) key.channel();  // 拿到触发事件的 channel
                        ByteBuffer buffer = ByteBuffer.allocate(16);
                        int read = channel.read(buffer);// 如果是正常断开,read 方法的返回值就是 -1
                        if (read == -1) {
                            key.cancel();
                        } else {
                            buffer.flip(); // 切换至读模式
                            ByteBufferUtil.debugAll(buffer);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();  // 因为客户端断开了,因此需要将 key 取消 (从 selector 的 keys 集合中真正删除 key )
                    }

                }

                // key.cancel();  // 取消订单
            }
        }
    }

}

事情到此就结束了吗?不会的,强如微软,也在 windows 问世后也弥补了很多漏洞。何况一个毫无经验的饭店呢?

有一天来了一位名叫,嚯,他的名字好长,名叫:超级无敌噼里啪啦英俊潇洒风流倜傥人见人爱花见花开车见车爆胎...万‘花’丛中过片叶不沾身神奇小子·尼古拉斯二货马,就他一个名字,整整写了三张纸。

 因为纸张的大小是有限制的,一次没写完只能把余下的写到另一张纸上面。

解决办法也很简单,客人来了,我们先记录如果一页不够写的话,就换大一点的纸张,并把之前记录的拷贝到新的纸张上面。但新纸张需要设置多大呢?两倍,第二次要是还不够,就再扩容两倍。例如:第一张纸容量是 10,第二张纸就是 20,第三张纸就是 40,第四张纸就是 80,以此类推。

餐厅:

package com.zhao.c1;

import com.zhao.io.ByteBufferUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 20:10
 * @Description: 阻塞餐馆
 */
public class Server {

    private static Logger LOGGER = LoggerFactory.getLogger(Server.class);

    // 进行字符串的切割
    public static void split(ByteBuffer source){

        // 切换至读模式
        source.flip();

        // 遍历
        for (int i = 0; i < source.limit(); i++) {

            // 如果到换行符,就停止
            if (source.get(i) == '\n'){
                int len = i + 1 - source.position();

                // 创建写入 Buffer
                ByteBuffer target = ByteBuffer.allocate(len);

                // 由 source 读,向 target 写入
                for (int j = 0; j < len; j++) {

                    // 切换至写入模式,移动指针,向source 写入
                    target.put(source.get());
                }

                // 打印结果
                ByteBufferUtil.debugAll(target);
            }
        }

        // 不进行元素覆盖,写到 Ho 时,进行写 w are you? 时,不会对 Ho 进行覆盖
        source.compact();
    }


    public static void main(String[] args) throws IOException {

        // 创建 Selector,管理多个 Channel
        Selector selector = Selector.open();

        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);

        // 建立 Selector 和 Channel 的联系(注册)
        // SelectionKey 将来事件发生后,通过它可以知道时哪个 Channel 发生的事件
        /**
         * 事件的类型
         * accept:会在有连接请求时触发
         * connect:是客户端,连接建立后触发
         * read:可读事件
         * write:可写事件
         */
        SelectionKey sscKey = ssc.register(selector, 0, null);

        // 关注的是哪种事件,现在 sscKey 只关注 ACCEPT 事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);
        LOGGER.debug("register key:{}", sscKey);

        ssc.bind(new InetSocketAddress(8080));
        while (true) {
            // 判断是否有事件发生,如果四种事件其中一种发生了,会向下运行。没有发生,则阻塞
            // select 在事件未处理时,它不会阻塞,事件发生后要么处理,要么取消,不能置之不理
            selector.select();
            // 处理事件,selectedKeys 集合内部包含了所有发生的事件(set集合)
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();

                // 处理 key 时,要从 selectedKeys 集合中删除,否则下次处理就会有问题
                iterator.remove();
                LOGGER.debug("register key:{}", key);

                if (key.isAcceptable()) {

                    ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                    SocketChannel sc = channel.accept();
                    LOGGER.debug("{}", sc);
                    sc.configureBlocking(false);

                    ByteBuffer buffer = ByteBuffer.allocate(16);

                    // 将一个 ByteBuffer 作为附件关联到 SelectionKey 上
                    SelectionKey scKey = sc.register(selector, 0, buffer);
                    scKey.interestOps(SelectionKey.OP_READ);
                    LOGGER.debug("{}", sc);
                    LOGGER.debug("scKey:{}", scKey);

                } else if (key.isReadable()) {  // 如果是 read

                    try {
                        SocketChannel channel = (SocketChannel) key.channel();  // 拿到触发事件的 channel

                        // 获取 SelectionKey 上关联的 Buffer
                        ByteBuffer buffer = (ByteBuffer) key.attachment();
                        int read = channel.read(buffer);// 如果是正常断开,read 方法的返回值就是 -1
                        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();  // 因为客户端断开了,因此需要将 key 取消 (从 selector 的 keys 集合中真正删除 key )
                    }

                }

                // key.cancel();  // 取消订单
            }
        }
    }

}

客人

package com.zhao.c1;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

/**
 * @Auther: HackerZhao
 * @Date: 2021/10/28 20:32
 * @Description: 食神
 */
public class Client {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost",8080));
        SocketAddress address = sc.getLocalAddress();

        sc.write(Charset.defaultCharset().encode("1234567\n890abcd3333\n"));
        sc.write(Charset.defaultCharset().encode("中国\n"));

        System.in.read();

    }

}

后续:后厨想给客人写一封信。信很长,如下

服务器端

package com.zhao.c1;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

/**
 * @Auther: HackerZhao
 * @Date: 2021/11/2 01:48
 * @Description: 服务器端
 */
public class WriteServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));

        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while(true){
            selector.select();
            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while(iter.hasNext()){
                SelectionKey key = iter.next();
                iter.remove();

                if (key.isAcceptable()){
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    
                    StringBuilder sb = new StringBuilder();

                    // 1. 向客户端发送大量数据
                    for (int i = 0; i < 3000000; i++) {
                        sb.append("a");
                    }

                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());

                    // 判断buffer中是否还有字节
                    while (buffer.hasRemaining()) {
                        int write = sc.write(buffer);// 不能保证把所有数据都写入客户端,返回值代表实际写入的字节数
                        System.out.println(write);
                    }
                }

            }

        }
    }
}

客户端

package com.zhao.c1;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * @Auther: HackerZhao
 * @Date: 2021/11/2 01:59
 * @Description: 客户端
 */
public class WriteClient {

    public static void main(String[] args) throws IOException {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress("localhost",8080));

        // 接收数据
        int count = 0;
        while (true) {

            ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
            count += sc.read(buffer);
            System.out.println(count);
            buffer.clear();
        }


    }
}

后厨写了很长的信,期间来订单了他也不管不问,只顾着自己写信,老板很生气,让他不忙了写信,忙了你去炒菜。

UpdateServerCode

package com.zhao.c1;

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;

/**
 * @Auther: HackerZhao
 * @Date: 2021/11/2 01:48
 * @Description:
 */
public class WriteServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.bind(new InetSocketAddress(8080));

        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
            while (iter.hasNext()) {
                SelectionKey key = iter.next();
                iter.remove();

                if (key.isAcceptable()) {
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    SelectionKey scKey = sc.register(selector, 0, null);

                    scKey.interestOps(SelectionKey.OP_READ);

                    StringBuilder sb = new StringBuilder();

                    // 1. 向客户端发送大量数据
                    for (int i = 0; i < 3000000; i++) {
                        sb.append("a");
                    }

                    ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());

                    // 2. 返回值代表实际写入的字节数
                    int write = sc.write(buffer);// 不能保证把所有数据都写入客户端,
                    System.out.println(write);

                    // 3. 判断buffer中是否有剩余内容
                    if (buffer.hasRemaining()) {

                        // 4. 关注可写事件
                        scKey.interestOps(scKey.interestOps() + SelectionKey.OP_WRITE);

                        // 5. 未写完的数据挂到 scKey 上
                        scKey.attach(buffer);
                    }
                } else if (key.isWritable()) {

                    // 当可写的时候,取出挂载的 buffer,继续写入
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    SocketChannel sc = (SocketChannel) key.channel();
                    int write = sc.write(buffer);  // 从 buffer 读,向channel 写
                    System.out.println(write);

                    // 6. 清理操作
                    if (!buffer.hasRemaining()) {
                        key.attach(null);  // 数据读完后,清除 buffer 与 key 之间的关系
                        key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);  // 不需要关注可写事件
                    }
                }

            }

        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海上钢琴师_1900

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值