NIO学习笔记(IO、AIO、BIO)--更新中

IO

I/O ? 或者输入/输出 ? 指的是计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。它对于任何计算机系统都非常关键,因而所有 I/O 的主体实际上是内置在操作系统中的。单独的程序一般是让系统为它们完成大部分的工作。

在 Java 编程中,直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被视为单个的字节的移动,通过一个称为 Stream 的对象一次移动一个字节。流 I/O 用于与外部世界接触。它也在内部使用,用于将对象转换为字节,然后再转换回对象。

传统流IO的好处是使用简单,将底层的机制都抽象成流,但缺点就是性能不足。而且IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。

传统BIO(blocking IO)

阻塞:accept方法,除非有socket返回,读写不能同时进行
当对Socket的输入流进行读取操作的时候,它会一直阻塞下去。
一个场景,由于网络延迟,导致数据发送缓慢。而由于使用的是阻塞IO,那么read方法一直处于阻塞状态,要等到数据传送完成才结束(返回-1)。那么这种场景下,在高并发。直接导致线程暴增、服务器宕机。
在这里插入图片描述

在bio基础上,把传统多线程改为使用一定muxnum的线程池,形成伪异步IO模型,
实现一个或多个线程处理n个客户端,但是底层还是同步阻塞IO

在这里插入图片描述

//线程池 懒汉式的单例
    private static ExecutorService executorService = Executors.newFixedThreadPool(60);

CachedThreadPool线程池

根本上无法解决高并发:因为限制了线程数量,如果发生大量并发请求,超过最大数量的线程就只能等待,直到线程池中的有空闲的线程可以被复用。

bio问题:

  • 1.没有一个socket,服务端都会开启一个线程处理
  • 2.每一个处理操作完后,线程都会被销毁

NIO(new io,Non-blocking io)

netty框架中实现了NIO的封装

同步非阻塞IO

缓冲区(Buffer):存储写入或者读出的数据

官方写到:
a Thread can ask channel to read data into a buffer ,While the channel reads data into buffer,the Thread can do else things.
所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

通道(Channel):同时读写 双向的

Channel主要分两大类:

- SelectableChannel:用户网络读写
- FileChannel:用于文件操作
- ServerSocketChannel服务器tcp数据传输
- SocketChannel客户端tcp数据传输

多路复用器(Selector):提供选择已经就绪的任务的能力

官方写道:
A selector can use a thread to monitor multiple channels for even’s.
Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

服务端:

  • 1.打开ServerSocketChannel
  • 2.设置为非阻塞
  • 3.监听客户端端口,绑定监听端口
  • 4.创建selector 把channel注册到selector上,监听我感兴趣的accept请求
  • 5.开始listen:
    (1)轮询selector访问准备就绪的SelectionKey
while(true){
            selector.select();
            //selector轮询准备就绪的key
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                handlekey(selectionKey);
            }
        }
 (2)监听到新的客户端接入,处理新的接入请求,将读写数据放在Buffer中
if(selectionKey.isAcceptable()){
            serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
            client = serverSocketChannel.accept();
            client.configureBlocking(false);
            client.register(selector,SelectionKey.OP_READ);
        }else if(selectionKey.isReadable()){
            client = (SocketChannel) selectionKey.channel();
            receiveBuffer.clear();
            -------------------------
        }else if(selectionKey.isWritable()){
            sendBuffer.clear();
            //服务端向客户端发送数据
            client = (SocketChannel) selectionKey.channel();
            sendText = "message from server:"+flag++;
            sendBuffer.put(sendText.getBytes());
            sendBuffer.flip();
            client.write(sendBuffer);
            client.register(selector,SelectionKey.OP_READ);
        }

客户端:

  • 1.打开socket通道
  • 2.设置非阻塞
  • 3.创建选择器selector 注册通道 SelectionKey.connect
  • 4.socket连接服务器ip和端口
  • 5.轮询selector已就绪的通道
  • 6.处理通道请求的数据
while (true) {
            selector.select();
            selectionKeys = selector.selectedKeys();
            iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                selectionKey = iterator.next();
                if (selectionKey.isConnectable()) {
                    System.out.println("client connect");
                    client = (SocketChannel) selectionKey.channel();
                    // 判断此通道上是否正在进行连接操作。
                    // 完成套接字通道的连接过程。
                    if (client.isConnectionPending()) {
                        client.finishConnect();
                        System.out.println("完成连接!");
                        sendBuffer.clear();
                        sendBuffer.put("Hello,Server".getBytes());
                        sendBuffer.flip();
                        client.write(sendBuffer);
                    }
                    client.register(selector, SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    client = (SocketChannel) selectionKey.channel();
                    //将缓冲区清空以备下次读取
                    receiveBuffer.clear();
                    //读取服务器发送来的数据到缓冲区中
                    count = client.read(receiveBuffer);
                    if (count > 0) {
                        receiveText = new String(receiveBuffer.array(), 0, count);
                        System.out.println("客户端接受服务器端数据--:" + receiveText);
                        client.register(selector, SelectionKey.OP_WRITE);
                    }

                } else if (selectionKey.isWritable()) {
                    sendBuffer.clear();
                    client = (SocketChannel) selectionKey.channel();
                    sendText = "message from client--" + (flag++);
                    sendBuffer.put(sendText.getBytes());
                    //将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
                    sendBuffer.flip();
                    client.write(sendBuffer);
                    System.out.println("客户端向服务器端发送数据--:" + sendText);
                    client.register(selector, SelectionKey.OP_READ);
                }
            }
            selectionKeys.clear();
        }

在这里插入图片描述

AIO、BIO、NIO理解

  • AIO的做法是,每个水壶上装一个开关,当水开了以后会提醒对应的线程去处理。

  • NIO的做法是,叫一个线程不停的循环观察每一个水壶,根据每个水壶当前的状态去处理。

  • BIO的做法是,叫一个线程停留在一个水壶那,直到这个水壶烧开,才去处理下一个水壶。

  • 本文作者: dzou | 微信:17856530567
  • 本文链接: http://www.dzou.top/post/e571ec33.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
  • 并保留本声明和上方二维码。感谢您的阅读和支持!