从BIO到NIO

我们知道,Java服务端编程,很重要的一块就是IO,而我们的Java IO,经历了由BIO到NIO再到AIO的过程。
首先,我们来看一下什么是BIO:

1.普通BIO
我们刚开始学Java Socket的时候,可能都写过这么一段代码:

    ServerSocket serverSocket = new ServerSocket(8000);
    while (true){
      Socket socket = serverSocket.accept();
      InputStream is = socket.getInputStream();
      byte [] buff = new byte[1024];
      int len = is.read(buff);
      while (len > 0){
        String msg = new String(buff,0,len);
        System.out.println("收到" + msg);
        len = is.read(buff);
      }
    }

显然,这段代码把对客户端接入后的处理都放在了while循环中,也就是说,这段代码一段时间只能处理一个客户端连接,那么问题来了,我们想一想,能作为服务端的服务器性能应该是不错的,所以这种方式浪费了我们的性能,而且,由于一次处理一个客户端连接,并发量自然而然地上不去,所以,这个方法不会在生产环境中使用。

2.多线程式BIO

为了提高并发量,我们可以将代码改成这个样子:

    ServerSocket serverSocket = new ServerSocket(8000);
    while (true){
      Socket socket = serverSocket.accept();
      new Thread(new Handler(socket)).start();
    }
public class Handler implements Runnable {

  private Socket socket;

  public Handler(Socket socket) {
    this.socket = socket;
  }

  @Override
  public void run() {
    InputStream is = null;
    try {
      is = socket.getInputStream();
      byte[] buff = new byte[1024];
      int len = is.read(buff);
      while (len > 0) {
        String msg = new String(buff, 0, len);
        System.out.println("收到" + msg);
        len = is.read(buff);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      if (is != null) {
        try {
          is.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }
}

但是这样会有一个问题,我们来一个客户端连接不管三七二十一就会new一个线程,这样线程数和客户端连接数是1:1的关系,我们知道,线程的创建和销毁是很耗资源的一件事情,同样地,这样也会增加线程上下文切换的开销。
为了降低线程创建和销毁的开销,我们可以引入线程池(代码先不贴了),但是这样并没有从根本上解决问题。
为了从根本上解决问题,Java引入了NIO:

3.NIO

我们可以这么来理解,我们把web服务器比作银行,客户端发起的连接比作要办理业务的客户,银行处理业务的工作人员比作处理客户端连接的线程,由于银行的资源(假如说只有一台计算机能够处理业务)有限,那么同一时刻只有一个人可以办理业务。
那么我们的BIO就相当于:进来一个客户,就给它分配一个业务员,但是业务员只有得到资源(计算机)才能处理业务,所以客户一进入银行就给他分配业务员是没什么卵用的,还浪费资源(业务员是要领工资的)
而我们的NIO呢,就相当于只有一个业务员,但是有一个排号系统,客户进入银行先在排号系统登记,领一个号码,等到轮到某个客户处理业务了,才给他分配一个业务员,处理他的业务。

public class NIOServer {
  private int port = 8000;
  private Selector selector = null;
  private Charset charset = Charset.forName("UTF-8");
  public NIOServer(int port) throws IOException {
    this.port = port;
    ServerSocketChannel server = ServerSocketChannel.open();
    server.bind(new InetSocketAddress(this.port));
    server.configureBlocking(false);
    selector = Selector.open();
    server.register(selector, SelectionKey.OP_ACCEPT);
  }

  public void listener() throws IOException {
    while (true) {
      int wait = selector.select();
      if (wait == 0) continue;
      Set<SelectionKey> keys = selector.selectedKeys();
      Iterator<SelectionKey> iterator = keys.iterator();
      while (iterator.hasNext()) {
        SelectionKey key = iterator.next();
        iterator.remove();
        process(key);
      }
    }
  }

  public void process(SelectionKey key) throws IOException {
    if (key.isAcceptable()) {
      ServerSocketChannel server = (ServerSocketChannel) key.channel();
      SocketChannel client = server.accept();
      client.configureBlocking(false);
      client.register(selector, SelectionKey.OP_READ);
    }
    if (key.isReadable()) {
      SocketChannel client = (SocketChannel) key.channel();
      ByteBuffer buff = ByteBuffer.allocate(1024);
      StringBuilder content = new StringBuilder();
      try {
        while (client.read(buff) > 0) {
          buff.flip();
          content.append(charset.decode(buff));
        }
        System.out.println(content);
        key.interestOps(SelectionKey.OP_READ);
      } catch (IOException io) {
        key.cancel();
        if (key.channel() != null) {
          key.channel().close();
        }
      }
    }
  }

  public static void main(String[] args) throws IOException {
    new NIOServer(8000).listener();
  }
}

NIO呢,就是我们来一个客户端连接,先不处理,先在Selector上注册,然后不断地轮询注册在Selector上面的channel,当准备好读或者写的时候,再对客户端连接进行操作,这样呢,就大大节省了服务端的资源,因此十分适合于大量长连接的场景。

猜你喜欢

转载自blog.csdn.net/qq_37043780/article/details/82740832