相关文章推荐
直爽的风衣  ·  Git remote prune、Git ...·  1 年前    · 
温暖的鸵鸟  ·  Springboot -- ...·  1 年前    · 
三行代码带你了解Tomcat的基本原理

三行代码带你了解Tomcat的基本原理

毕业工作以来,一直在做跟Web相关的后端开发工作,也自称Web全栈工程师,虽然很多时候都在写CURD,对于一直在用tomcat和 Spring Mvc 也未真正深入了解过,趁国庆假期,看了一下tomcat的架构和代码,并对代码运行过程进行了跟踪,基本理清了它的原理和运行过程。

对于一次http的请求过程,无非是客户端跟服务端建立一个TCP连接,然后跟服务端发起一次GET/POST请求,服务端再返回对应的数据。这是简化之后的请求过程:

那么,怎么实现这个程序呢?最简单的,应该有一部分,是负责底层的Socket连接与请求报文的数据解析,这块是tomcat的工作,然后将这个请求,再转给SpringMvc进行处理,SpringMvc根据请求路径,Dispatch到这个路径绑定的处理函数去处理,并返回数据,再由tomcat输出给客户端。那么,顺着这个思路,我下面这一过程进行跟踪,看是否跟自己的设想吻合。

0.准备工作

学习一个软件,最好的方法就是把它跑起来,并找到一条线索,打断点进行跟踪调试,这里我利用 Spring Initializer 初始化一个带有 Spring Web Spring Boot 工程,下载后用 IDEA 打开,并在程序入口处增加了一个api,总共三行代码,然后直接DEBUG运行即可。

@RequestMapping("/")
@ResponseBody
public String home(@RequestParam("name") String name) { return "Hello " + name; }

1. 分析过程

1.1 Tomcat对Socket连接的处理

程序运行起来之后,我们先将程序暂停一下,先分析一下,目前有哪些线程,并且这些线程停在哪里,可以看到有以下几个比较重要的线程,我们来逐个分析一下:

1.1.1 `http-nio-8080-Acceptor-0`

//org.apache.tomcat.util.net.NioEndpoint
protected class Acceptor extends AbstractEndpoint.Acceptor {
    @Override
    public void run() {
        while (running) {
                //...
            try {
                SocketChannel socket = null;
                try {
                    // Accept the next incoming connection from the server
                    // socket
                    socket = serverSock.accept();
                } catch (IOException ioe) {
                    //...
                            // Configure the socket
                if (running && !paused) {
                    // setSocketOptions() will hand the socket off to
                    // an appropriate processor if successful
                    if (!setSocketOptions(socket)) {
                        closeSocket(socket);
                } else {
                    closeSocket(socket);
            } catch (Throwable t) {

可以看到,这里最重要的代码就是 serverSock.accept(); ,这里是tomcat接受客户端连接的入口,获取socket之后,再通过 setSocketOptions(socket) 将这个socket交给Poller线程;

1.1.2 `http-nio-8080-ClientPoller-0`

//org.apache.tomcat.util.net.NioEndpoint
public class Poller implements Runnable {
     * The background thread that adds sockets to the Poller, checks the
     * poller for triggered events and hands the associated socket off to an
     * appropriate processor as events occur.
    @Override
    public void run() {
        // Loop until destroy() is called
        while (true) {
            boolean hasEvents = false;
            try {
                if (!close) {
                    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);
            } catch (Throwable x) {
            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();
                NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
                // Attachment may be null if another thread has called
                // cancelledKey()
                if (attachment == null) {
                    iterator.remove();
                } else {
                    iterator.remove();
                    processKey(sk, attachment);
            }//while
        }//while

这里比较重要的就是 keyCount = selector.select(selectorTimeout); ,接受acceptor的socket,然后再调用 processKey(sk, attachment); ,将socket传给worker线程进行处理

1.1.3 `http-nio-8080-exec-0`

//org.apache.tomcat.util.net.NioEndpoint
protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
    @Override