The socket is analysed

When we use Tomcat, we will use port 8080 to receive data by default. For example, if we start Tomcat, we can access localhost:8080, which is a project that Tomcat deploys by default

As I said in the article “Overview of basic Use and Overall Module of Tomcat”, Tomcat is only used to receive data, read and encapsulate the data given by the operating system into Request, and then go through various layers of processing of valves and filters, and finally go into servlet. The core of reading data is how to parse the data given by the operating system into a Request object in Java code.

When you look at How Tomcat parses data, you can see that the data is actually taken out of the socket. Remember from the Socket demo in BIO, when we created a Server, we created a new ServerSocket, specified a port number, and then wrote a client, new a socket, specified the IP address and port number. Then you get the OutPutStream, and you can send data to the server. In this demo, we can see that the socket sends messages, only need to know the IP address and port number of the other party. And we know that the transmission layer protocol is TCP protocol, we use the Socket to send data, we did not go to the FORMAT of THE TCP protocol to send data, so the TCP protocol must be a “person” has been implemented, the “person” is Socket? We can look at what’s going on in the method when we do new ServerSocket.

The name of the class: java.net.DualStackPlainSocketImpl
static native int socket0(boolean stream, boolean v6Only) throws IOException;
Copy the code

I didn’t find anything related to TCP, but I did see something useful: finally get an int file descriptor using a local method, socketCreate() -> socket0(). Describes the methods of the JVM that the Socket is ultimately created using the JNI method. Since you can only go to the JVM level, this is a system-level thing. Thus it can be inferred that THE TCP protocol is actually implemented by the operating system. A socket is an interface exposed by the operating system to allow other applications to use TCP.

The communication between two computers essentially means that the two operating systems implement TCP protocol. The two operating systems expose their ports to the other side, and then send data through the IP and port of the other side. These data are transmitted to the network adapter of the other computer over the network. Then through a series of operations from the network card into memory, and the socket is the operating system to the application to manipulate the data (read, send) bridge.

Therefore, the socket caches the data received by the operating system. Because I wrote the application, can I choose that after the client sends the data, the server doesn’t read the data, the server doesn’t read the data, the data is gone? Of course not, so the socket must cache the data, let’s try.

public class ServerMain {
    public static void main(String[] args) throws Exception{
        ServerSocket serverSocket = new ServerSocket(8888); TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); }}Copy the code
public class SocketClientMain {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("127.0.0.1".8888);
        String str = "abc";
        int i = 1;
        while (true) { System.out.println(i++); socket.getOutputStream().write(str.getBytes()); }}} Output:... omit916693line916694
916695
916696
916697
916698
916699 // The first 916698 lines are all very fast prints, but from here they are very slow (about every half minute)
Copy the code

As you can see from the output, the socket does have a cache, and the cache is limited. In fact, the Socket itself has two buffers, one called RecvBuff and one called SendBuff. For Tomcat to receive the data and convert it into a Request object, it is essentially fetching the data from the RecvBuff.

How does an HTTP request socket in BIO pass into a specific HTTP protocol processor

We know that in the thread model of BIO, one thread processes a socket, and we write code that accepts a socket and creates another thread (or thread pool) to process that socket. This allows the main thread to go back and accept the next socket, which is also used in the Tomcat BIO model. As we mentioned above, when Tomcat is started, a thread is started to continuously accept sockets:

The name of the class: org.apache.tomcat.util.net.AbstractEndpoint
public final void start(a) throws Exception {
    if (bindState == BindState.UNBOUND) {
        bind();
        bindState = BindState.BOUND_ON_START;
    }
    startInternal();
}
Copy the code

In the AbstractEndpoint start() method, call the startInternal() method and choose to look at the BIO code

You see that the startAcceptorThreads() method is called and the createAcceptor() method is called internally

The name of the class: org.apache.tomcat.util.net.JIoEndpointprotected AbstractEndpoint.Acceptor createAcceptor(a) {
    return new Acceptor();
}
Copy the code

Enter a method in BIO mode and see a new Acceptor, which is a protected static inner class that implements Runnable. The following is a snippet of the run method of the class

The name of the class: org.apache.tomcat.util.net.AbstractEndpoint.Acceptor method name:public void run(a)
try {
    // Accept the next incoming connection from the server
    // bio socket
    socket = serverSocketFactory.acceptSocket(serverSocket);//
    socket.getLocalSocketAddress();
    System.out.println("Received a socket connection");
} catch (IOException ioe) {
    countDownConnection();
    errorDelay = handleExceptionWithDelay(errorDelay);
    throw ioe;
}
Copy the code

As you can see, it is the Acceptor thread that repeatedly calls the Accept method of the serverSocket to receive data. This is where it all started. After receiving data:

The name of the class: org.apache.tomcat.util.net.AbstractEndpoint.Acceptor method name:public void run(a)
// If the Endpoint is running and not paused, then the socket is processed
if (running && ! paused && setSocketOptions(socket)) {
    // Hand this socket off to an appropriate processor
    // The socket is delivered to the thread pool as normal, and processSocket returns true
    Return false if the thread pool is not delivered or the Endpoint is stopped midway
    // Close the socket if false is returned
    if(! processSocket(socket)) { countDownConnection();// Close socket right awaycloseSocket(socket); }}else {
    countDownConnection();
    // Close socket right away
    closeSocket(socket);
}
Copy the code

In the processSocket method, the socket is wrapped as a SockeetWrapper object, which is passed to another class that implements Runnable: SocketProcessor, which in turn is handed to the thread pool to execute the class’s run method.

The name of the class: org.apache.tomcat.util.net.JIoEndpoint method name:protected boolean processSocket(Socket socket)
// bio, each socket connection corresponds to one thread
// One HTTP request for one thread?
getExecutor(a).execute(new SocketProcessor(wrapper));
Copy the code

The data is ultimately read in the Run method of the SocketProcessor. In the run method, a call to handler.process is found:

The name of the class: org.apache.tomcat.util.net.JIoEndpoint.SocketProcessor method name:public void run(a)
// If the current socket is not closed, the socket is processed
if ((state ! = SocketState.CLOSED)) {
    // SocketState is a state defined by Tomcat. This state needs to be determined by handling the socket, because it depends on the client and the specific request information
    if (status == null) {
        state = handler.process(socket, SocketStatus.OPEN_READ);
    } else {
        // status indicates whether data should be read or written
        // state Indicates the state of the socket after it is processedstate = handler.process(socket,status); }}Copy the code

This handler object is a handler interface type with two implementation classes

We can infer that this handler must be Http11ConnectionHandler when we use HTTP to access Tomcat. It must have been initialized according to the Connector label when Tomcat was started. We can see the reason

The name of the class: org. Apache. Catalina. Connector.Connector
public void setProtocol(String protocol) {
    if (AprLifecycleListener.isAprAvailable()) {
        if ("HTTP / 1.1".equals(protocol)) {
            setProtocolHandlerClassName
            ("org.apache.coyote.http11.Http11AprProtocol");
        } else if ("AJP / 1.3".equals(protocol)) {
            setProtocolHandlerClassName
            ("org.apache.coyote.ajp.AjpAprProtocol");
        } else if(protocol ! =null) {
            setProtocolHandlerClassName(protocol);
        } else {
            setProtocolHandlerClassName
            ("org.apache.coyote.http11.Http11AprProtocol"); }}else {
        if ("HTTP / 1.1".equals(protocol)) {
            setProtocolHandlerClassName
            ("org.apache.coyote.http11.Http11Protocol");  // BIO
        } else if ("AJP / 1.3".equals(protocol)) {
            setProtocolHandlerClassName
            ("org.apache.coyote.ajp.AjpProtocol");
        } else if(protocol ! =null) {
            setProtocolHandlerClassName(protocol); // org.apache.coyote.http11NIOProxot}}}Copy the code

In Connector’s setProtocol method, set the specific protocol class name based on the protocol attribute value of the Connector tag configured in server.xml. The HTTP / 1.1 is considered org. Apache. Coyote. Http11. Class Http11Protocol the agreement, the subsequent will generate the protocol instances of the class, take a look at this agreement class constructor

The name of the class: org. Apache. Coyote. Http11.Http11Protocol
public Http11Protocol(a) {
    Public Abstract Class AbstractEndpoint
    AbstractEndpoint has 3 classes: 1. NioEndpoint; 2. JioEndpoint(BIO); 3. AprEndpoint
    // The endpoint provides a different IO model, reads data (streams) from sockets, and parses the data according to the protocol
    An Acceptor class is a runnable implementation class that accepts the socket from the serverSocket(initialized in the endpoint.bind () method)
    // Call processSocket(socket) to process the socket
    endpoint = new JIoEndpoint();
    cHandler = new Http11ConnectionHandler(this);
    ((JIoEndpoint) endpoint).setHandler(cHandler);
    setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
    setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
    setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
}
Copy the code

In the Http11Protocol protocol class:

  1. The new Endpoint is JIoEndpoint
  2. New out Http11ConnectionHandler as cHandler, in the Http11ConnectionHandler class constructor will itself (this, The Http11Protocol object) passes to the proto property of the Http11ConnectionHandler object (cHandler)
  3. This cHandler is assigned to the Handler property of the JIoEndpont object.
The name of the class: org.apache.tomcat.util.net.JIoEndpoint.Acceptor method name:public void run(a)
// Continue to accept acceptSockets in the while(running) loop
socket = serverSocketFactory.acceptSocket(serverSocket);
Copy the code
The name of the class: org.apache.tomcat.util.net.DefaultServerSocketFactory@Override
public Socket acceptSocket(ServerSocket socket) throws IOException {
    return socket.accept();
}
Copy the code

Here it goes: A thread started in an Acceptor object, which is not a static internal class to JioEndpoint, repeatedly calls socket.accept() to accept the socket, and calls processSocket on each socket it receives. The processSocket method then new the SocketProcessor to the thread pool, so the SocketProcessor’s run method can use the cHandler property of its external class. CHandler is the Http11ConnectionHandler object. Call cHandler’s process method from the run method.

In cHandler(Http11ConnectionHandler), the process method gets a Processor. If the Processor is empty, it initializes a Processor. The method is too long and only the core code is attached:

The name of the class: org. Apache. Coyote. AbstractProtocol.AbstractConnectionHandler
public SocketState process(SocketWrapper wrapper, SocketStatus status) {
    S socket = wrapper.getSocket();
    Processor<S> processor = connections.get(socket);
    if (processor == null) {
        // Get the processor from the reclaimed processor
        processor = recycledProcessors.poll();
    }
    if (processor == null) {
        processor = createProcessor(); // Create the processor in BIO: Http11Processor
    }
    state = processor.process(wrapper);
    return state;
}
Copy the code

Create an Http11Porcessor object, and then call the processor’s process method. Pass in a wrapped SocketWrapper object that actually reads data from the socket.

GetInputStream (); tomcat also needs to follow this logic. Socket data is input stream, which is 0101. Therefore, the input streams are parsed according to different application layer protocols in the processors of Different Protocols of Tomcat. For HTTP, Tomcat has written its own logic for parsing HTTP. Let’s take a look at how Tomcat resolves data in sockets using HTTP.

Analysis of HTTP1.1 protocol Request Packets

I use Postman to send an HTTP POST request to Tomcat to print out the data, as shown below

POST/HelloServlet/servletDemo HTTP / 1.1 the content-type: application/json the user-agent: PostmanRuntime / 7.28.4 Accept: */* Postman-Token: ce27f37f-d2ef-4a72-ab51-1b77bfd92aa1 Accept-Encoding: gzip, deflate, br Connection: Keep-alive content-length: 38 {"name":" name","age":35}Copy the code

HTTP protocol has format requirements. The following will analyze the format requirements of the HTTP1.1 protocol

The three items in the request line are separated by Spaces, the request header contains some description of the request, the end of the request header is a blank line, and then the body of the request.

The HTTP1.1 specification defines the header field (including the request header and the response header)

HTTP1.1 header field

The HTTP/1.1 specification defines the following 47 header fields

Generic header field

The field name Field to explain
Cache-Control Controls the behavior of caching
Connection Controls header fields that are no longer forwarded to the agent/manages whether persistent (long) connections are made
Date Date and time when the packet was created
Pragma Packet instructions
Trailer View the header of the packet end
Transfer-Encoding Specifies the encoding transmission mode of the packet body
Upgrade Upgrade to another protocol
Via Proxy server information
Warning Error notification

Request header field

The field name Field to explain
Accept The type of media that the user agent can handle
Accept-Charset Preferred character set
Accept-Encoding Priority content encoding
Accept-Language Preferred language (natural language)
Authorization Web Authentication Information
Expect Expect specific behavior from the server
From Email address of the user
Host The server where the resource is requested
If-Match Compare Entity Tag (ETag)
If-Modified-Since Compares the update times of resources
If-None-Match Compare entity tags (as opposed to if-match)
If-Range Send scope requests for entity Byte when the resource is not updated
If-Unmodified-Since Compare resource update times (as opposed to if-modified-since)
Max-Forwards Maximum transmission hop by hop
Proxy-Authorization The proxy server requires authentication information of the client
Range Byte range request for the entity
Referer The original acquirer of the URI in the request
TE Priority of transmission encoding
User-Agent HTTP client program information

Response header field

The field name Field to explain
Accept-Ranges Whether to accept byte range requests
Age Calculate the elapsed time of resource creation
ETag Matching information of resources
Location Causes the client to redirect to the specified URI
Proxy-Authenticate The proxy server authenticates the client
Retry-After Request the timing of the request to be made again
Server-HTTP Server installation information
vary Proxy server cache management information
WWW-Authenticate Authentication information about the server to the client

Entity head field

The field name Field to explain
Allow HTTP methods supported by the resource
Content-Encoding The encoding method applicable to the entity body
Content-Language The natural language of entity subjects
Content-Length Size of entity body in bytes
Content-Location Replace the URI of the corresponding resource
Content-MD5 The packet digest of the entity body
Content-Range The location range of the entity body
Content-Type The media type of the entity body
Expires The date and time when the entity body expires
Last-Modified The last modified date and time of the resource

Create an HTTP protocol processor in Tomcat

Since the HTTP protocol is inherently formatted, Tomcat needs to parse it according to its requirements. Here is how Tomcat creates the HTTP protocol handler

The name of the class: org. Apache. Coyote. Http11. Http11Protocol@Override
protected Http11Processor createProcessor(a) {
    Http11Processor processor = new Http11Processor(
            proto.getMaxHttpHeaderSize(), proto.getRejectIllegalHeaderName(),
            (JIoEndpoint)proto.endpoint, proto.getMaxTrailerSize(),
            proto.getAllowedTrailerHeadersAsSet(), proto.getMaxExtensionSize(),
            proto.getMaxSwallowSize(), proto.getRelaxedPathChars(),
            proto.getRelaxedQueryChars());
    processor.setAdapter(proto.adapter);
    processor.setMaxKeepAliveRequests(proto.getMaxKeepAliveRequests());
    processor.setKeepAliveTimeout(proto.getKeepAliveTimeout());
    processor.setConnectionUploadTimeout(
            proto.getConnectionUploadTimeout());
    processor.setDisableUploadTimeout(proto.getDisableUploadTimeout());
    processor.setCompressionMinSize(proto.getCompressionMinSize());
    processor.setCompression(proto.getCompression());
    processor.setNoCompressionUserAgents(proto.getNoCompressionUserAgents());
    processor.setCompressableMimeTypes(proto.getCompressableMimeTypes());
    processor.setRestrictedUserAgents(proto.getRestrictedUserAgents());
    processor.setSocketBuffer(proto.getSocketBuffer());
    processor.setMaxSavePostSize(proto.getMaxSavePostSize());
    processor.setServer(proto.getServer());
    processor.setDisableKeepAlivePercentage(
            proto.getDisableKeepAlivePercentage());
    processor.setMaxCookieCount(proto.getMaxCookieCount());
    register(processor);
    return processor;
}
Copy the code
The name of the class: org. Apache. Coyote. Http11.Http11Processor
public Http11Processor(int headerBufferSize, boolean rejectIllegalHeaderName,
        JIoEndpoint endpoint, int maxTrailerSize, Set<String> allowedTrailerHeaders,
        int maxExtensionSize, int maxSwallowSize, String relaxedPathChars,
        String relaxedQueryChars) {
    super(endpoint);
    httpParser = new HttpParser(relaxedPathChars, relaxedQueryChars);
    inputBuffer = new InternalInputBuffer(request, headerBufferSize, rejectIllegalHeaderName,
            httpParser);
    request.setInputBuffer(inputBuffer);
    outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
    response.setOutputBuffer(outputBuffer);
    // Initialize the Filter, not the Filter in the Servlet specification, but the Filter in Tomcat
    // Includes InputFilter and OutputFilter
    // InputFilter is used to process the request body
    // OutputFilter is used to process the response body
    initializeFilters(maxTrailerSize, allowedTrailerHeaders, maxExtensionSize, maxSwallowSize);
}
Copy the code

Tomcat encapsulates the Http11Protocol information in the Http11Protocol processor, such as the maximum number of long-link requests, the maximum timeout for long-link requests, and so on. Let’s see how these two parameters for long links are used.

Tomcat’s implementation of long links

HTTP protocol has a header field Connection. In the case of long Connection, the value can be keep-alive or close. Let’s look at the phenomenon

The Connection header field in HTTP is keep-alive

Create index.html in the HelloServlet folder in webApps

File name: / webapps/HelloServlet/index. The HTML<html>
    <body>
    <h2>hello world</h2>
        <img src="http://localhost:8080/HelloServler/servletDemo? time=1">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=2">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=3">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=4">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=5">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=6">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=7">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=8">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=9">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=10">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=11">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=12">
    </body>
</html>
Copy the code
System.out.println(inputStream + ":" + request.unparsedURI().toString());
Copy the code

Find a random place in the Java code of InternalInputBuffer to parse the socket, A print the inputStream properties In the browser to visit http://localhost:8080/HelloServlet/index.html to get the following output:

Output java.net.SocketInputStream@5e11f428: / HelloServlet/index. HTML java.net.SocketInputStream@5e11f428: /HelloServler/servletDemo?time=1 java.net.SocketInputStream@6482263a: /HelloServler/servletDemo?time=2 java.net.SocketInputStream@5c487d1: /HelloServler/servletDemo?time=3 java.net.SocketInputStream@550a91db: /HelloServler/servletDemo?time=4 java.net.SocketInputStream@22c22bd9: /HelloServler/servletDemo?time=5 java.net.SocketInputStream@2c4dc77f: /HelloServler/servletDemo?time=6 java.net.SocketInputStream@6482263a: /HelloServler/servletDemo?time=7 java.net.SocketInputStream@5e11f428: /HelloServler/servletDemo?time=8 java.net.SocketInputStream@5c487d1: /HelloServler/servletDemo?time=9 java.net.SocketInputStream@22c22bd9: /HelloServler/servletDemo?time=10 java.net.SocketInputStream@6482263a: /HelloServler/servletDemo?time=11 java.net.SocketInputStream@5e11f428: /HelloServler/servletDemo?time=12 java.net.SocketInputStream@5e11f428: /favicon.icoCopy the code

The maxKeepAliveRequests attribute of the Connector is set to 2

File name: /conf/server.xml<Connector port="8080" protocol="HTTP / 1.1"
           connectionTimeout="20000"
           redirectPort="8443" maxKeepAliveRequests="2"/>
Copy the code

Restart Tomcat and access the HTML again to see the output:

Output java.net.SocketInputStream@25684cdc: / HelloServlet/index. HTML java.net.SocketInputStream@62180732: /HelloServler/servletDemo?time=2 java.net.SocketInputStream@25684cdc: /HelloServler/servletDemo?time=1 java.net.SocketInputStream@2c84029e: /HelloServler/servletDemo?time=3 java.net.SocketInputStream@18d5c9cb: /HelloServler/servletDemo?time=5 java.net.SocketInputStream@3ee39c93: /HelloServler/servletDemo?time=4 java.net.SocketInputStream@300e6080: /HelloServler/servletDemo?time=6 java.net.SocketInputStream@300e6080: /HelloServler/servletDemo?time=9 java.net.SocketInputStream@18d5c9cb: /HelloServler/servletDemo?time=10 java.net.SocketInputStream@62180732: /HelloServler/servletDemo?time=12 java.net.SocketInputStream@3ee39c93: /HelloServler/servletDemo?time=11 java.net.SocketInputStream@2c84029e: /HelloServler/servletDemo?time=7 java.net.SocketInputStream@12884c66: /HelloServler/servletDemo?time=8 java.net.SocketInputStream@12884c66: /favicon.icoCopy the code

For instance, if I have 12 tabs in HTML, the browser will first send the index. HTML I visited separately, and then parse how many IMG tags are in the HTML. The request for all six tags is then sent together.

If you look closely, you can see that if I hadn’t set the Connector’s maxKeepAliveRequests attribute, the input stream for the first request, which is HTML, would be @5e11f428, and since time1 and time6 were sent together, The socket used for index is idle, so it is used by time1. Time2 through time6 is a new socket, and time7 through time12 is a socket used for index. Including the final request url icon is also used @5e11f428

If only this analysis, we can only come to one conclusion: after the socket is established, it does not seem to be closed, as if the subsequent request sent after the previous request socket idle, can always reuse the previous request socket. So we need to analyze the output of the second time

On the first request, we create a socket. We get its input stream @25684cdc, and then send time1 to time6. Time2 (time6); time2 (time6); time2 (time6); Before and after 6 times request and response, behind six times request is sent, we found that the @ 25684 CDC did not appear in time7 to time12, including time8 use @ 12884 c66 have not appeared in the seven times in front of the request, is given to illustrate the new time8 socket, Time7, time9, and time12 all reuse the previous socket, and then the last request icon request, reuse the socket time8.

The socket can be reused as many times as the maxKeepAliveRequests attribute is set.

Let’s look at the second response, the response in the browser

So we can see that the Connection field is keep-alive in the request header of all the requests, indicating that this is set by the browser. The default is long Connection, but Tomcat has made restrictions on the socket, so you can configure it. The client cannot send data through the same socket all the time. Tomcat closes the socket when Connection=close is in the response header of its response.

This only proves that the browser can reuse the socket, but this dimension is too large and I don’t think it’s possible to reuse the socket if the same browser accesses different domain addresses, but what about localhost and 127.0.0.1? Will it reuse sockets? I guess not, but there’s no point in guessing. You can try it out. So I changed the domain name from time7 to time12 in index. HTML to 127.0.0.1:8080 to see if I could reuse time7 to time12

File name: / webapps/HelloServlet/index. The HTML<html>
    <body>
    <h2>hello world</h2>
        <img src="http://localhost:8080/HelloServler/servletDemo? time=1">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=2">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=3">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=4">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=5">
        <img src="http://localhost:8080/HelloServler/servletDemo? time=6">
        <img src="Http://127.0.0.1:8080/HelloServler/servletDemo? time=7">
        <img src="Http://127.0.0.1:8080/HelloServler/servletDemo? time=8">
        <img src="Http://127.0.0.1:8080/HelloServler/servletDemo? time=9">
        <img src="Http://127.0.0.1:8080/HelloServler/servletDemo? time=10">
        <img src="Http://127.0.0.1:8080/HelloServler/servletDemo? time=11">
        <img src="Http://127.0.0.1:8080/HelloServler/servletDemo? time=12">
    </body>
</html>
Copy the code

Using localhost to access HTML and restarting Tomcat (just to initialize socket reuse), my maxKeepAliveRequests attribute remains 2

Output java.net.SocketInputStream@28ef627: / HelloServlet/index. HTML java.net.SocketInputStream@28ef627: /HelloServler/servletDemo?time=1 java.net.SocketInputStream@558d912c: /HelloServler/servletDemo?time=2 java.net.SocketInputStream@58f82f4e: /HelloServler/servletDemo?time=3 java.net.SocketInputStream@4691c63d: /HelloServler/servletDemo?time=4 java.net.SocketInputStream@22aca64: /HelloServler/servletDemo?time=7 java.net.SocketInputStream@1ee53b84: /HelloServler/servletDemo?time=5 java.net.SocketInputStream@6c78945e: /HelloServler/servletDemo?time=9 java.net.SocketInputStream@75001be8: /HelloServler/servletDemo?time=6 java.net.SocketInputStream@407599b8: /HelloServler/servletDemo?time=10 java.net.SocketInputStream@2edca1c7: /HelloServler/servletDemo?time=8 java.net.SocketInputStream@263364d9: /HelloServler/servletDemo?time=11 java.net.SocketInputStream@22aca64: /HelloServler/servletDemo?time=12 java.net.SocketInputStream@58f82f4e: /favicon.icoCopy the code

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * At this point we look at the response header of the icon request. The value of the Connection header is close

The above illustration:

  1. Connection=keep-alive is for domain names
  2. The server can control when the Connection is disconnected. The last response before the Connection is disconnected, the server will give the client a Connection=close response header, indicating that the client can also close the socket.
  3. The so-called long connection is that when the client sends a request, it checks whether the browser has a free and available socket created by the domain name. If so, it uses the existing socket and does not need to create a new socket.
  4. If the Connection header is close when the client sends the request, the server will not continue to use the socket.

Tomcat implementation of long connection

First, repeat how Tomcat accepts sockets

  1. Acceptor threads continually call socket.accept() while(running) to accept the socket
  2. On receiving a socket, create a task and drop it into the thread pool
  3. The task must implement Runnable and is created/retrieved in the run method of the task (if it was recycled before, it would be retrieved from the recycle bin, not created). Call the process method of this object (in its subclass AbstractHttp11Processor)
  4. Read/parse the input stream in the socket in processor.process(socketWrapper)

In AbstractHttp11Processor, there are three protected attributes associated with the Keep-alive business logic

The name of the class: org. Apache. Coyote. Http11. AbstractHttp11Processor// Indicates whether the current request is a keep-alive
protected boolean keepAlive = true;
// indicates that the current request is a keep-alive
// Indicates that the last request of the current request was keep-alive, so the current request can reuse the socket of the last request
protected boolean keptAlive;
// Flag used to indicate that the socket should remain open
protected boolean openSocket = false;
Copy the code

In the process method, we have to handle the keep-alive case. According to our analysis, if Tomcat resolves the request header and finds that the Connection field is keep-alive, then after it has processed the request, We can probably summarize the way this code is written, as for the real source code, it is too long, you can have a look, In the org. Apache. Coyote. Http11. AbstractHttp11Processor class public SocketState process method. The following is my own summary of the extracted core code (and the source is not large, by my own sorting):

Pseudo-code, this code will be in getExecutor().execute(newSocketProcessor(socketWrapper) is executed in the thread poolSocketState process(Socket socket) {
    keepAlive = true;
    while (keepAlive) {
        InputStream in = socket.getInputStream();
        // Parse the input stream and encapsulate it into a Request object
        Request request = in.read();
        // Get the Connection in the request header
        String conn = request.getHeaders().get("Connection");
        keepAlive = "keep-alive".equals(conn);
        / / call the Servlet
        adapter.service(request, response);
        endRequest();
        nextRequest();
        // If keepAlive = true, the socket should not be closed
        opensocket = keepAlive;
        // If the socket has no data for the next request, exit the current loop
        // If it is keepAlive, the socket is not closed; if it is close, the socket is closed
        // For BIO, since one thread processes a socket, the current thread terminates when exiting the loop
        For keepAlive, however, the socket should not be closed even if the current thread terminates
        if(inputBuffer.lastValid == 0) {
            // A normal browser will send only one complete request in a socket at a time
            // However, there are some programs that intentionally send several connected requests in a socket. This is to prevent this situation
            // If an application sends conjoined requests in a socket and keepAlive = true, Tomcat will continue to process the next request in this loop after the last request ends
            break; }}// If openSocket is still true at the end of the process, the process method returns the socket status as open
    if (openSocket) {
        returnSocketState.OPEN; }}Copy the code

The above code is executed in the SocketProcessor thread’s run method, which determines the final socket state returned by the Process method. If it is OPEN, GetExecutor ().execute(new SocketProcessor(socketWrapper)) again executes the process method.

The name of the class: org.apache.tomcat.util.net.JIoEndpoint.SocketProcessor method name:public void run(a)
// If the Socket is closed, subtract the number of connections and close the Socket
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.UPGRADING_TOMCAT  ||
        state == SocketState.UPGRADED){
    // If the process method returns socketstate. OPEN, set the socket to be keepAlive(keptAlive = true)
    socket.setKeptAlive(true);
    // Call the socket access method, marking the time of this access, which is the last time of the last request for the next request
    socket.access();
    // Setting launch to true determines whether to throw the socket into the thread pool again
    launch = true;
} else if (state == SocketState.LONG) {
    // The socket is not closed, but the current thread is terminated
    socket.access();
    waitingRequests.add(socket);
}
Copy the code

In the finally block of the run method, the value of launch is determined

The name of the class: org.apache.tomcat.util.net.JIoEndpoint.SocketProcessor method name:public void run(a)
finally {
    if (launch) {
        try {
            // If launch = true is determined above, the requested socket will continue to be thrown into the thread pool in the finally block
            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"), npe); }}}}Copy the code

This is the general process, followed by the previous four steps, which begin by parsing the input stream in the socket

  1. The Run method of the SocketProcessor(Runnable) is used to invoke the Process method of the AbstractHttp11Processor

In the process method

1. Check whether the Connection header is keep-alive.2. If it is 1.4.1 of keep-alive, if the client sends multiple simultaneous requests in the socket, it will continue processing the next request in the while without breaking out of the while loop. If the client is normal and does not send any connected request, set the socket status to OpenCopy the code
  1. The run method of the SocketProcessor(Runnable) returns the socket status from the Process method
  2. If the socket status is open, continue to set the socket to a new SocketProcessor(Runnable)
  3. Throw the new SocketProcessor into the thread pool for execution

Tomcat timeout handling for long connection Settings

Since this involves the way Tomcat reads data from the socket input stream, it will be explained in a future blog post