1. 你对NIO有多少了解
    • NIO是JDK 1.4 开始有的,其目的是为了提高速度。NIO 翻译成 no-blocking io 或者 new io
    1. NIO和传统IO有什么区别
    • 传统IO是一次一个字节地处理数据,NIO 是以块(缓冲区)的形式处理数据。最主要的是,NIO 可以实现非阻塞,而传统 IO 只能是阻塞的
    • IO 的实际场景是文件 IO 和网络 IO,NIO 在网络IO场景下提升就尤其明显了
    • 在 Java NIO 有三个核心部分组成。分别是 Buffer(缓冲区)、Channel(管道)以及 Selector(选择器)
    • 可以简单的理解为:Buffer 是存储数据的地方,Channel 是运输数据的载体,而 Selector 用于检查多个 Channel 的状态变更情况
    1. IO 模型有几种
    • 在 Unix 下 IO 模型分别有:阻塞IO、非阻塞IO、IO复用、信号驱动以及异步I/O。在开发中碰得最多的就是阻塞IO、非阻塞IO以及IO复用
    1. 重点讲讲 IO 复用模型
    • 以 Linux 系统为例好了,我们都知道 Linux 对文件的操作实际上就是通过文件描述符(fd)
    • IO 复用模型指的就是:
      • 通过一个进程监听多个文件描述符,一旦某个文件描述符准备就绪,就去通知程序做相对应的处理
      • 这种以通知的方式,优势并不是对于单个连接能处理得更快,而是在于它能处理更多的连接
      • 在 Linux 下 IO 复用模型用的函数有 select/pollepoll
    1. 讲讲这 select 和 epoll 函数的区别
    • select
      • select函数它支持最大的连接数是1024或2048,因为在select函数下要传入fd_set参数,这个fd_set的大小要么1024或2048(其实就看操作系统的位数)
      • fd_set就是bitmap的数据结构,可以简单理解为只要位为0,那说明还没数据到缓冲区,只要位为1,那说明数据已经到缓冲区。
      • select函数做的就是每次将fd_set遍历,判断标志位有没有发现变化,如果有变化则通知程序做中断处理
    • epoll
      • 在Linux2.6内核正式提出,完善了select 的一些缺点
      • 定义了epoll_event结构体来处理,不存在最大连接数的限制
      • 它不像select函数每次把所有的文件描述符(fd)都遍历,简单理解就是epoll把就绪的文件描述符(fd)专门维护了一块空间,每次从就绪列表里边拿就好了,不再进行对所有文件描述符(fd)进行遍历
    • 总结
      • image.png
    1. 什么叫做零拷贝
    • 以读操作为例,假设用户程序发起一次读请求
    • 程序调用 read 相关的「系统函数」,然后会从用户态切换到内核态,随后 CPU 会告诉 DMA 去磁盘把数据拷贝到内核空间。
    • 等到「内核缓冲区」真正有数据之后,CPU会把「内核缓存区」数据拷贝到「用户缓冲区」,最终用户程序才能获取到
    • 因为应用程序不能直接去读取硬盘的数据,从上面描述可知需要依赖「内核缓冲区」
    • 一次读操作会让DMA将磁盘数据拷贝到内核缓冲区,CPU将内核缓冲区数据拷贝到用户缓冲区
    • 所谓的零拷贝就是将「CPU将内核缓冲区数据拷贝到用户缓冲区」这次CPU拷贝给省去,来提高效率和性能
    • 常见的零拷贝技术有 mmap(内核缓冲区与用户缓冲区的共享)、sendfile(系统底层函数支持) —》 nginx 使用了这个技术
    • 零拷贝可以提高数据传输的性能,这块在Kafka、Nginx等有相关的实践
    • image.png
    1. NIO Demo

      1. public class NoBlockServer {
      2. public static void main(String[] args) throws IOException {
      3. // 1.获取通道
      4. ServerSocketChannel server = ServerSocketChannel.open();
      5. // 2.切换成非阻塞模式
      6. server.configureBlocking(false);
      7. // 3. 绑定连接
      8. server.bind(new InetSocketAddress(6666));
      9. // 4. 获取选择器
      10. Selector selector = Selector.open();
      11. // 4.1将通道注册到选择器上,指定接收“监听通道”事件
      12. server.register(selector, SelectionKey.OP_ACCEPT);
      13. // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
      14. while (selector.select() > 0) {
      15. // 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
      16. Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
      17. // 7. 获取已“就绪”的事件,(不同的事件做不同的事)
      18. while (iterator.hasNext()) {
      19. SelectionKey selectionKey = iterator.next();
      20. // 接收事件就绪
      21. if (selectionKey.isAcceptable()) {
      22. // 8. 获取客户端的链接
      23. SocketChannel client = server.accept();
      24. // 8.1 切换成非阻塞状态
      25. client.configureBlocking(false);
      26. // 8.2 注册到选择器上-->拿到客户端的连接为了读取通道的数据(监听读就绪事件)
      27. client.register(selector, SelectionKey.OP_READ);
      28. } else if (selectionKey.isReadable()) { // 读事件就绪
      29. // 9. 获取当前选择器读就绪状态的通道
      30. SocketChannel client = (SocketChannel) selectionKey.channel();
      31. // 9.1读取数据
      32. ByteBuffer buffer = ByteBuffer.allocate(1024);
      33. // 9.2得到文件通道,将客户端传递过来的图片写到本地项目下(写模式、没有则创建)
      34. FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
      35. while (client.read(buffer) > 0) {
      36. // 在读之前都要切换成读模式
      37. buffer.flip();
      38. outChannel.write(buffer);
      39. // 读完切换成写模式,能让管道继续读取文件的数据
      40. buffer.clear();
      41. }
      42. }
      43. // 10. 取消选择键(已经处理过的事件,就应该取消掉了)
      44. iterator.remove();
      45. }
      46. }
      47. }
      48. }

      ```java public class NoBlockClient {

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

      1. // 1. 获取通道
      2. SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));
      3. // 1.1切换成非阻塞模式
      4. socketChannel.configureBlocking(false);
      5. // 1.2获取选择器
      6. Selector selector = Selector.open();
      7. // 1.3将通道注册到选择器中,获取服务端返回的数据
      8. socketChannel.register(selector, SelectionKey.OP_READ);
      9. // 2. 发送一张图片给服务端吧
      10. FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\面试造火箭\\1.png"), StandardOpenOption.READ);
      11. // 3.要使用NIO,有了Channel,就必然要有Buffer,Buffer是与数据打交道的呢
      12. ByteBuffer buffer = ByteBuffer.allocate(1024);
      13. // 4.读取本地文件(图片),发送到服务器
      14. while (fileChannel.read(buffer) != -1) {
      15. // 在读之前都要切换成读模式
      16. buffer.flip();
      17. socketChannel.write(buffer);
      18. // 读完切换成写模式,能让管道继续读取文件的数据
      19. buffer.clear();
      20. }
    1. // 5. 轮训地获取选择器上已“就绪”的事件--->只要select()>0,说明已就绪
    2. while (selector.select() > 0) {
    3. // 6. 获取当前选择器所有注册的“选择键”(已就绪的监听事件)
    4. Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
    5. // 7. 获取已“就绪”的事件,(不同的事件做不同的事)
    6. while (iterator.hasNext()) {
    7. SelectionKey selectionKey = iterator.next();
    8. // 8. 读事件就绪
    9. if (selectionKey.isReadable()) {
    10. // 8.1得到对应的通道
    11. SocketChannel channel = (SocketChannel) selectionKey.channel();
    12. ByteBuffer responseBuffer = ByteBuffer.allocate(1024);
    13. // 9. 知道服务端要返回响应的数据给客户端,客户端在这里接收
    14. int readBytes = channel.read(responseBuffer);
    15. if (readBytes > 0) {
    16. // 切换读模式
    17. responseBuffer.flip();
    18. System.out.println(new String(responseBuffer.array(), 0, readBytes));
    19. }
    20. }
    21. // 10. 取消选择键(已经处理过的事件,就应该取消掉了)
    22. iterator.remove();
    23. }
    24. }
    25. }

    } ```