tomcat&jetty笔记2

Jetty架构特点之Connector组件

Tomcat一样,Jetty也是一个“HTTP服务器 + Servlet容器 Jetty中的Connector组件和Handler组件分别来实现这两个功能,而这两个组件工作时所需要的线程资源都直接从一个全局线程池ThreadPool中获取。

Jetty Server可以有多个Connector在不同的端口上监听客户请求,而对于请求处理的Handler组件,也可以根据具体场景使用不同的Handler。这样的设计提高了Jetty的灵活性,需要支持Servlet,则可以使用ServletHandler;需要支持Session,则再增加一个SessionHandler。也就是说我们可以不使用Servlet或者Session,只要不配置这个Handler就行了。

为了启动和协调上面的核心组件工作,Jetty提供了一个Server类来做这个事情,它负责创建并初始化ConnectorHandlerThreadPool组件,然后调用start方法启动它们。

二者区别

第一个区别是Jetty中没有Service的概念,Tomcat中的Service包装了多个连接器和一个容器组件,一个Tomcat实例可以配置多个Service,不同的Service通过不同的连接器监听不同的端口;而JettyConnector是被所有Handler共享的。

第二个区别是,在Tomcat中每个连接器都有自己的线程池,而在Jetty中所有的Connector共享一个全局的线程池。

Connector组件

Tomcat一样,Connector的主要功能是对I/O模型和应用层协议的封装。I/O模型方面,最新的Jetty 9版本只支持NIO,因此JettyConnector设计有明显的Java NIO通信模型的痕迹。至于应用层协议方面,跟TomcatProcessor一样,Jetty抽象出了Connection组件来封装应用层协议的差异。

Java NIO回顾

Java NIO的核心组件是ChannelBufferSelectorChannel表示一个连接,可以理解为一个Socket,通过它可以读取和写入数据,但是并不能直接操作数据,需要通过Buffer来中转。

Selector可以用来检测Channel上的I/O事件,比如读就绪、写就绪、连接就绪,一个Selector可以同时处理
多个Channel,因此单个线程可以监听多个Channel,这样会大量减少线程上下文切换的开销。下面我们通
过一个典型的服务端NIO程序来回顾一下如何使用这些组件。

首先,创建服务端Channel,绑定监听端口并把Channel设置为非阻塞方式。

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

然后,创建Selector,并在Selector中注册Channel感兴趣的事件OP_ACCEPT,告诉Selector如果客户端有新的连接请求到这个端口就通知我。

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

接下来,Selector会在一个死循环里不断地调用select()去查询I/O状态,select()会返回一个SelectionKey列表,Selector会遍历这个列表,看看是否有客户感兴趣的事件,如果有,就采取相应的动作。

比如下面这个例子,如果有新的连接请求,就会建立一个新的连接。连接建立后,再注册Channel的可读事件到Selector中,告诉Selector我对这个Channel上是否有新的数据到达感兴趣。

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);
        }
    }
}

服务端在I/O通信上主要完成了三件事情:监听连接、I/O事件查询以及数据读写。因此Jetty设计了AcceptorSelectorManagerConnection来分别做这三件事情,下面分别来说说这三个组件。

Acceptor

Acceptor用于接受请求,跟Tomcat一样,Jetty也有独立的Acceptor线程组用于处理连接请求。在Connector的实现类ServerConnector中,有一个_acceptors的数组,在Connector启动的时候, 会根据_acceptors数组的长度创建对应数量的Acceptor,而Acceptor的个数可以配置。

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

AcceptorServerConnector中的一个内部类,同时也是一个RunnableAcceptor线程是通过getExecutor()得到的线程池来执行的,前面提到这是一个全局的线程池。

Acceptor通过阻塞的方式来接受连接,这一点跟Tomcat也是一样的。

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

接受连接成功后会调用accepted()函数,accepted()函数中会将SocketChannel设置为非阻塞模式,然后交给Selector去处理,因此这也就到了Selector的地界了。

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

SelectorManager

JettySelectorSelectorManager类管理,而被管理的Selector叫作ManagedSelectorSelectorManager内部有一个ManagedSelector数组,真正干活的是ManagedSelector

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

SelectorManager从本身的Selector数组中选择一个Selector来处理这个Channel,并创建一个任务Accept交给ManagedSelectorManagedSelector在处理这个任务主要做了两步:

第一步,调用Selectorregister方法把Channel注册到Selector上,拿到一个SelectionKey

_key = _channel.register(selector, SelectionKey.OP_ACCEPT, this);

第二步,创建一个EndPointConnection,并跟这个SelectionKeyChannel)绑在一起:

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);
}

你到餐厅吃饭,先点菜(注册I/O事件),服务员(ManagedSelector)给你一个单子(SelectionKey),等菜做好了(I/O事件到了),服务员根据单子就知道是哪桌点了这个菜,于是喊一嗓子某某桌的菜做好了(调用了绑定在SelectionKey上的EndPoint的方法)。

ManagedSelector并没有调用直接EndPoint的方法去处理数据,而是通过调用EndPoint的方法返回一个Runnable,然后把这个Runnable扔给线程池执行,这个Runnable才会去真正读数据和处理请求。

Connection

RunnableEndPoint的一个内部类,它会调用Connection的回调方法来处理请求。JettyConnection组件类比就是TomcatProcessor,负责具体协议的解析,得到Request对象,并调用Handler容器进行处理。下面我简单介绍一下它的具体实现类HttpConnection对请求和响应的处理过程。

请求处理HttpConnection并不会主动向EndPoint读取数据,而是向在EndPoint中注册一堆回调方法:

getEndPoint().fillInterested(_readCallback);

告诉EndPoint,数据到了你就调我这些回调方法_readCallback吧,有点异步I/O的感觉,也就是说Jetty在应用层面模拟了异步I/O模型。

而在回调方法_readCallback里,会调用EndPoint的接口去读数据,读完后让HTTP解析器去解析字节流,HTTP解析器会将解析后的数据,包括请求行、请求头相关信息存到Request对象里。

响应处理Connection调用Handler进行业务处理,Handler会通过Response对象来操作响应流,向流里面写入数据,HttpConnection再通过EndPoint把数据写到Channel,这样一次响应就完成了。

猜你喜欢

转载自blog.csdn.net/wjl31802/article/details/102675607
今日推荐