Netty(2) -- 四种IO模型的比较

1、BIO

  传统的网络编程是基于BIO模型的,Server服务端提供IP地址与端口号,客户端向服务端发起请求,经过三次握手建立连接,如果连接成功,双方就可以通过套接字(Socket)进行通信了。
  我们使用传统网络编程进行通信时,大致思路是服务端开启线程,占用IP和端口号,开启accept()方法,等待客户端的连接。客户端连接成功后,便开启网络输出流输出数据。服务端亦是如此,双方均可使用输入输出流进行通信。这种做法无疑是简单明了的,但却存在极大地问题。第一、传统网络编程的一些API均为造成线程阻塞,如accept(),在服务端未接收到客户端的请求连接时,此时线程一直阻塞着;还有输入输出流的读写方法,若为接受到停止信号,也会一直阻塞线程。着会大大地降低网络通信的新能。第二、服务端需要对每一个客户端均创建一个线程,着便极大地限制了传统网络编程的发展。如今的互联网讲究的是高并发高性能,若是上万个客户端同时访问,服务端便要创建上万个线程,这显然是不允许的。

2、伪异步IO

  为了解决BIO面临的一个客户端就需要一个线程的问题,也就是上述问题的第二点,后来有人对它的线程模型进行了优化,即使用线程池和消息队列对客户端请求进行管理。后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N。通过线程池可以灵活地调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。
  当有新的客户端接入时,将客户端的Socket 封装成一个Task(Task实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK的线程池维护一个消息队列和N个活跃线程,对消息队列中的任务进行处理.由于线程池可以设置消息队列的大小和最大线程数。因此,它的费源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机。
在这里插入图片描述
  然而,通过上面对BIO的讲述,知道伪异步IO仅仅解决了其中一个问题,还有另外一个问题没被解决,那就是阻塞问题。当一个请求被阻塞时,排在队列后面的请求将被永远阻塞!这在网络速度较慢或者传输内容太多时,是常有发生的。

3、NIO

  与 Socket类和ServerSocket类相对应,NIO也提供了 SocketChannel 和 ServerSocketChannel两种不同的套接字通道实现.这两种新増的通道都支持阻塞和非阻塞两种模式。阻塞模式使用非常简单,但是性能和可靠性都不好。非阻塞模式则正好相反。一般来说,低负载、低并发的应用程序可以选择同步阻塞IO以降低編程复杂度;对于高负栽、高并发的网络应用,需要使用NIO的非阻塞模式进行开发。
  NIO较BIO的进步之处的功能在于它新定义的三个组件:Buffer、Channel、Selector

3.1、Buffer

  Buffer是一个对象,它包含一些要写入或者要读出的数据.在NIO类库中加入Buffer对象,体现了NIO与BIO的一个重要区别。在面向流的I/O中,可以将数据直接写入或者将数据直接读到Stream对象中。在NIO中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的:在写入数据时,写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。
  Buffer实际上是一个特殊的数组,它还内置了三个属性,所以可以跟踪和记录缓冲区的状态变化,进而实现更加复杂的操作。其中,最重要的就是容量(capacity)、界限(limit)、位置(position)
容量:缓冲区能够容纳的数据元素的最大数量,这一个容量在缓冲区被初始化时确定;
界限:缓冲区的第一个不能被读或写的元素;
位置:下一个要被读或写的元素的索引,位置会自动由相应的 get( )和 put( )函数更新;
标记:可直接将position定位到mark。
在这里插入图片描述

3.2、Channel

  Channel是一个通道,它就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流只是在一个方向上移动(一个流必须是Inputstream或者Outputstream的子类),而通道可以用于读、写或者二者同时进行。因为Channel是全双工的,所以它可以比流更好地映射底层操作系统的API。特别是在UNIX网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作。

3.3、Selector

  Selector(选择器)能够检测多个注册的通道上是否有事件发生。如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并 且避免了多线程之间的上下文切换导致的开销。
  简单来说,就是一个线程拥有一个Selector,而一个Selector管理多个读写IO,这样即使一个IO没有进行读写操作,也不会造成线程阻塞,导致性能降低。

4、AIO

  NIO 2.0的异步套接字通道是真正的异步非阻塞I/O,对应于UNIX网络編程中的事件驱动I/O。它不需要通过多路复用器对注册的通道进行轮询操作即可实现异步读写,从而简化了NIO的编程模型。

4.1、AIO的简单案例

服务端 – AsyncTimeServerHandler 类:

package aio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.CountDownLatch;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:09
 */
public class AsyncTimeServerHandler implements Runnable {
    private int port;
    CountDownLatch latch;
    AsynchronousServerSocketChannel asynchronousServerSocketChannel;

    public AsyncTimeServerHandler(int port) {
        this.port = port;
        try {
            asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
            asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
            System.out.println("The time server is start in port:" + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        latch = new CountDownLatch(1);
        doAccept();
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void doAccept() {
        asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
    }
}

服务端 – AcceptCompletionHandler 类

package aio;

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:10
 */
public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel,AsyncTimeServerHandler> {
    @Override
    public void completed(AsynchronousSocketChannel result, AsyncTimeServerHandler attachment) {
        attachment.asynchronousServerSocketChannel.accept(attachment, this);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        result.read(buffer, buffer, new ReadCompletionHandler(result));
    }

    @Override
    public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
        exc.printStackTrace();
        attachment.latch.countDown();
    }
}

服务端 – ReadCompletionHandler 类

package aio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.Date;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 14:26
 */
public class ReadCompletionHandler implements CompletionHandler<Integer,ByteBuffer> {
    private AsynchronousSocketChannel channel;

    public ReadCompletionHandler(AsynchronousSocketChannel channel) {
        if (this.channel == null) {
            this.channel = channel;
        }
    }

    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        attachment.flip();
        byte[] body = new byte[attachment.remaining()];
        attachment.get(body);
        try {
            String req = new String(body, "UTF-8");
            System.out.println("The time server receive order:" + req);
            String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
            doWrite(currentTime);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    private void doWrite(String currentTime) {
        if (currentTime != null && currentTime.trim().length() > 0) {
            byte[] bytes = (currentTime).getBytes();
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
            writeBuffer.put(bytes);
            writeBuffer.flip();

            channel.write(writeBuffer, writeBuffer,new CompletionHandler<Integer, ByteBuffer>() {
                @Override
                public void completed(Integer result, ByteBuffer buffer) {
                    //如果没有发送完成,继续发送
                    if (buffer.hasRemaining()) {
                        channel.write(buffer, buffer, this);
                    }
                }

                @Override
                public void failed(Throwable exc, ByteBuffer attachment) {
                    try {
                        channel.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            this.channel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务端 – TimeServer 类:

package aio;

import java.io.IOException;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:16
 */
public class TimeServer {
    public static void main(String[] args) throws IOException {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                //
            }
        }

        AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port);
        new Thread(timeServer,"AIO-AsyncTimeServerHandler-001").start();
    }
}

客户端 – AsyncTimeClientHandler 类

package aio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:13
 */
public class AsyncTimeClientHandler implements CompletionHandler<Void,AsyncTimeClientHandler>,Runnable {
    private AsynchronousSocketChannel client;
    private String host;
    private int port;
    private CountDownLatch latch;

    public AsyncTimeClientHandler(String host, int port) {
        this.host = host;
        this.port = port;
        try {
            client = AsynchronousSocketChannel.open();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        latch = new CountDownLatch(1);
        client.connect(new InetSocketAddress(host, port), this, this);
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void completed(Void result, AsyncTimeClientHandler attachment) {
        byte[] req = "QUERY TIME ORDER".getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
        writeBuffer.put(req);
        writeBuffer.flip();
        client.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer buffer) {
                if (buffer.hasRemaining()) {
                    client.write(buffer, buffer, this);
                }else {
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    client.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {
                        @Override
                        public void completed(Integer result, ByteBuffer buffer) {
                            buffer.flip();
                            byte[] bytes = new byte[buffer.remaining()];
                            buffer.get(bytes);
                            String body;
                            try {
                                body = new String(bytes, "UTF-8");
                                System.out.println("Now is:" + body);
                                latch.countDown();
                            } catch (UnsupportedEncodingException e) {
                                e.printStackTrace();
                            }
                        }

                        @Override
                        public void failed(Throwable exc, ByteBuffer attachment) {
                            try {
                                client.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                try {
                    client.close();
                    latch.countDown();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    @Override
    public void failed(Throwable exc, AsyncTimeClientHandler attachment) {
        try {
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端 – TimeClient 类

package aio;

/**
 * @author RuiMing Lin
 * @date 2020-03-24 16:13
 */
public class TimeClient {
    public static void main(String[] args) {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                //
            }
        }

        new Thread(new AsyncTimeClientHandler("127.0.0.1",port),"AIO-AsyncTimeClientHandler-001").start();
    }
}

5、四种IO的比较

BIO 异步IO NIO AIO
客户端:IO线程 1:1 M:N(其中M >> N) M:1 M:0
是否阻塞 阻塞 阻塞 非阻塞 非阻塞
是否同步 同步 同步 同步 异步
API使用难度 简单 简单 非常复杂 复杂
调试难度 简单 简单 复杂 复杂
可靠性 非常差
吞吐量

此博客为读书笔记,参考书籍:《Netty权威指南》

有错误的地方敬请指出,欢迎大家评论区或者私信交流!每日持续更新Java、Python、大数据技术,请大家多多关注!

发布了64 篇原创文章 · 获赞 148 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Orange_minger/article/details/105072710