BIO application in tomcat - JIoEndpoint

1. BIO basics

    BIO is blocking I/O, which is the most basic IO method provided by Java. In network communication, it is necessary to establish a two-way link between the client and the server through Socket to achieve communication. The main steps are as follows:

    1. The server monitors whether there is a connection request on a port.

    2. The client sends a link request to the server.

    3. The server returns an Accept message to the client, and the connection is successful at this time.

    4. The client and server communicate with each other through Send(), Write() and other methods.

    5. Close the link.

 

    A mind map of BIO (image source: https://my.oschina.net/langxSpirit/blog/830620 )



 

     Java provides two classes Socket and ServerSocket respectively, which are used to represent the client and server of the bidirectional link. Based on these two classes, a simple network communication example is as follows:

    1. Client

package com.wang.test.net.io.bio;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * Client
 */
public class Client {

    public static void main(String[] args) throws Exception {
        try (
            //Send a request to port 8080 of the machine
            Socket socket = new Socket("127.0.0.1", 8080);
            //Construct a BufferedReader object from standard input
            BufferedReader clientInput = new BufferedReader(new InputStreamReader(System.in));
            //Get the output stream through Socket, construct the PrintWriter object
            PrintWriter writer = new PrintWriter(socket.getOutputStream());
            //Get the input stream through Socket and construct a BufferedReader object
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        ){
            //read input information
            String input = clientInput.readLine();
            while ( !input.equals("exit") ){
                //Send the input information to the server
                writer.print(input);
                //Refresh the output stream so that the server can receive the request information immediately
                writer.flush();
                //Read the information returned by the server
                System.out.println("The corresponding server side is: " + reader.readLine());
                //read the next input
                input = clientInput.readLine();
            }
        } catch ( Exception e ) {
            System.out.println(e);
        }
    }
}

     2. Server

package com.wang.test.net.io.bio;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Service-Terminal
 */
public class Server {

    public static void main(String[] args) {
        try(
            //Create ServerSocket listening port 8080
            ServerSocket server = new ServerSocket(8080);
            // wait for client request
            Socket socket = server.accept();
            //Construct a BufferedReader object from standard input
            BufferedReader serverInput = new BufferedReader(new InputStreamReader(System.in));
            //Get the output stream through the Socket object, BufferedReader
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //Get the output stream through the Socket object and construct the PrintWrite object
            PrintWriter writer = new PrintWriter(socket.getOutputStream());
        ){
            //read client request
            System.out.println("Client request:" + reader.readLine());
            // input server corresponding
            String input = serverInput.readLine();
            //If the input is "exit", exit
            while (!input.equals("exit")){
                writer.print(input);
                // output the string to the client
                writer.flush();
                //read client request
                System.out.println("Client request:" + reader.readLine());
                // input server corresponding
                input = serverInput.readLine();
            }
        } catch ( Exception e ){
            System.out.println(e);
        }
    }
}

 

2. JIoEndpoint

    In the implementation of Tomcat server, Tomcat's IO monitoring is completed by Endpoint. Specifically, BIO is JIoEndpoint. The startup process of JIoEndpoint is as follows:

    1. Create a ServerSocket instance according to the IP address (in the case of multiple IPs) and port; part of the code of the bind() method of the JIoEndpoint class

        if (serverSocket == null) { //Create a ServerSocket instance based on the IP address (in the case of multiple IPs) and port
            try {
                if (getAddress() == null) {
                    serverSocket = serverSocketFactory.createSocket(getPort(),
                            getBacklog ());
                } else {
                    serverSocket = serverSocketFactory.createSocket(getPort(),
                            getBacklog(), getAddress());
                }
            } catch (BindException orig) {
                String msg;
                if (getAddress() == null)
                    msg = orig.getMessage() + " <null>:" + getPort();
                else
                    msg = orig.getMessage() + " " +
                            getAddress().toString() + ":" + getPort();
                BindException be = new BindException(msg);
                be.initCause(orig);
                throw be;
            }
        }

     2. If the Connector is not configured with a shared thread pool, create a request processing thread pool; part of the code of the startInternal() method of the JIoEndpoint class

            // Create worker collection
            if (getExecutor() == null) { //If the Connector is not configured with a shared thread pool
                createExecutor(); //Create a request processing thread pool
            }

     The createExecutor() method is implemented in the JIoEndpoint parent abstract class

    public void createExecutor() {
        internalExecutor = true;
        TaskQueue taskqueue = new TaskQueue();
        TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
        executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
        taskqueue.setParent( (ThreadPoolExecutor) executor);
    }

     3. Create and start the Accept thread according to the number of AcceptThreadCount configuration, part of the code of the startInternal() method of the JIoEndpoint class

startAcceptorThreads(); //According to the number of AcceptorThreadCount configuration, create and start Acceptor threads

     The startAcceptThread method is implemented in the parent abstract class:

/**
     * Create and start Acceptor threads according to the number configured by AcceptorThreadCount
     */
    protected final void startAcceptorThreads() {
        int count = getAcceptorThreadCount();
        acceptors = new Acceptor[count];

        for (int i = 0; i < count; i++) {
            acceptors[i] = createAcceptor();
            String threadName = getName() + "-Acceptor-" + i;
            acceptors[i].setThreadName(threadName);
            Thread t = new Thread(acceptors[i], threadName); //The thread is started separately and is not placed in the thread pool, so it will not affect concurrent processing of requests
            t.setPriority(getAcceptorThreadPriority());
            t.setDaemon(getDaemon());
            t.start();
        }
    }

     In this method, these threads are started independently, so it does not affect the number of concurrent requests. createAcceptor obtains the inner class Accept in the JIoEndpoint class, which inherits the inner class Accept in the JIoEndpoint parent abstract class. Acceptor implements the Runnable interface and is responsible for polling to receive client requests. Inner class Acceptor in JIoEndpoint class

/**
     * Responsible for polling to receive client requests, and also to detect EndPoint status and the maximum number of connections
     * The background thread that listens for incoming TCP/IP connections and
     * hands them off to an appropriate processor.
     */
    protected class Acceptor extends AbstractEndpoint.Acceptor {

        @Override
        public void run() {

            int errorDelay = 0;

            // Loop until we receive a shutdown command
            while (running) {

                // Loop if endpoint is paused
                while (paused && running) {
                    state = AcceptorState.PAUSED;
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        // Ignore
                    }
                }

                if (!running) {
                    break;
                }
                state = AcceptorState.RUNNING;

                try {
                    //if we have reached max connections, wait
                    countUpOrAwaitConnection();

                    Socket socket = null;
                    try {
                        // Accept the next incoming connection from the server
                        // socket
                        socket = serverSocketFactory.acceptSocket(serverSocket);	
                    } catch (IOException ioe) {
                        countDownConnection();
                        // Introduce delay if necessary
                        errorDelay = handleExceptionWithDelay(errorDelay);
                        // re-throw
                        throw ioe;
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // Configure the socket
                    if (running && !paused && setSocketOptions(socket)) { //When a client request is received
                        // Hand this socket off to an appropriate processor
                        if (!processSocket(socket)) {	//
                            countDownConnection();
                            // Close socket right away
                            closeSocket(socket);
                        }
                    } else {
                        countDownConnection();
                        // Close socket right away
                        closeSocket(socket);
                    }
                } catch (IOException x) {
                    if (running) {
                        log.error(sm.getString("endpoint.accept.fail"), x);
                    }
                } catch (NullPointerException npe) {
                    if (running) {
                        log.error(sm.getString("endpoint.accept.fail"), npe);
                    }
                } catch (Throwable t) {
                    ExceptionUtils.handleThrowable(t);
                    log.error(sm.getString("endpoint.accept.fail"), t);
                }
            }
            state = AcceptorState.ENDED;
        }
    }

    4. After receiving the client request, create a SocketProcessor object and submit it to the thread pool for processing.

    5. SocketProcessor does not directly process Socket, but passes it to a specific protocol processing class. The HTTP protocol in BIO mode uses HTTP11Processor.

    Inner class SocketProcessor in JIoEndpoint class

/**
     * This class is the equivalent of the Worker, but will simply use in an
     * external Executor thread pool.
     * SocketProcessor does not directly process Socket, but passes it to a specific protocol processing class, such as Http11Processor for processing HTTP protocol in BIO mode
     */
    protected class SocketProcessor implements Runnable {

        protected SocketWrapper<Socket> socket = null;
        protected SocketStatus status = null;

        public SocketProcessor(SocketWrapper<Socket> socket) {
            if (socket==null) throw new NullPointerException();
            this.socket = socket;
        }

        public SocketProcessor(SocketWrapper<Socket> socket, SocketStatus status) {
            this(socket);
            this.status = status;
        }

        @Override
        public void run() {
            boolean launch = false;
            synchronized (socket) {
                try {
                    SocketState state = SocketState.OPEN;
                    handler.beforeHandshake(socket);
                    try {
                        // SSL handshake
                        serverSocketFactory.handshake(socket.getSocket());
                    } catch (Throwable t) {
                        ExceptionUtils.handleThrowable(t);
                        if (log.isDebugEnabled()) {
                            log.debug(sm.getString("endpoint.err.handshake"), t);
                        }
                        // Tell to close the socket
                        state = SocketState.CLOSED;
                    }

                    if ((state != SocketState.CLOSED)) {
                        if (status == null) {
                            state = handler.process(socket, SocketStatus.OPEN_READ);
                        } else {
                            state = handler.process(socket,status);
                        }
                    }
                    if (state == SocketState.CLOSED) {
                        // Close socket
                        if (log.isTraceEnabled()) {
                            log.trace("Closing socket:"+socket);
                        }
                        countDownConnection();
                        try {
                            socket.getSocket().close();
                        } catch (IOException e) {
                            // Ignore
                        }
                    } else if (state == SocketState.OPEN ||
                            state == SocketState.UPGRADING  ||
                            state == SocketState.UPGRADED){
                        socket.setKeptAlive(true);
                        socket.access();
                        launch = true;
                    } else if (state == SocketState.LONG) {
                        socket.access();
                        waitingRequests.add(socket);
                    }
                } finally {
                    if (launch) {
                        try {
                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN_READ));
                        } catch (RejectedExecutionException x) {
                            log.warn("Socket reprocessing request was rejected for:"+socket,x);
                            try {
                                //unable to handle connection at this time
                                handler.process(socket, SocketStatus.DISCONNECT);
                            } finally {
                                countDownConnection();
                            }


                        } catch (NullPointerException npe) {
                            if (running) {
                                log.error(sm.getString("endpoint.launch.fail"),
                                        name);
                            }
                        }
                    }
                }
            }
            socket = null;
            // Finish up this request
        }

    }

 6. In addition, JIoEndpoint also constructs a separate thread to detect timeout requests. Part of the code of the startInternal method in the JIoEndpoint class

      // Start async timeout thread is used to detect timeout requests
      setAsyncTimeout(new AsyncTimeout());
      Thread timeoutThread = new Thread(getAsyncTimeout(), getName() + "-AsyncTimeout");
      timeoutThread.setPriority(threadPriority);
      timeoutThread.setDaemon(true);
      timeoutThread.start();

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326219345&siteId=291194637