Tomcat currently supports BIO (blocking I/O), NIO (non-blocking I/O), AIO (asynchronous non-blocking IO, NIO upgrade), APR (Apache Portable Runtime) model, this paper mainly introduces NIO model, NIO model is widely used in a variety of distributed, communication, Java system. Such as Dubbo, Jetty, Zookeeper and other framework middleware, use NIO to implement the basic communication components

The disadvantages of BIO

In the traditional BIO model, a thread is created for each request. When a thread requests data from the kernel, it waits until the kernel is ready and returns the data

In Tomcat, Http11Protocol by the implementation of blocking Http protocol request, through the traditional ServerSocket operation, according to the incoming parameters set to listen to the port, if the port is legitimate and not occupied, the service listening success, and then through an infinite loop to listen to the client connection, If no client is connected, the main thread blocks on the Accept operation of the ServerSocket (the BIO is no longer supported in versions 8 and 9 of Tomcat).

In BIO, two blocks occur:

First block connect call: waiting for a connection request from the client. If there is no connection from the client, the server will block and wait

The second block accept call: after the client connects, the server waits for the client to send data. If the client does not send data, the server blocks for the client to send data

In Tomcat, a worker thread pool is maintained to handle socket requests. If there are no idle threads in the worker thread pool, acceptors will block. The server is under a lot of stress

NIO model

NIO model makes up for the deficiency of BIO model. It is based on the selector to detect the ready state of the Socket to notify thread processing, so as to achieve the purpose of non-blocking. Tomcat NIO is based on THE I/O reuse (SELECT /poll/epoll) model, and NIO has the following concepts:

Channel

A Chnnel is a Channel. Network data is read and written through a Channel. The difference between a Channel and a stream is that a stream is unidirectional while a Channel is bidirectional

Selector

The multiplexer Selector constantly polls the channels registered on it, and if a read or write occurs on a Channel, that Channel is ready to be selected by the Selector, The collection of ready channels can be retrieved through the SelectionKey (Channel listening key) for subsequent IO operations. So as long as there’s one thread that’s doing the Selector polling, you can access thousands and thousands of clients

In Tomcat, HTTP/1.1 protocol requests for non-blocking IO are handled by the NioEndpoint

NioEndpoint.bind()

    public void bind(a) throws Exception {
      this.serverSock = ServerSocketChannel.open();
      this.socketProperties.setProperties(this.serverSock.socket());
      InetSocketAddress addr = this.getAddress() ! =null ? new InetSocketAddress(this.getAddress(), this.getPort()) : new InetSocketAddress(this.getPort());
      this.serverSock.socket().bind(addr, this.getAcceptCount());
      this.serverSock.configureBlocking(true);
      if (this.acceptorThreadCount == 0) {
          this.acceptorThreadCount = 1;
      }

      if (this.pollerThreadCount <= 0) {
          this.pollerThreadCount = 1;
      }

      this.setStopLatch(new CountDownLatch(this.pollerThreadCount));
      this.initialiseSsl();
      this.selectorPool.open();
  }
Copy the code

Bind () enables the ServerSocketChannel to bind addresses and ports through the ServerSocketChannel

NioEndpoint.startInternal()

    public void startInternal(a) throws Exception {
        if (!this.running) {
            this.running = true;
            this.paused = false;
            this.processorCache = new SynchronizedStack(128.this.socketProperties.getProcessorCache());
            this.eventCache = new SynchronizedStack(128.this.socketProperties.getEventCache());
            this.nioChannels = new SynchronizedStack(128.this.socketProperties.getBufferPool());
            if (this.getExecutor() == null) {
                this.createExecutor();
            }
			// Control the maximum number of connections
            this.initializeConnectionLatch();
            // Start poller thread
            this.pollers = new NioEndpoint.Poller[this.getPollerThreadCount()];

            for(int i = 0; i < this.pollers.length; ++i) {
                this.pollers[i] = new NioEndpoint.Poller();
                Thread pollerThread = new Thread(this.pollers[i], this.getName() + "-ClientPoller-" + i);
                pollerThread.setPriority(this.threadPriority);
                pollerThread.setDaemon(true);
                pollerThread.start();
            }
			// Start the acceptor thread
            this.startAcceptorThreads(); }}Copy the code

StartInternal () initializes connections and starts worker thread pools poller thread groups and acceptor thread groups. Acceptors listen for Socket connections. After each acceptor is started, the Accept () method of the ServerSocketChannel is looped to receive new connections. And then call the endpoint. SetSocketOptions (socket) processing a new connection, in the endpoint. SetSocketOptions (socket) is through getPoller0 (). The register (channel), Register the current NioChannel with Poller, and this logic is handled in acceptor.run ()

After calling getPoller0().register(channel), the request socket is wrapped as a PollerEvent and added to events. This is done by the Poller thread. Poller’s run() loops through the events() method to handle channal registered to a Selector (each poller turns on a Selector), listening for the channel’s OP_READ event. If the state is readable, place the task into the worker thread pool in processKey (). The whole process is roughly shown below

In the NIO model, no connection has to correspond to a processing thread, the connection is registered with the Selector, and when the Selector listens for a valid request, a corresponding thread is dispatched to handle it. When the connection has no request, there is no worker thread to handle it

Specify the IO model in Tomcat

You can specify the IO protocol for the connector in Tomcat by using the protocol attribute in connector element of server.xml. The default value is HTTP/1.1, indicating the current version of the default protocol. You can change HTTP/1.1 to the IO protocol specified below

Org. Apache. Coyote. Http11. Http11Protocol: BIO

Org. Apache. Coyote. Http11. Http11NioProtocol: NIO

Org. Apache. Coyote. Http11. Http11Nio2Protocol: NIO2

Org. Apache. Coyote. Http11. Http11AprProtocol: APR

conclusion

BIO creates one thread per connection, which is costly in performance and not suitable for high-concurrency scenarios. NIO monitors connection status and notifies thread processing based on multiplexing selector. Only when there is a request on the connection monitored, will a thread be allocated to handle it. NIO uses a small number of threads to manage a large number of connections, optimizing IO read and write, but also increasing CPU calculation, which is suitable for a large number of connection scenarios