关于后端:Tomcat8二链接建立与请求处理Poller

53次阅读

共计 8026 个字符,预计需要花费 21 分钟才能阅读完成。

承受申请:
当客户端发来新的申请后,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<PollerEvent> 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<SelectionKey> 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<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {if (socketWrapper == null) {return false;}
            SocketProcessorBase<S> 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,而后利用适配器去解决。

正文完
 0