Jetty architecture - Connector component

If you feel that the article contains errors, welcome to leave a message or private message to discuss~

  Jetty is also an "HTTP server + Servlet container". Jetty has many similarities with Tomcat in architectural design, but it is smaller and easier to customize. As a rising star, Jetty has a wider and wider range of applications. For example, Google App Engine uses Jetty. Today we will talk about the differences between Jetty and Tomcat to deepen our understanding of web container architecture design, and on the other hand, we will be more aware of their design differences.

Jetty overall architecture

  Jetty's architecture is composed of multiple Connectors, Handlers and a thread pool, as shown in the following figure:
insert image description here

  Like tomcat, Jetty also has the functions of HTTP server and Servlet container, so the Connector component and Handler component in Jetty realize these two functions respectively, and the thread resources required for the work of these two components are directly obtained from a global thread pool Get it from ThreadPool.

  Jetty's Server can have multiple Connectors listening on different ports, and then for the Handler component of request processing, different Handlers can be used according to different scenarios. For example, if you need to support Session, you can add another SessionHandler.

  In order to start and coordinate the work of the above core components, Jetty provides a Server class to do this, which is responsible for creating and initializing Connector, Handler, ThreadPool components, and then calling the start method to start them.

  Let's compare the overall architecture diagram of Tomcat. You will find that Tomcat is very similar to Jetty. The first difference is that there is no concept of Service in Jetty. Service in Tomcat wraps multiple connectors, a container component, and a Tomcat instance Multiple Services can be configured, and different Services listen to different ports through different connectors; while the Connector in Jetty is shared by all Handlers.

insert image description here

  The second difference is that each connector in Tomcat has its own thread pool, while Connectors in Jetty share a global thread pool.

Connector component

  Like Tomcat, the main function of Connector is to encapsulate the I/O model and application protocol. In terms of the I/O model, the latest Jetty9 only supports NIo, so Jetty's Connector design has obvious traces of the Java NIO communication model. As for the application layer protocol, like Tomcat's Processor, Jetty abstracts the Connection component to encapsulate the differences in the application layer protocol.

Java NIO Review

  Most development students should not have much contact with IO development (after all, many of them are ready-made and packaged, we just need to be API call fighters). Let's start by reviewing. The core components of Java NIO are Channel, Buffer and Selector. Channel represents a connection, which can be understood as a Socket through which data can be read and written, but the data cannot be directly manipulated and needs to be transferred through Buffer.

  The Selector can detect I/O events on the Channel, such as read-ready and write-ready. A Selector can handle multiple Channels, so a single thread can monitor multiple Channels, which can reduce the overhead of thread context switching.

  Let's write a classic example together:

ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(port));
server.configureBlocking(false);

  Then, create a Selector, and register the event OP_ACCEPT that Channel is interested in in the Selector, and tell the Selector to notify me if the client has a new connection request to this port.

Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);

  Next, the Selector will continuously call select to query the I/O status in an infinite loop. Select will return a list of SelectionKeys. The Selector will traverse the list to see if there is an event that the "customer" is interested in. If so, it will take corresponding action. For example, the following example:

 while (true) {
    
    
        selector.select();//查询I/O事件
        for (Iterator<SelectionKey> i = selector.selectedKeys().iterator(); i.hasNext();) {
    
     
            SelectionKey key = i.next(); 
            i.remove(); 

            if (key.isAcceptable()) {
    
     
                // 建立一个新连接 
                SocketChannel client = server.accept(); 
                client.configureBlocking(false); 
                
                //连接建立后,告诉Selector,我现在对I/O可读事件感兴趣
                client.register(selector, SelectionKey.OP_READ);
            } 
        }
    } 

  To briefly review, the server mainly does three things in I/O communication: monitoring connections, I/O event query, and data reading and writing . Therefore, Jetty designed the corresponding Acceptor, SelectorManager and Connection to do these three things respectively.

Acceptor

  Acceptors are used to accept requests. In the Connector implementation class ServerConnector, there is an array of _acceptors. When the Connector is started, a corresponding number of Acceptors will be created according to the length of the _acceptors array, and the number of Acceptors can be configured.


for (int i = 0; i < _acceptors.length; i++)
{
    
    
  Acceptor a = new Acceptor(i);
  getExecutor().execute(a);
}

  You can see that the Acceptor can be submitted to the thread pool (the global thread pool mentioned earlier), so it is a Runnable and an internal class of ServerConnector.

  Acceptor accepts connections by blocking, which is the same as Tomcat.

public void accept(int acceptorID) throws IOException
{
    
    
  ServerSocketChannel serverChannel = _acceptChannel;
  if (serverChannel != null && serverChannel.isOpen())
  {
    
    
    // 这里是阻塞的
    SocketChannel channel = serverChannel.accept();
    // 执行到这里时说明有请求进来了
    accepted(channel);
  }
}

  After accepting the connection successfully, the accepted function will be called. In the accepted function, the SocketChannel will be set to non-blocking mode, and then it will be handed over to the Selector for processing, so this has reached the boundary of the Selector.

private void accepted(SocketChannel channel) throws IOException
{
    
    
    channel.configureBlocking(false);
    Socket socket = channel.socket();
    configure(socket);
    // _manager是SelectorManager实例,里面管理了所有的Selector实例
    _manager.accept(channel);
}

SelectorManager

  Jetty's Selector is managed by the SelectorManager class, and the managed Selector is called ManagedSelector. There is a ManagedSelector array inside the SelectorManager, and the real work is the ManagedSelector. Then the above accept function

public void accept(SelectableChannel channel, Object attachment)
{
    
    
  //选择一个ManagedSelector来处理Channel
  final ManagedSelector selector = chooseSelector();
  //提交一个任务Accept给ManagedSelector
  selector.submit(selector.new Accept(channel, attachment));
}

  We see that the task Accept will be submitted to ManageSelector here. There are mainly two steps here:

  1. Call the register method of the Selector to register the Channel to the Selector and get a SelectionKey
 _key = _channel.register(selector, SelectionKey.OP_ACCEPT, this);
  1. Create an EndPoint and Connection, and bind it with this SelectionKey (Channel)
private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException
{
    
    
    //1. 创建EndPoint
    EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey);
    
    //2. 创建Connection
    Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment());
    
    //3. 把EndPoint、Connection和SelectionKey绑在一起
    endPoint.setConnection(connection);
    selectionKey.attach(endPoint);
    
}

  What is the significance of these two steps? In layman's terms, you go to a restaurant to eat, order first (register I/O event), the waiter (ManagerSelector) gives you a list (SelectionKey), wait for the dishes to be ready (I/O event arrives), the waiter according to the list I know which table ordered what dishes, so I shouted that the dishes of a certain table are ready (calling the method of EndPoint).

  What you need to pay special attention to here is that ManagedSelector does not directly call the EndPoint method to process data, but returns a Runnable by calling the EndPoint method, and then throws the Runnable to the thread pool for execution, so you can guess that this Runnable is It will actually read the data and process the request.

Connection

  Connection is Tomcat's Processor, which is responsible for analyzing the protocol, getting the Request object, and then throwing it to the Handler for processing. The following briefly introduces its specific implementation class HttpConnection to process requests and responses.

  Request processing: HttpConnection does not actively read data from EndPoint, but registers a bunch of callback methods in EndPoint:

getEndPoint().fillInterested(_readCallback);

  This code is to tell EndPoint that when the data arrives, you can call these callback methods _readCallback. It feels a bit asynchronous I/O, which means that Jetty simulates the asynchronous I/O model at the application level.

  In the callback method, the interface of EndPoint will be called to read the data, and after reading, let the Http parser parse the byte stream to obtain the relevant information including the request line and request header and store it in the Request object.

  Response processing: Connection calls Handler for business processing. Handler will operate the response stream through the Response object, write data into the stream, and HttpConnection will write the data to Channel through EndPoint, so that a response is completed.

Summarize

  At this point, you should understand the working principle of Connector, you can refer to the following figure to recall:
insert image description here

  1. The Acceptor monitors the connection request and accepts the connection when a connection request arrives. A connection corresponds to a Channel, and the Acceptor hands the Channel to the ManagedSelector for processing.
  2. ManagedSelector registers the Channel to the Selector, and creates an EndPoint and Connection bound to the Channel, and then continuously detects I/O events.
  3. When the I/O event arrives, call the EndPoint method to get a Runnable and throw it to the thread pool for execution.
  4. A thread is scheduled in the thread pool to execute Runnable.
  5. When the Runnable is executed, the callback function is called, and the callback function is registered in the EndPoint by the Connection.
  6. The internal implementation of the callback function is actually to call the interface method of EndPoint to read the data.
  7. Connection parses the read data, generates a request object and hands it to the Handler component for processing.

Guess you like

Origin blog.csdn.net/qq_43654226/article/details/127291944