diyTomcat series 3 introduces thread pool and handles long TCP connections

diyTomcat series 3 introduces thread pool and handles long TCP connections

Several problems to be solved in this section:

Frequent creation and destruction of threads, every time HTTP Every request generates a new Socket Connection. When the number of requests is too large, the overhead is large

Solution:

Introduction of thread pool and utilization Connection=keep-alive,Connection timeout control TCP Long connection to handle http request

1. Introduce thread pool

In normal network requests, a large number of http requests need to be processed at the same time. If each http request is handed over to a separate thread for processing, the thread will be destroyed immediately after processing, which will consume performance and cause unnecessary overhead. Therefore, we introduce thread pool to process requests

//Let's first look at the parameters to be passed in by the thread pool construction method
ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);
//The parameters are:
/The size of the core thread pool to obtain the number of cores of the server processor
int corePoolSize = Runtime.getRuntime().availableProcessors();
//Maximum number of threads in the core thread pool
int maxPoolSize=corePoolSize * 2;
//Maximum idle time of thread
long keepAliveTime=10;
//The time unit is set to seconds
TimeUnit unit=TimeUnit.SECONDS;
//The capacity of the blocking queue is maxPoolSize * 4, and a maximum of maxPoolSize * 4 idle tasks are allowed
BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<>(maxPoolSize * 4);
//Thread creation factory
ThreadFactory threadFactory = new NameThreadFactory();
//Thread pool reject policy
RejectedExecutionHandler handler = new MyIgnorePolicy();

If the number of connections is small, using thread pool is actually a waste of resources. By default, all Tomcat does not use thread pool. Wait until a certain number of requests to use thread pool. Of course, based on the design idea of high cohesion and low coupling, we can also use thread pool on server Set the default startup thread pool manually in the configuration file in line 74 of XML

2. Handle long TCP connections

First of all, we should know that HTTP protocol is an application layer protocol. The application layer protocol is to define message dialogue, ensure that the message being sent gets the expected response, and call the correct service when transmitting data. The HTTP protocol is based on the request / response pattern. The long connection we usually discuss is a TCP long connection. The application layer does not pay attention to whether it is a long connection or a short connection. As long as the server gives a response, the HTTP connection ends, or more accurately, the HTTP request ends, and the next time is a new request and a new response. Therefore, there is no long connection at all. Then naturally, there is no short connection.

According to the code, the TCP long connection, that is, the socket has not been closed and is always connected.

How to set up a long connection?

We only need to set connection: keep alive in the request header, so that when our server receives the request, it will not take the initiative to close the socket connection. When a web page is opened, the TCP connection between the client and the server for transmitting HTTP data will not be closed. If the client accesses the web page on the server again, Will continue to use this established connection. Of course, the server set to keep alive will not permanently maintain the connection. It has a retention time, which can be set in different server software (such as Tomcat). If the client does not make an HTTP request within the specified time, it is good to disconnect and implement it in the code, that is, close the socket. To realize long connection, both client and server should support long connection. The long connection and short connection of HTTP protocol are essentially the long connection and short connection of TCP protocol.

The connection timeout can be set in Tomcat server Configure the Connector in line 69 of the XML. You can see that the default time of Tomcat long connection is 20s

In the author's second article, long connections are not handled. You can see that the connection is closed after each http request, and then a new connection will be made

Next, let's optimize it (in fact, it's very simple, just don't turn off the socket connection and set the timeout):

The main code is modified in TaskService in the second article. The modified code and code are pasted here first, and the complete code is pasted at the end of the article. The author also limits the number of connections here to prevent exceptions caused by a thread processing too many requests. With the line pool, the number of threads in active state can be increased to process requests.

Neither HttpServletRequest nor HttpServletResponse can close the flow connection, because if the flow obtained through the socket is closed, the socket will also be closed, which will cause an exception.

public void run() {
    long connectionTime = 0L;//Record connection time
    while (flag){
        try {
            connectionCount++;
            logger.info(Thread.currentThread().getName()+",This long connection has been processed"+connectionCount+"Secondary request");
            if(connectionCount > MAX_REQUEST_COUNT){
                connection = "close";
            }
            //http request encapsulation. If there is no request, it will be blocked here
            HttpServletRequest req = new HttpServletRequest(socket);
            //Respond to requests
            HttpServletResponse resp = new HttpServletResponse(req, socket);
        } catch (Exception e) {
            logger.error("Thread running error",e);
            connection = "close";
        }finally {
            //Timeout close connection
            if(connectionTime == 0){
                connectionTime = System.currentTimeMillis();
            }else {
                connectionTime = System.currentTimeMillis() - connectionTime;
                if(connectionTime > 20000){//The timeout can be read from the xml configuration file
                    flag = false;
                    connection = "close";//Close connection
                }
            }
            //The number of connections exceeded and the connection was closed
            if("close".equals(connection)){
                flag = false;
                //Close the resource if it is not a long link
                try {
                    if(socket != null){
                        socket.close();
                    }
                } catch (IOException ioException) {
                    logger.error(ioException.getMessage());
                }
                try {
                    if(in != null){
                        in.close();
                    }
                } catch (IOException ioException) {
                    logger.error(ioException.getMessage());
                }
                try {
                    if(out != null){
                        out.close();
                    }
                } catch (IOException ioException) {
                    logger.error(ioException.getMessage());
                }
            }
        }
    }
}

3. Complete project code

Referring to the code of phase II, only a few modifications have been made here.

Remember that neither HttpServletRequest nor HttpServletResponse can close the flow connection, because if the flow obtained through the socket is closed, the socket will also be closed, which will cause exceptions. Close the socket connection and process it in TaskService

3.1 MyCatServer

package com.fx.tomcat;


import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author:fengxian
 * @date: 2022/1/17 17:02
 * Description:
 */
public class MyCatServer {
    private final static Logger logger = Logger.getLogger(MyCatServer.class);
    private static Map<String, Map<String,String>> map;//Used to wrap configuration files
    private static boolean flag=true;
    public static void main(String[] args) {
        MyCatServer myCatServer = new MyCatServer();
        //Get the port of the service from the configuration file
        int tomcatServicePort = Integer.parseInt(map.get("Connector").get("port"));
        //Use different multithreading policies according to the configuration file
        if("true".equalsIgnoreCase(map.get("Connector").get("executor"))){
            myCatServer.startServerByThreadPool(tomcatServicePort);
        }else {
            myCatServer.startServerByThread(tomcatServicePort);
        }
    }

    /**
     * Not through thread pool
     */
    private void startServerByThread(int port){
        try (ServerSocket ss = new ServerSocket(port)) {
            while (flag){
                Socket socket = ss.accept();
                logger.debug("There are user requests,He is"+socket.getLocalSocketAddress());
                new Thread(new TaskService(socket)).start();
            }
        } catch (Exception e) {
            logger.error("Server startup exception"+e);
        }
    }

    /**
     * Thread pool open thread
     */
    private void startServerByThreadPool(int port) {
        //The size of the core thread pool to obtain the number of cores of the server processor
        int corePoolSize = Runtime.getRuntime().availableProcessors();
        //Maximum number of threads in the core thread pool
        int maxPoolSize=corePoolSize * 2;
        //Maximum idle time of thread
        long keepAliveTime=10;
        //Time unit
        TimeUnit unit=TimeUnit.SECONDS;
        //The capacity of the blocking queue is 2. At most two idle tasks can be put in
        BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<>(maxPoolSize * 4);
        //Thread creation factory
        ThreadFactory threadFactory = new NameThreadFactory();
        //Thread pool reject policy
        RejectedExecutionHandler handler = new MyIgnorePolicy();
        //Thread pool performer
        ThreadPoolExecutor executor;
        //Uncontrolled thread creation is not allowed. Thread pool must be used
        executor = new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);
        //Pre start all core threads to improve efficiency
        executor.prestartAllCoreThreads();
        try (ServerSocket ss = new ServerSocket(port)) {
            while(flag){
                Socket socket = ss.accept();
                logger.debug("There are user requests,He is"+socket.getLocalSocketAddress());
                //Perform tasks
                executor.submit(new TaskService(socket));
            }
        } catch (Exception e) {
        	logger.error("Server startup exception:{}",e);
        } finally {
            //Thread pool shutdown
            executor.shutdown();
        }
    }

    /**
     * Thread factory
     */
    static class NameThreadFactory implements ThreadFactory {
        Logger logger = Logger.getLogger(NameThreadFactory.class);
        //Thread id, AtomicInteger, atomic class
        private final AtomicInteger threadId=new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, "thread " + threadId.getAndIncrement());//Equivalent to i++
            logger.info(t.getName()+"Has been created");
            return t;
        }
    }

    /**
     * Reject policy after thread pool BlockingQueue is full
     */
    public static class MyIgnorePolicy implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            logger.error("Thread pool+"+ e.toString()+r.toString()+"Rejected");
        }
    }

    //dom4j parsing xml
    static {
        map = new ConcurrentHashMap<>();
        Map<String,String> sonMap = new HashMap<>();
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read("conf/server.xml");
        } catch (DocumentException e) {
            logger.error("Configuration file parsing failed{}",e);
        }
        assert document != null;
        Element root = document.getRootElement();
        Element connector = root.element("Service").element("Connector");
        sonMap.put("port",connector.attributeValue("port"));
        sonMap.put("protocol",connector.attributeValue("protocol"));
        sonMap.put("connectionTimeout",connector.attributeValue("connectionTimeout"));
        sonMap.put("redirectPort",connector.attributeValue("redirectPort"));
        sonMap.put("executor",connector.attributeValue("executor"));
        map.put("Connector",sonMap);
    }
}

3.2 TaskService

package com.fx.tomcat;

import org.apache.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.time.Duration;
import java.time.Instant;

/**
 * @author: fengxian
 * @date: 2022/1/18 9:09
 * Description:
 */
public class TaskService implements Runnable{
    private final Logger logger = Logger.getLogger(TaskService.class);
    private Socket socket;
    private InputStream in = null;
    private OutputStream out = null;
    private String connection="keep-alive";
    private boolean flag;
    private final int MAX_REQUEST_COUNT = 30;
    private int connectionCount;//Number of connections
    public TaskService(Socket socket){
        this.socket=socket;
        flag = true;
    }
    @Override
    public void run() {
        long connectionTime = 0L;
        while (flag){
            try {
                connectionCount++;
                logger.info(Thread.currentThread().getName()+",This long connection has been processed"+connectionCount+"Secondary request");
                if(connectionCount > MAX_REQUEST_COUNT){
                    connection = "close";
                }
                //http request encapsulation. If there is no request, it will be blocked here
                HttpServletRequest req = new HttpServletRequest(socket);
                //Respond to requests
                HttpServletResponse resp = new HttpServletResponse(req, socket);
            } catch (Exception e) {
                logger.error("Thread running error",e);
                connection = "close";
            }finally {
                //Timeout close connection
                if(connectionTime == 0){
                    connectionTime = System.currentTimeMillis();
                }else {
                    connectionTime = System.currentTimeMillis() - connectionTime;
                    if(connectionTime > 20000){//The timeout can be read from the xml configuration file
                        flag = false;
                        connection = "close";//Close connection
                    }
                }
                //The number of connections exceeded and the connection was closed
                if("close".equals(connection)){
                    flag = false;
                    //Close the resource if it is not a long link
                    try {
                        if(socket != null){
                            socket.close();
                        }
                    } catch (IOException ioException) {
                        logger.error(ioException.getMessage());
                    }
                    try {
                        if(in != null){
                            in.close();
                        }
                    } catch (IOException ioException) {
                        logger.error(ioException.getMessage());
                    }
                    try {
                        if(out != null){
                            out.close();
                        }
                    } catch (IOException ioException) {
                        logger.error(ioException.getMessage());
                    }
                }
            }
        }
    }
}

HttpServletResponse and HttpServletRequest only eliminate all the codes that close the flow, and others are not modified

4. Project demonstration effect and new problem thinking

Here's another interesting point

**Why do you set up long connections or generate so many new socket connections** Did you time out?

Let's set the timeout as long as the sky and long as the earth, and test it again

Test results:

We will find that six socket connections will still be generated, but we will find that six socket connections will be generated each time after we test several times

Why?

That's because the browser can't request resources only by one thread, which will cause slow speed. Therefore, the browser will set the default maximum number of HTTP concurrent connections for the same server. The default number of concurrent connections for mainstream browsers is as follows:

BrowserHTTP/1.1HTTP/1.0
IE 8,966
IE 6,724
Firefox 1766
Firefox 366
Firefox 228
Safari 3,444
Chrome 1,26?
Chrome 344
Chrome 4+6?

We all know that @ WebServlet can be used instead of xml file for address mapping in Tomcat. In the next article, the author will customize this annotation and realize the access of Servlet dynamic resources. The process of customizing annotations can be seen in the writer's article, Annotation learning 1. Java built-in annotation and annotation writing and Annotation learning II. Using annotations to imitate junit test framework First, try to write a few comments.

Keywords: Java server

Added by deezzer on Mon, 24 Jan 2022 02:36:11 +0200