Tomcat8(二):链接建立与请求处理Poller

接受请求:
当客户端发来新的请求后,Acceptor线程不再阻塞,会获取到socket,继续运行调用setSocketOptions():

  @Override
    protected boolean setSocketOptions(SocketChannel socket) {
        NioSocketWrapper socketWrapper = null;
        try {
            // Allocate channel and wrapper
            NioChannel channel = null;
            if (nioChannels != null) {
                channel = nioChannels.pop();
            }
            if (channel == null) {
                SocketBufferHandler bufhandler = new SocketBufferHandler(
                        socketProperties.getAppReadBufSize(),
                        socketProperties.getAppWriteBufSize(),
                        socketProperties.getDirectBuffer());
                if (isSSLEnabled()) {
                    channel = new SecureNioChannel(bufhandler, this);
                } else {
                    channel = new NioChannel(bufhandler);
                }
            }

            NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);
            channel.reset(socket, newWrapper);
            connections.put(socket, newWrapper);
            socketWrapper = newWrapper;

            // Set socket properties
            // Disable blocking, polling will be used
            socket.configureBlocking(false);// todo 每个socket是非阻塞式的
            socketProperties.setProperties(socket.socket());

            socketWrapper.setReadTimeout(getConnectionTimeout());
            socketWrapper.setWriteTimeout(getConnectionTimeout());
            socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
            // 将一个链接socket请求注册到poller中,然后使用异步io,处理读写事件
            poller.register(socketWrapper);// 将请求的socket放在poller中开始轮询处理
            return true;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            try {
                log.error(sm.getString("endpoint.socketOptionsError"), t);
            } catch (Throwable tt) {
                ExceptionUtils.handleThrowable(tt);
            }
            if (socketWrapper == null) {
                destroySocket(socket);
            }
        }
        // Tell to close the socket if needed
        return false;
    }

setSocketOptions正如方法名字,包装了一下此次客户端与服务端socket,
设置了socket的IO模式为非阻塞,我猜测这样的好处是,一旦建立好链接好,客户端可以发送多个读写事件,而不会阻塞。
在方法结束的地方,有一行很重要的方法poller.register(socketWrapper);
它负责利用NIO的方式,注册服务端感情兴趣的可读事件(SelectionKey.OP_READ),不过这里并没有真正的进行注册,仅仅是封装成了Event,然后放在Poller的SynchronizedQueue队列中。

 private final SynchronizedQueue events = new SynchronizedQueue<>();

 public void register(final NioSocketWrapper socketWrapper) {
            socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
            PollerEvent pollerEvent = createPollerEvent(socketWrapper, OP_REGISTER);
            addEvent(pollerEvent);
        }

 private void addEvent(PollerEvent event) {
            events.offer(event);
            if (wakeupCounter.incrementAndGet() == 0) {
                // 重点!!!事件注册好后,唤醒一次selector,进行处理,否则selector会阻塞
                selector.wakeup();
            }
        }


现在是时候看看Poller这个线程在干什么事情了,直接看他的run();

   @Override
        public void run() {
            // Loop until destroy() is called
            while (true) {

                boolean hasEvents = false;

                try {
                    if (!close) {
                        // 这里主要是处理register,每当有一个socket链接请求到来的时候
                        // 每个socket需要注册自己感兴趣的时间
                        // 这里的socket请求来源是在Acceptor中
                        // 也就是说Acceptor不停的往events队列中投放事件
                        // poller不停的处理事件
                        // 因此这里最重要的就是理解 每一个事件是什么
                        // todo 要对这一块理解透彻,必须对非阻塞IO有非常深入的了解
                        // todo 再次加深理解
                        hasEvents = events();

                        // 每当有一个新的链接请求来的时候,都必须现在selector中注册自己感兴趣的事件
                        // 这样当客户端发送事件后,才能够处理事件。
                        // 然而,下面的select()只有当有事件来的时候,才会唤醒继续向下执行
                        if (wakeupCounter.getAndSet(-1) > 0) {
                            // If we are here, means we have other stuff to do
                            // Do a non blocking select
                            keyCount = selector.selectNow();
                        } else {
                            keyCount = selector.select(selectorTimeout);
                        }
                        wakeupCounter.set(0);
                    }
                    if (close) {
                        events();
                        timeout(0, false);
                        try {
                            selector.close();
                        } catch (IOException ioe) {
                            log.error(sm.getString("endpoint.nio.selectorCloseFail"), ioe);
                        }
                        break;
                    }
                    // Either we timed out or we woke up, process events first
                    if (keyCount == 0) {
                        hasEvents = (hasEvents | events());
                    }
                } catch (Throwable x) {
                    ExceptionUtils.handleThrowable(x);
                    log.error(sm.getString("endpoint.nio.selectorLoopError"), x);
                    continue;
                }

                Iterator iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
                // 重点理解 Walk through the collection of ready keys and dispatch any active event.
                while (iterator != null && iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    iterator.remove();
                    // attachment 怎么来的,是在注册的时候得到的。
                    // todo 理解一下attachment工作原理
                    NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();
                    // Attachment may be null if another thread has called
                    // cancelledKey()
                    if (socketWrapper != null) {
                        // sk主要用来判断事件的类型,事件的内容在附件中
                        processKey(sk, socketWrapper);
                    }
                }

                // Process timeouts
                timeout(keyCount,hasEvents);
            }

            getStopLatch().countDown();
        }

在每次循环中,先看看是否有新的请求链接利用NIO注册感兴趣的事件,如果有,那么就进行处理。如果没有就看看是否有IO事件,如果有则调用processKey()进行处理。
这里有一个疑问,为什么有了Acceptor还要有Poller呢?为什么不能在Acceptor中做这些事情。
Acceptor主要是负责链接的建立,那么谁来负责处理链接,另外,当链接好后,发生了读写事件了,该有谁来统一管理呢?Poller扮演了这个角色。
第二个疑问是,为什么要在Poller中来实现真正的注册,而不是在Acceptor,要理解这一点必须要对NIO有一定了解。在NIO中Selector的register()和select()都是阻塞式的,对于一个socke来说,肯定式要register()再select()。先现在假设第一个客户端A发来链接请求,Poller由于在启动阶段线程就一直在运行了,但是由于没有事件发生,因此被阻塞在select()方法,此时虽然A客户端发来了请求,但是由于阻塞的原因,并不能注册成功,没有注册成功,自然也就没有读写事件,就这样一直等待着,那这样岂不死锁了?为了解决这个问题,才有addEvent()结尾处有一个唤醒的方法。

Poller循环中不仅负责老板交给的任务,还要分派任务,下面看下Poller如何处理Socket?Poller会通过NIO模型,当有IO事件到来时候,调用processKey()->processSocket()处理请求。而在processSocket()中,会把每个IO事件封装成SocketProcessorBase任务,交给wokers线程池执行。

    public boolean processSocket(SocketWrapperBase socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase sc = null;
            if (processorCache != null) {
                sc = processorCache.pop();
            }
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            // 注意这里的Executor,其实就是真正干活的workers
            // workers的大小和一些信息,已经在初始化的方法中统一做了,需要注意的是,使用linkQ作为阻塞队列。
            // 那么这样的话,大量的请求如果不能及时处理,阻塞队列可能爆满,不能再执行新的任务,
            // 因此tomcat会取消此次读事件!!并且关闭socket链接

            // 这里大概是这样的,每个socket链接建立好后,会有大量的读事件进行处理
            // 因此这里委派给了workers线程池来处理
            // 而我们的具体的任务是前面的SocketProcessorBase 他是一个Runnable实现的对象
            // 因此要看
            Executor workers = getExecutor();
            if (dispatch && workers != null) {
                workers.execute(sc);
            } else {
                sc.run();
            }
        } catch (RejectedExecutionException ree) {
            getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
            return false;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            // This means we got an OOM or similar creating a thread, or that
            // the pool and its queue are full
            getLog().error(sm.getString("endpoint.process.fail"), t);
            return false;
        }
        return true;
    }

这里我们可以把Acceptor理解我们的老板,我们TL就是那个接老板活的人(Poller),然后指定谁来干。嗯。。。。这里的"我们",就是真正干活的workers。tomcat利用这三个组件,把相关的职责分开了,系统更加清晰,也更加高效(两个线程),值得学习。另外也可以看到,Acceptor和Poller都是单线程,因为他们只分派任务,所以不存在性能瓶颈,而workers则必须以线程池的方式来运行了。所以在做系统设计的时候,也要注意哪里才是最需要资源的地方。
woker工作内容:在前面,每个IO事件,被封装成SocketProcessorBase任务,交给线程池运行,因此我们关注下SocketProcessorBase的run(),它会调用doRun()

  @Override
        protected void doRun() {
                  ...
                if (handshake == 0) {
                    SocketState state = SocketState.OPEN;
                    // Process the request from this socket
                    if (event == null) {
                        state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                    } else {
                        state = getHandler().process(socketWrapper, event);
                    }
                    if (state == SocketState.CLOSED) {
                        poller.cancelledKey(getSelectionKey(), socketWrapper);
                    }
                } else if (handshake == -1 ) {
                    getHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL);
                    poller.cancelledKey(getSelectionKey(), socketWrapper);
                } else if (handshake == SelectionKey.OP_READ){
                    socketWrapper.registerReadInterest();
                } else if (handshake == SelectionKey.OP_WRITE){
                    socketWrapper.registerWriteInterest();
                }
            } catch (CancelledKeyException cx) {
                poller.cancelledKey(getSelectionKey(), socketWrapper);
            } catch (VirtualMachineError vme) {
                ExceptionUtils.handleThrowable(vme);
            } catch (Throwable t) {
                log.error(sm.getString("endpoint.processing.fail"), t);
                poller.cancelledKey(getSelectionKey(), socketWrapper);
            } finally {
                socketWrapper = null;
                event = null;
                //return to cache
                if (running && processorCache != null) {
                    processorCache.push(this);
                }
            
        }

方法省略了很多内容,核心看下getHandler().process(socketWrapper, event);这里获取到的Handler,就是在前面启动Connector创建的ConnectionHandler。ConnectionHandler处理的主要内容,根据具体协议
找到对应的Processor去处理这个socket。

Processor processor = (Processor) wrapper.takeCurrentProcessor();
  if (processor == null) {
                    // 根据我们指定的协议,去创建对应的处理器
                    processor = getProtocol().createProcessor();
                    register(processor);
                    if (getLog().isDebugEnabled()) {
                        getLog().debug(sm.getString("abstractConnectionHandler.processorCreate", processor));
                    }
                }
  // 根据具体协议去处理请求事件了
                    state = processor.process(wrapper, status);

前面我在Connector启动的时候,创建了一个默认协议Http11NioProtocol,
因此getProtocol()返回Http11NioProtocol,然后通过它创建一个Processor,它会生成以Http11Processor。

 Http11Processor继承自AbstractProcessor
 public AbstractProcessor(AbstractEndpoint endpoint) {
        this(endpoint, new Request(), new Response());
    }

下面看下Http11Processor是如何进行处理的,这里又用到了模板方法(Http11Processor继承自AbstractProcessorLight,实现了Processor接口):process->service();

   @Override
    public SocketState service(SocketWrapperBase socketWrapper)
        throws IOException {
getAdapter().service(request, response);

这里可以看到service的入参是SocketWrapperBase,数据被解析进了request,和response两个对象中。另外前面Connector启动的时候,也指定了适配器是,CoyoteAdapter,因此最终是调用在CoyoteAdapter.service(request, response).
到了这里我们可以看到客户端发送数据后,服务端利用NIO收到可读事件,获取数据后,把内容转换成了request和response,然后利用适配器去处理。

你可能感兴趣的:(后端)