网络IO模型BIO vs NIO

Posted 绝世好阿狸

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络IO模型BIO vs NIO相关的知识,希望对你有一定的参考价值。

先回顾下几个概念。

阻塞 vs 非阻塞:指的是调用者线程会不会被阻塞。BIO就是一种阻塞io,如果没有就绪,调用者线程会被阻塞挂起。而NIO是非阻塞的。

同步 vs 异步:指的是调用方式,同步调用可以理解为调用结果需要调用者主动获取,而异步调用则是使用回调/通知机制,将结果回调给调用者,调用者无需关心。

可以看到,二者是两个维度的概念,没有绑定关系。同步调用可以是阻塞的也可以是非阻塞的,比如BIO和NIO。异步调用通常是非阻塞的,比如AIO。

目前现状是BIO和NIO占绝大多数的场景,AIO却用的不是很多。

 

下面以Java程序为例,探讨下BIO和NIO各自的使用及优势。

1.BIO

在BIO下,所有的调用都是阻塞的,包括建连以及读写。

简单的单线程Server

public static void main(String[] args) 
    serverLoop();


private static void serverLoop() 
    byte[] buffer = new byte[4096];
    try 
        ServerSocket serverSocket = new ServerSocket(15001);
        while (true) 
            Socket socket = serverSocket.accept();
            System.out.println("client connect");
            InputStream inputStream = socket.getInputStream();

            int size = inputStream.read(buffer);
            String msg = new String(ArrayUtils.subarray(buffer, 0, size));
            System.out.printf("receive data: %s, size: %d \\n", msg, size);
        
     catch (IOException e) 
        e.printStackTrace();
    

功能很简单,接收客户端的链接,并将消息内容打印出来。

简单的客户端

public static void main(String[] args) 
    for (int i = 0; i < 2; i ++) 
        int finalI = i;
        new Thread(() -> 
            try 
                Socket socket = new Socket("127.0.0.1", 15001);
                OutputStream outputStream = socket.getOutputStream();

                String msg = "hello" + finalI;

                firstReqSleep(finalI);

                outputStream.write(msg.getBytes());
                outputStream.flush();
                outputStream.close();
             catch (IOException e) 
                e.printStackTrace();
            
        ).start();
        try 
            Thread.sleep(100);
         catch (InterruptedException e) 
            e.printStackTrace();
        
    


// 第二个请求不阻塞
private static void firstReqSleep(int i) 
    if (i == 1) 
        return;
    

    try 
        Thread.sleep(5000);
     catch (InterruptedException e) 
        e.printStackTrace();
    

进行了两次tcp数据发送,第一次sleep5秒,第二次直接发送。结果如下:

client connect
receive data: hello0, size: 6 
client connect
receive data: hello1, size: 6 

可以看到,单线程BIO server,只能串行处理请求,如果当前socket比较耗时,后面的socket只能等待,无并发能力。为了解决这个问题,可以采用多线程方式,多线程server如下。

    public static void main(String[] args) 
        serverLoopMulti();
    

    private static void serverLoopMulti() 
        byte[] buffer = new byte[4096];
        try 
            ServerSocket serverSocket = new ServerSocket(15001);
            while (true) 
                Socket socket = serverSocket.accept();
                System.out.println("client connect");
                new Thread(() -> 
                    try 
                        InputStream inputStream = socket.getInputStream();
                        int size = inputStream.read(buffer);
                        String msg = new String(ArrayUtils.subarray(buffer, 0, size));
                        System.out.printf("receive data: %s, size: %d \\n", msg, size);
                     catch (IOException e) 
                        e.printStackTrace();
                    
                ).start();
            
         catch (IOException e) 
            e.printStackTrace();
        
    

client connect
client connect
receive data: hello1, size: 6 
receive data: hello0, size: 6 

可以看到,第一次请求sleep时,第二次请求被优先处理,并不受前面socket的影响。可以通过多线程方式提升BIO server的并发性能。这也是所谓的“request per thread”模型,即一个请求对应一个线程。这种模式的缺点也很明显,当并发数升高时,需要消耗大量的线程资源,线程数太高,也会一定程度上降低系统性能。优点也显而易见,编程简单。

 

2.NIO

因为BIO是阻塞IO,一个线程只能处理一个连接,所以诞生了NIO。NIO需要操作系统的支持,在linux操作系统上,是由select、poll以及epoll三类非阻塞io的系统调用来支持的,其中epoll效率最高。非阻塞IO的核心是多路复用技术,多路复用使得应用程序可以同时处理多个连接,并发性能得到很好的提升。那么多路复用技术相比于之前的多线程BIO模型的优势是什么?首先,二者都能够并发处理多个连接。如果并发连接数不多,二者性能没有太大差别,反而BIO由于编程简单更占优势。如果并发连接数很大,BIO就需要开启对应数量的线程,系统性能将会降低。而NIO的线程数与连接数无关,所以性能不受连接数增加影响。另外,假设并发连接数很高,并且有些连接是空闲的,BIO server下,这些连接会占用线程池资源,如果超过线程池的处理能力,新的请求将会触发拒绝策略,导致无法提供服务。但是NIO server却不受影响,只是select空闲连接时不返回事件,后续新的请求到达时仍然能够被处理。(前提是连接数没有超过操作系统限制)。

总结:

1.并发连接数不大,都可以;

2.并发连接数很高时,即便是空闲连接也会消耗BIO线程资源,而NIO不会;另外随着连接数增加,BIO线程数也随之增加,性能会下降,而NIO的线程数与连接数无关。

 

接下来看一个NIO server的例子

public class Nioserver 

    private static ServerSocketChannel serverSocketChannel;
    private static Selector selector;

    public static void main(String[] args) 
        try 
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(15001));

            //设置为非阻塞
            serverSocketChannel.configureBlocking(false);

            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) 
                selector.select();
                Set selected = selector.selectedKeys();
                Iterator it = selected.iterator();
                while (it.hasNext()) 
                    //分发事件
                    dispatch((SelectionKey) (it.next()));
                    it.remove();
                
            
         catch (IOException e) 
            e.printStackTrace();
        
    

    private static void dispatch(SelectionKey key) 
        if (key.isAcceptable()) 
            try 
                System.out.println("client connect");
                SocketChannel socketChannel = serverSocketChannel.accept();
                socketChannel.configureBlocking(false);
                socketChannel.register(selector, SelectionKey.OP_READ);
             catch (IOException e) 
                e.printStackTrace();
            
         else if (key.isReadable()) 
            SocketChannel channel = (SocketChannel) key.channel();

            ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
            try 
                int size = channel.read(byteBuffer);
                String msg = new String(byteBuffer.array()).trim();
                System.out.printf("receive data: %s, size: %d \\n", msg, size);
                channel.close();
             catch (IOException e) 
                e.printStackTrace();
            
        
    

client connect
client connect
receive data: hello1, size: 6 
receive data: hello0, size: 6

可以看到,单线程的NIO server就能并发处理多个连接。NIO server依赖selector组件,需要将连接注册到selector上,serversocket只能注册accept事件,accept到连接以后,可以在连接上注册读写事件。不同于BIO轮询连接,NIO轮询selector,通过selector取得事件和连接。

 

参考:https://www.jianshu.com/p/bffbc1911053

以上是关于网络IO模型BIO vs NIO的主要内容,如果未能解决你的问题,请参考以下文章

Netty—— 概念剖析(NIO vs BIO)

网络IO模型——简析NIO / BIO / AIO

网络IO模型——简析NIO / BIO / AIO

网络IO模型——简析NIO / BIO / AIO

Netty系列:基础篇 BIO-NIO-AIO

网络通信之 AIO 和 BIO和 NIO