• NIO的浅了解


    一、五种IO类型

    1、阻塞IO

    用户进程一直等待数据准备好,在复制完成之前都是阻塞的

    2、非阻塞IO

    用户进程需要不断轮询查看是否数据准备好

    优化了提升并发连接数量,但是每一个请求都需要创建一个socket建立连接,每个线程都需要去遍历轮询,导致cpu的消耗,直到数据返回成功

    3、IO复用

    (1)select/poll

    单个线程可以处理多个网络连接。相对于BIO多了一步,注册到selector的过程,进程被selector函数阻塞。

    select单个进程所能打开的fd()【就是文件描述符,linux内把所有的外部设备都看成一个文件操作】,对于每一个文件的读写操作都会调用内核提供的系统命令来返回给fd,对于socket的读写也会返回一个fd,所以放fd的时候,说明数据是可读或者是可写。

    所以这个模型在单进程里,操作的连接数默认是1024,可以修改,但是会带来网络性能的下降,因为会去加大轮询次数,带来网络延迟,因为只有少数连接处于活跃状态,而每次轮询是查询所有的连接。

    jdk1.6之前是使用这种模型

    (2)epoll

    jdk1.6后使用此模型。解决select-poll的缺陷

    • 对单个进程锁打开的连接数没有限制(连接需要占用内存,1TB大概10w个连接)
    • 利用每个fd上的callback函数来实现异步回调,省去了轮询的开销
    • mmap:通过内核和用户空间映射同一块内存空间,来减少内存复制

    4、异步IO

    二、NIO

    1、了解

    基于通道和缓冲区操作的:

    • 通道:一个新的原始IO
    • 缓冲支持:负责数据存储和传输的缓冲区
    • 具体:数据从通道读取到缓冲,数据从缓冲区写入通道

    非阻塞的:

    • 针对网络的,当线程从通道读取数据到缓冲区,线程依然可以进行其他事情(1.6以后epoll模型)

    选择器:

    • 用于监听多个通道的事件,例如链接打开、数据到达
    • 单个线程可以监听多个数据通道

    2、Channel

    FileChannel:从文件中读写数据(不适合Selector,因为不能非阻塞)

    DatagramChannel:通过UDP协议读写网络中的数据

    SocketChannel:通过TCP协议读写网络中的数据

    ServerSocketChannel:监听一个TCP连接,对于每一个新的客户端连接都会创建一个SocketChannel。

    3、Buffer

    buffer是一个对象,包含需要写入或者刚读出的数据,常用的缓冲区类型是ByteBuffer

    1. public class aaa {
    2. public static void main(String[] args) throws IOException {
    3. try {
    4. FileInputStream fis = new FileInputStream(new File("D:/test.txt"));
    5. FileOutputStream fos = new FileOutputStream(new File("D:/test.txt"));
    6. FileChannel fin = fis.getChannel();
    7. FileChannel fout = fos.getChannel();
    8. //初始化一个缓冲区
    9. ByteBuffer buffer = ByteBuffer.allocate(1024);
    10. //读取数据到缓冲区
    11. fin.read(buffer);
    12. //从读转化为写
    13. buffer.flip();
    14. fout.write(buffer);
    15. //重置缓冲区
    16. buffer.clear();
    17. } catch (FileNotFoundException e) {
    18. e.printStackTrace();
    19. }
    20. }
    21. }

    4、IO和NIO的区别

    类型操作区域处理数据IO
    IO面向最初的数据源

    每次读取时=读取所有字节或字符,无缓存

    无法前后移动读取流中的数据

    当一个线程在读或者写的时候,当数据被完全读取/写入之后,并且数据未准备好时,线程不能做其他任务,只能一直等待。

    当线程处于活跃状态并且外部未准备好时,阻塞。

    NIO面相缓冲区

    先将数据读取到缓冲区

    可在缓冲区前后移动数据流

    当一个线程向某个通道发送请求时,当数据被完全读取/写入,并且数据未准备好时,线程可以操作其他任务,直到数据准备后再切换回原通道,继续读写,也就是selector的使用。

    外部准备好时才唤醒线程,则不会阻塞。

    5、缓冲区的内部细节

    缓冲区本质是一块可以写入数据,以及从中读取数据的内存,实际上也是一个byte[]数据,只是在NIO中被封装成了NIO Buffer对象,并提供了一组方法来访问这个内存块。

    - capacity:容量

    - position:位置。读是跟踪从缓冲区读取了多少数据;写是数据放入数组哪个位置;

    - limit:写是还有多少数据取出来写到通道;读是还有多少空间可以放出去;

    例如:写入

    初始话时,capacity是8,就是一个8长度的格子,当写入“hello”的时候,就是ficn.reds("hello);时,postion和limit都会指向4(从0开始),当flip()时,position指向0,limit是4,0-4就是要写入的字符,最后write()写入。

    6、零拷贝的原理

    IO流程:内核给磁盘发送命令需要读取磁盘的数据,在DMA的控制下,把磁盘上的数据读入到内核缓冲区,内核把数据从内核缓冲区复制到用户缓冲区。用户缓冲区再将数据拷贝到Socket buffer(也是内核),最后发送到网卡缓冲区,四个步骤。

    设计到一个用户态到内核态的切换,影响cpu的性能。

    从内核态到用户缓冲区没有用,为什么要设计呢

    为了提升IO性能,假设应用程序进行读,内核缓冲区对于读相当于一个缓冲空间,当用户只读取一小部分数据的时候,但是内核从磁盘会读取一块数据,下次用户再读其他的数据的时候,在内核缓冲区已经存在就不需要再去磁盘获取,从这个角度是提升了性能的。

    使用内存映射缓存mmap,通过内核和用户空间映射同一块内存空间,来减少内存复制。

    零拷贝就是减少拷贝次数,减少内核态和用户态之间的数据的复制,提升IO性能。

    1. public class aaa {
    2. public static void main(String[] args) throws IOException {
    3. try {
    4. FileChannel in = FileChannel.open(Paths.get("D:/logo.png"), StandardOpenOption.READ);
    5. FileChannel out = FileChannel.open(Paths.get("D:/logo_cp.png"), StandardOpenOption.READ, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
    6. MappedByteBuffer inMap = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
    7. MappedByteBuffer outMap = in.map(FileChannel.MapMode.READ_WRITE, 0, in.size());
    8. byte[] bytes = new byte[inMap.limit()];
    9. inMap.get(bytes);
    10. outMap.put(bytes);
    11. in.close();
    12. out.close();
    13. } catch (FileNotFoundException e) {
    14. e.printStackTrace();
    15. }
    16. }
    17. }
    1. public class ccc {
    2. public static void main(String[] args) {
    3. try {
    4. SocketChannel socketChannel = SocketChannel.open();
    5. socketChannel.connect(new InetSocketAddress("localhost", 9090));
    6. FileChannel channel = new FileInputStream("D:/1.txt").getChannel();
    7. int position = 0;
    8. long size = channel.size();
    9. while (size > 0) {
    10. long tf = channel.transferTo(position, size, socketChannel);
    11. if (tf > 0) {
    12. position += tf;
    13. size = tf;
    14. }
    15. }
    16. socketChannel.close();
    17. channel.close();
    18. } catch (IOException e) {
    19. e.printStackTrace();
    20. }
    21. }
    22. }
    1. public class ddd {
    2. public static void main(String[] args) {
    3. try {
    4. ServerSocketChannel channel = ServerSocketChannel.open();
    5. channel.socket().bind(new InetSocketAddress(9090));
    6. SocketChannel accept = channel.accept();
    7. ByteBuffer allocate = ByteBuffer.allocate(1024);
    8. int r = 0;
    9. FileChannel fileChannel = new FileOutputStream("D:/text_cp.txt").getChannel();
    10. while (r != -1){
    11. r = accept.read(allocate);
    12. allocate.flip();
    13. fileChannel.write(allocate);
    14. allocate.clear();
    15. }
    16. } catch (IOException e) {
    17. e.printStackTrace();
    18. }
    19. }
    20. }

    7、SocketChannel和ServerSocketChannel

    服务端

    1. public class aaa {
    2. public static void main(String[] args) throws IOException {
    3. try {
    4. //支持两种模式:阻塞、非阻塞
    5. ServerSocketChannel open = ServerSocketChannel.open();
    6. open.configureBlocking(false);
    7. //绑定端口号
    8. open.socket().bind(new InetSocketAddress(9090));
    9. while (true) {
    10. SocketChannel accept = open.accept();
    11. //存在连接
    12. if (accept != null) {
    13. ByteBuffer buffer = ByteBuffer.allocate(1024);
    14. accept.read(buffer);
    15. System.out.println(new String(buffer.array()));
    16. //再把消息写回到客户端
    17. Thread.sleep(10000);
    18. buffer.filp();
    19. accept.write(buffer);
    20. } else {
    21. Thread.sleep(1000);
    22. System.out.println("无客户端连接");
    23. }
    24. }
    25. } catch (FileNotFoundException | InterruptedException e) {
    26. e.printStackTrace();
    27. }
    28. }
    29. }

    客户端

    1. public class aaa {
    2. public static void main(String[] args) throws IOException {
    3. try {
    4. SocketChannel open = SocketChannel.open();
    5. //把客户端设置为非阻塞,在非阻塞模式下,不一定是等到连接建立之后再往下执行
    6. open.configureBlocking(false);
    7. open.connect(new InetSocketAddress("localhost", 9090));
    8. if(open.isConnectionPending()) {
    9. open.finishConnect();
    10. }
    11. ByteBuffer buffer = ByteBuffer.allocate(1024);
    12. buffer.flip();
    13. open.write(buffer);
    14. //读取服务端返回的数据
    15. buffer.clear();
    16. //非阻塞模式,这里不阻塞
    17. int r = open.read(buffer);
    18. if(r > 0) {
    19. System.out.println("收到服务端的消息" + new String(buffer.array()));
    20. }
    21. } catch (FileNotFoundException e) {
    22. e.printStackTrace();
    23. }
    24. }
    25. }

    8、选择器Selector

    一个单独的线程可以管理多个channel,从而管理多个网络连接。

    1. public class aaa {
    2. static Selector selector;
    3. public static void main(String[] args) throws IOException {
    4. //创建一个多路复用器
    5. Selector selector = Selector.open();
    6. ServerSocketChannel channel = ServerSocketChannel.open();
    7. //连接的非阻塞
    8. channel.configureBlocking(false);
    9. channel.socket().bind(new InetSocketAddress(9090));
    10. //监听连接事件,会返回一个SelectionKey,通道的唯一标识
    11. channel.register(selector, SelectionKey.OP_ACCEPT);
    12. //轮询
    13. while (true) {
    14. //阻塞,所有注册到复用器上事件
    15. selector.select();
    16. //一旦某个channel准备就绪,就返回他们的key
    17. Set<SelectionKey> selectionKeys = selector.selectedKeys();
    18. Iterator<SelectionKey> iterator = selectionKeys.iterator();
    19. while (iterator.hasNext()) {
    20. //一定是已经就绪的通道
    21. SelectionKey next = iterator.next();
    22. //拿到通道后,可以处理了,避免重复处理
    23. iterator.remove();
    24. if(next.isAcceptable()){
    25. //连接事件
    26. HandleAccept(next);
    27. }else if(next.isReadable()){
    28. //读事件
    29. HandleRead(next);
    30. }
    31. }
    32. }
    33. }
    34. private static void HandleAccept(SelectionKey selectionKey) throws IOException {
    35. ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
    36. SocketChannel accept = serverSocketChannel.accept();
    37. //IO的非阻塞
    38. accept.configureBlocking(false);
    39. accept.write(ByteBuffer.wrap("Server write".getBytes()));
    40. //注册的是accept的读事件
    41. accept.register(selector, SelectionKey.OP_ACCEPT);
    42. }
    43. private static void HandleRead(SelectionKey selectionKey) throws IOException {
    44. SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
    45. ByteBuffer buffer = ByteBuffer.allocate(1024);
    46. socketChannel.read(buffer);
    47. System.out.println("Server receive msg: " + new String(buffer.array()));
    48. }
    49. }
    1. public class bbb {
    2. static Selector selector;
    3. public static void main(String[] args) {
    4. try {
    5. selector = Selector.open();
    6. SocketChannel socketChannel = SocketChannel.open();
    7. socketChannel.configureBlocking(false);
    8. socketChannel.connect(new InetSocketAddress("localhost", 9090));
    9. socketChannel.register(selector, SelectionKey.OP_CONNECT);
    10. while (true) {
    11. selector.select();
    12. Set<SelectionKey> selectionKeys = selector.selectedKeys();
    13. Iterator<SelectionKey> iterator = selectionKeys.iterator();
    14. while (iterator.hasNext()) {
    15. SelectionKey next = iterator.next();
    16. iterator.remove();
    17. if(next.isConnectable()){
    18. //连接事件
    19. HandleConnect(next);
    20. }else if(next.isReadable()){
    21. //读事件
    22. HandleRead(next);
    23. }
    24. }
    25. }
    26. } catch (IOException e) {
    27. e.printStackTrace();
    28. }
    29. }
    30. private static void HandleConnect(SelectionKey selectionKey) throws IOException {
    31. SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
    32. if(socketChannel.isConnectionPending()){
    33. socketChannel.finishConnect();
    34. }
    35. socketChannel.configureBlocking(false);
    36. socketChannel.write(ByteBuffer.wrap("Client receive".getBytes()));
    37. socketChannel.register(selector, SelectionKey.OP_READ);
    38. }
    39. //读取服务端返回的数据
    40. private static void HandleRead(SelectionKey selectionKey) throws IOException {
    41. SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
    42. ByteBuffer buffer = ByteBuffer.allocate(1024);
    43. socketChannel.read(buffer);
    44. System.out.println("Client receive msg: " + new String(buffer.array()));
    45. }
    46. }

    9、BIO\NIO\AIO的区别

    BIONIOAIO
    阻塞阻塞:一个线程执行IO操作会被阻塞非阻塞:线程可以同时处理多个IO请求非阻塞
    同步同步:需要等待IO操作完成后才能继续执行异步:轮询方式=channel+buffer+selector回调机制
    处理    面向流缓冲区面向事件
    并发低,需要创建大量线程通过单线程或少量线程处理同左
    场景连接数少且吞吐量不高连接数较多但请求量较小高并发

  • 相关阅读:
    音视频进阶教程|如何实现游戏中的实时语音
    http缓存策略以及强缓存和协商缓存浅析
    [附源码]计算机毕业设计网咖管理系统Springboot程序
    【算法】求最小子集的和被5整除
    华为OD机试 - 单词接龙 - 数据结构map、list (Java 2023 B卷 100分)
    Git命令总结
    MFC Windows 程序设计[139]之多样的静态框页签(附源码)
    分库分表实现方式Client和Proxy,性能和维护性该怎么选?
    33.友元
    IDEA中DEBUG调试代码F7、F8、F9
  • 原文地址:https://blog.csdn.net/m0_63297646/article/details/134380297