BIO NIO AIO Netty 同步 异步 阻塞 非阻塞

1. BIO

1.1 bio的服务端:

public class Server {
    
    
    public static void main(String[] args) throws IOException {
    
    
        ServerSocket ss = new ServerSocket();
        ss.bind(new InetSocketAddress("127.0.0.1", 8888));
        while (true) {
    
    
            Socket s = ss.accept(); // 阻塞
            new Thread(() -> handle(s)).start();
        }
    }

    static void handle(Socket s) {
    
    
        try {
    
    
            byte[] bytes = new byte[1024];
            int len = s.getInputStream().read(bytes); // 阻塞
            System.out.println(new String(bytes, 0, len));
            s.getOutputStream().write(bytes, 0, len); // 阻塞
            s.getOutputStream().flush();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

}

1.2 bio的客户端:

public class Client {
    
    
    public static void main(String[] args) throws IOException {
    
    
        for (int i = 0; i < 500; i++) {
    
    
            sendRequest("这是第【" + i + "】个请求");
        }
    }

    public static void sendRequest(String str) throws IOException {
    
    
        Socket s = new Socket("127.0.0.1", 8888);
        s.getOutputStream().write(str.getBytes());
        s.getOutputStream().flush();
        System.out.println("等待server");
        byte[] bytes = new byte[1024];
        int len = s.getInputStream().read(bytes);
        System.out.println(new String(bytes, 0, len));
        s.close();
    }
}

1.3 bio总结:

一个客户端连接到服务器端,服务器端就要新建立一个线程来处理数据的输入和输出

  • 客户端连上来可能会阻塞;服务器端和客户端的读写过程也可能阻塞。
    在这里插入图片描述

2. 单线程NIO

在这里插入图片描述

2.1 服务器端代码:

public class Server {
    
    
    public static void main(String[] args) throws IOException {
    
    
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8888));
        ssc.configureBlocking(false);
        // 已经绑定了本机的 8888端口
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
    
    
            selector.select(); // 阻塞,如果有注册事情发生了,往下走。
            Set<SelectionKey> keys = selector.selectedKeys();//获取所有的 发生的事情的key
            Iterator<SelectionKey> it = keys.iterator();
            while (it.hasNext()) {
    
    
                SelectionKey key = it.next();
                it.remove();
                handle(key);
            }
        }
    }

    private static void handle(SelectionKey key) {
    
    
        if (key.isAcceptable()) {
    
     // 如果是客户端 想连接。
            try {
    
    
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); // 获取这个key对应的channel
                SocketChannel sc = ssc.accept(); // accept,接收连接。
                sc.configureBlocking(false); // 设置非阻塞
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        } else if (key.isReadable()) {
    
     // 如果客户端想向你发送数据。你需要读数据。
            SocketChannel sc = null;
            try {
    
    
                sc = (SocketChannel) key.channel(); // 获取这个key对应的channel
                ByteBuffer buffer = ByteBuffer.allocate(512);
                buffer.clear();
                int len = sc.read(buffer);//读。
                if (len != -1) {
    
    
                    System.out.println(new String(buffer.array(), 0, len));
                }
                ByteBuffer bufferToWrite = ByteBuffer.wrap("hello client".getBytes());
                sc.write(bufferToWrite); // 写
            } catch (IOException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                if (sc != null) {
    
    
                    try {
    
    
                        sc.close(); //关闭channel
                    } catch (IOException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

2.2 总结单线程NIO

selector像一个大管家,设置一些他感兴趣的事情:客户端连接、读数据、写数据。进行轮询,如果有感兴趣的事情发生了,获取这些事情的key,挨个获取channel亲自去处理。然后再次看看有没有感兴趣的事情发生…

  • 跟BIO有什么差别呢?BIO是一个客户端连接,就创建一个线程去处理,然而读数据,写数据是阻塞的,所以大部分时间,各个线程是没事干的状态。然而用NIO,只用一个线程,需要读或者写数据的时候,这个线程采取处理。就相当于,一个人干了很多人的活。【避免了很多人在那里傻等着活的情况
    在这里插入图片描述

3. 多线程NIO

3.1 概念

  • 单线程NIO的大管家必须事事躬亲,但是如果与一个客户的读写时间很长的话,其他客户端的操作就在等待。
  • 所以引出了NIO的多线程模型。设置了一个线程池,一个大管家,领着一群工人,大管家轮询,看看有没有事情需要处理,如果有的话,就交给闲着的工人。如果所有工人都在忙,可以等待或者创建一个新的工人。
  • 这个模型和BIO不一样。BIO是一个工人服务一个客户。而这里,工人都是螺丝钉,哪里需要哪里搬。
    在这里插入图片描述

4. AIO

4.1 代码

  • aio的accept方法不是阻塞的了,accept代码运行了,就往下走,也不轮询了。
  • 和NIO的区别在于,需不需要轮询。NIO需要轮询,有感兴趣的事件,就一起处理,然后再次轮询… 而AIO直接让OS持有处理代码的引用,有事件发生,OS直接去运行那段代码。
  • accept方法==将一段代码给OS,告诉他,如果有客户端要连,你就运行这段代码就行。==这是观察者的设计模式/回调函数/钩子函数。
  • OS有了这段方法的引用,当事件发生,回调一下就行了。不用大管家去轮询了。
    在这里插入图片描述

4.2 AIO理解

我有一个主线程,然后运行代码。给OS勾上一段代码,如果有客户端需要运行,就运行那段代码。然后再将一段代码勾到建立的通道上,如果有数据读写,就运行第二个勾上的程序。主程序接着往下运行,该干嘛干嘛去。
在这里插入图片描述

4.3 NIO AIO Netty 三者的关系

  • netty封装了NIO。
  • java运行在Linux上时,NIO和AIO的操作底层都用的是epoll(轮询模型),所以直接用NIO就行,AIO多封装了一下,结果到底层都一样
  • 而Netty就是封装了NIO,因为NIO的Api不好用。
  • Netty的API类似AIO的API
  • windows上面NIO和AIO的底层实现不同。AIO底层用的是事件模型。
  • 所以在windows上的AIO效率最高。但是服务器一般都是linux,所以Netty封装了NIO。

5. Netty

在这里插入图片描述

6. 同步 异步 阻塞 非阻塞

  • 同步和异步是消息通信机制中的概念。一直需要你处理,就是同步。否则就是异步。
  • 阻塞和非阻塞是等待消息时的状态:需不需要傻等着。
  • BIO是同步阻塞
  • NIO是同步非阻塞
  • AIO是异步非阻塞(直接给OS钩子函数,不用再关心了,该干啥干啥)

7. 参考

  • 马士兵老师的公开课
  • https://www.bilibili.com/video/BV1i4411p7kk?t=8521

猜你喜欢

转载自blog.csdn.net/Mason97/article/details/107648993