nio

Posted on By xqw

  • 区分

    按照《Unix网络编程》的划分,IO模型可以分为:阻塞IO、非阻塞IO、IO复用、信号驱动IO和异步IO,
    按照POSIX标准来划分只分为两类:同步IO和异步IO。
    如何区分呢?
    首先一个IO操作(read/write系统调用)其实分成了两个步骤:1)发起IO请求和2)实际的IO读写(内核态与用户态的数据拷贝)
    阻塞IO和非阻塞IO的区别在于第一步,发起IO请求的进程是否会被阻塞,
    如果阻塞直到IO操作完成才返回,那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。
    同步IO和异步IO的区别就在于第二步,实际的IO读写(内核态与用户态的数据拷贝)是否需要进程参与,
    如果需要进程参与则是同步IO,如果不需要进程参与就是异步IO。
    如果实际的IO读写需要请求进程参与,那么就是同步IO。因此阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步IO
      
    在编程上,这种非阻塞IO一般都采用IO状态事件+回调方法的方式来处理IO操作。
    如果是同步IO,则状态事件为读写就绪。此时的数据仍在内核态中,但是已经准备就绪,可以进行IO读写操作。
    如果是异步IO,则状态事件为读写完成。此时的数据已经存在于应用进程的地址空间(用户态)中。以上全是和IO相关的同步异步。
      
    另外在编程的方法调用上也存在同步调用和异步调用的说法。
    就拿RPC来说吧:如果同步调用,则调用的结果会在本次调用后返回。
    如果异步调用,则调用的结果不会直接返回。会返回一个Future或者Promise对象来供调用方主动/被动的获取本次调用的结果。
    

关于同步,异步,阻塞,非阻塞,IOCP/epoll,select/poll,AIO ,NIO ,BIO的总结 多路服用

  • 网络IO的需求
    开启一个socket,接收连接,读数据、处理数据、写数据
    BIO(阻塞):新的连接分配一个线程,等待读数据,有了之后处理数据,再写数据
    NIO(非阻塞):将连接放到一起,事件驱动,select/poll事件,将读数据、处理数据、写数据分开
    Reactor模型终极模式,主线程select事件,读、写IO交给一个线程池,业务处理交给另一个线程池
    AIO(异步IO):主动select/poll事件是同步的,异步则通过epoll注册到操作系统,等待通知

  • BIO的问题?

  • NIO解决的问题?
    同步非阻塞IO,不用一直等待客户端IO输入,读写还是同步阻塞的

  • IO多路复用是复用什么
    一般指一个线程或少量线程处理多个TCP连接(或Chanel),复用一个线程或少量线程 也就是reactor线程模型

  • netty线程模型
    通常单reactor多worker

  • select/poll,epoll区别
    poll遍历数组,判断状态 epoll在操作系统实现时不需要轮询,主动通知

BIO

服务器端启动一个SeverSocket

客户端启动Socket对服务器端发起通信,默认情况下服务器端需为每个客户端创建一个线程与之通讯

客户端发起请求后,先咨询服务器端是否有线程响应,如果没有则会等待或被拒绝

如果有线程响应,客户端线程会等待请求结束后,再继续执行

//BIO-服务器端
public class BIOSever {
    public static void main(String[] args) throws IOException {
        //在BIO中,可以使用线程池进行优化
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        ServerSocket serverSocket = new ServerSocket(6666);
        System.out.println("服务器已启动");

        while (true){
            System.out.println("等待客户端连接.....(阻塞中)");
            Socket socket = serverSocket.accept();
            System.out.println("客户端连接");
            cachedThreadPool.execute(new Runnable() {
                public void run() {
                    handler(socket);
                }
            });
        }
    }

    //从客服端socket读取数据
    public static void handler(Socket socket){
        try{
            InputStream inputStream = socket.getInputStream();
            byte[] b = new byte[1024];
            while (true){
                System.out.println("等待客户端输入.....(阻塞中)");
                int read = inputStream.read(b);
                if (read != -1){
                    System.out.println(new String(b, 0, read));
                }else {
                    break;
                }
            }
            inputStream.close();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

BIO问题分析
从上面代码中可以看出BIO编程的两个问题:

    服务器端在监听客户端连接时(serverSocket.accept()),服务器端处于阻塞状态,不能处理其他事务
    
    服务器端需要为每个客户端建立一个线程,虽然可以用线程池来优化,但在并发较大时,线程开销依旧很大
    
    当连接的客户端没有发送数据时,服务器端会阻塞在read操作上,等待客户端输入,造成线程资源浪费