Tomcat源码分析2 之 Protocol实现分析

简介

本文继续以tomcat8 为例,简单分析下Tomcat的几种protocol的差异,以及处理链接的细节。
  主要有以下内容:</br>
  - tomcat protocol的分类</br>
  - tomcat protocol的实现</br>
  - 各个protocol的差异</br>

Tomcat protocol 配置

参考官方文档,tomcat protocol配置,可以看到protocol主要是有四种,默认使用的HTTP/1.1 ,对于tomcat8 以更高版本来说,HTTP/1.1的配置会默认使用nio来处理,也就是org.apache.coyote.http11.Http11NioProtocol

其他的几种:</br>
  org.apache.coyote.http11.Http11Protocol java的bio connector,使用ServerSocket处理请求。</br>
  org.apache.coyote.http11.Http11NioProtocol java的nio connector,使用SocketChannel处理请求.</br>
  org.apache.coyote.http11.Http11Nio2Protocol java7新出的aio connector,使用AsynchronousSocketChannel处理请求。</br>
  org.apache.coyote.http11.Http11Nio2Protocol tomcat的native library connector。(这个我们稍后再讲)</br>
  
  对于tomcat8 以更高版本来说,HTTP/1.1的配置会默认使用nio来处理,也就是
org.apache.coyote.http11.Http11NioProtocol。由org.apache.catalina.connector.ConnectorsetProtocol()方法也可以看到:

    public void setProtocol(String protocol) {
        //如果配置了apr,会默认使用apr(apr后面再讲)
        if (AprLifecycleListener.isAprAvailable()) {
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpAprProtocol");
            } else if (protocol != null) {
                setProtocolHandlerClassName(protocol);
            } else {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11AprProtocol");
            }
        } else {
           //这里可以看到,HTTP/1.1是server.xml的默认配置,会默认使用nio处理
            if ("HTTP/1.1".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.http11.Http11NioProtocol");
            } else if ("AJP/1.3".equals(protocol)) {
                setProtocolHandlerClassName
                    ("org.apache.coyote.ajp.AjpNioProtocol");
            } else if (protocol != null) {
            //其他情况下,使用指定的protocol
                setProtocolHandlerClassName(protocol);
            }
        }

    }

Tomcat protocol的处理流程

ConnectorstartInternal()方法会启动protocol

    @Override
    protected void startInternal() throws LifecycleException {

        // Validate settings before starting
        ...
        try {
            protocolHandler.start();
        } catch (Exception e) {
            ...
        }
    }

这里的protocolHandler,就是上面setProtocol()方法指定的protocol。暂时以Http11NioProtocol为例,分析下请求处理流程。上面startInternal()方法接下来会到org.apache.coyote.AbstractProtocolstart()方法。

    @Override
    public void start() throws Exception {
        if (getLog().isInfoEnabled())
            getLog().info(sm.getString("abstractProtocolHandler.start",
                    getName()));
        try {
            //每个protocol都有一个对应的enpoint,最终是由endpoint来负责处理链接的。
            endpoint.start();
        } catch (Exception ex) {
            getLog().error(sm.getString("abstractProtocolHandler.startError",
                    getName()), ex);
            throw ex;
        }
    }

每个protocol对应了一个Endpoint</br>
  Http11Protocol对应org.apache.tomcat.util.net.JIoEndpoint</br>
  Http11NioProtocol对应org.apache.tomcat.util.net.NioEndpoint</br>
  Http11Nio2Protocol对应org.apache.tomcat.util.net.Nio2Endpoint</br>
  继续查看Nio2EndpointstartInternal()方法.

    @Override
    public void startInternal() throws Exception {

        if (!running) {
            ...
            //初始化最大连接数限制,在server.xml中可配置
            initializeConnectionLatch();

            // Start poller threads
            // poller 主要负责检查各个 Selector 的状态以及处理超时等
            pollers = new Poller[getPollerThreadCount()];
            for (int i=0; i<pollers.length; i++) {
                pollers[i] = new Poller();
                Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
                pollerThread.setPriority(threadPriority);
                pollerThread.setDaemon(true);
                pollerThread.start();
            }

            //启动接受链接的线程
            startAcceptorThreads();
        }
    }

startAcceptorThreads()方法会启动Endpoint内部的Acceptor。继续查看org.apache.tomcat.util.net.NioEndpoint.Acceptorrun()方法:

        @Override
        public void run() {
            ...
            // Loop until we receive a shutdown command
            while (running) {
                ...
                try {
                    //检查下当前是否已经达到了最大链接数
                    //if we have reached max connections, wait
                    countUpOrAwaitConnection();

                    SocketChannel socket = null;
                    try {
                        // Accept the next incoming connection from the server socket
                        socket = serverSock.accept();
                    } catch (IOException ioe) {
                       ...
                    }
                    // Successful accept, reset the error delay
                    errorDelay = 0;

                    // setSocketOptions() will add channel to the poller if successful
                    // setSocketOptions 会把channel添加到poller
                    if (running && !paused) {
                        if (!setSocketOptions(socket)) {
                            countDownConnection();
                            closeSocket(socket);
                        }
                    } else {
                        countDownConnection();
                        closeSocket(socket);
                    }
                } ...
            }
            state = AcceptorState.ENDED;
        }
    }

从代码中可以看到,使用 SocketChannel 接受连接,然后通过 setSocketOptions 方法把 channel 交给 poller 处理。setSocketOptions(SocketChannel socket) 方法则是真正处理链接的地方。

    protected boolean setSocketOptions(SocketChannel socket) {
        // Process the connection
        try {
            //disable blocking, APR style, we are gonna be polling it
            socket.configureBlocking(false);
            Socket sock = socket.socket();
            socketProperties.setProperties(sock);
            NioChannel channel = nioChannels.pop();
            ...
            //会把 调用poller的 register 方法,把 channel 交给 poller
            getPoller0().register(channel);
        } catch (Throwable t) {
           ...
        }
        return true;
    }

getPoller0() 会根据设置的poll线程数,返回一个当前可用的 Poller 对象,使用Round robin算法实现一个简单的负载均衡。

    public Poller getPoller0() {
        int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;
        return pollers[idx];
    }

接下来查看 register()方法

        public void register(final NioChannel socket) {
            socket.setPoller(this);
            // 新建一个 KeyAttachment 对象,保存跟socket相关的一些信息
            KeyAttachment ka = new KeyAttachment(socket);
            ka.setPoller(this);
            ka.setTimeout(getSocketProperties().getSoTimeout());
            ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
            ka.setSecure(isSSLEnabled());
            PollerEvent r = eventCache.pop();

            ka.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.
            if ( r==null) r = new PollerEvent(socket,ka,OP_REGISTER);
            else r.reset(socket,ka,OP_REGISTER);
            //比较重要的是这句,把 r 添加到当前的 events 队列中。
            addEvent(r);
        }

上面可以看到,每个请求进来之后,会通过 register 方法创建一个 PollerEvent 对象并添加到当前的 SynchronizedQueue<PollerEvent> events 队列中。
接下来看 Poller的run()方法,上面已经说过,Poller 在startInternal()方法调用的时候创建并启动。

        @Override
        public void run() {
            // Loop until destroy() is called
            while (true) {
                try {
                    ...
                    boolean hasEvents = false;
                    // Time to terminate?
                    if (close) {
                        events();
                        timeout(0, false);
                        ...
                        break;
                    } else {
                        //调用 events() 方法
                        hasEvents = events();
                    }
                }
              ...
            }//while
            stopLatch.countDown();
        }

接下来查看events()方法

        public boolean events() {
            boolean result = false;
            PollerEvent pe = null;
            while ( (pe = events.poll()) != null ) {
                result = true;
                try {
                    //调用 PollerEvent 的 run() 方法
                    pe.run();
                    pe.reset();
                    if (running && !paused) {
                        eventCache.push(pe);
                    }
                } catch ( Throwable x ) {
                    log.error("",x);
                }
            }
            return result;
        }

可以看到,在tomcat启动之后,会启动相应的endpoint的Acceptor来接受请求,同时启动相应的Poller来处理请求。</br>
  PollerEvent的run方法

        @Override
        public void run() {
            //对于新增的链接,会注册给 poller 的 selector 处理。
            if ( interestOps == OP_REGISTER ) {
                try {
                //把当前的key注册给poller中的selector对象,准备后续处理。
                    socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, key);
                } catch (Exception x) {
                    log.error("", x);
                }
            } else {
                ...
            }//end if
        }//run

再回过来看 Poller 的 run() 方法。里面有一段

                    while (iterator != null && iterator.hasNext()) {
                        SelectionKey sk = iterator.next();
                        KeyAttachment attachment = (KeyAttachment)sk.attachment();
                        // Attachment may be null if another thread has called
                        // cancelledKey()
                        if (attachment == null) {
                            iterator.remove();
                        } else {
                            attachment.access();
                            iterator.remove();
                            processKey(sk, attachment);
                        }
                    }//while

会检查selector所有的key,然后处理调用processKey()进行处理。
processKey() 会根据配置的Executer调用SocketProcessorrun() 方法.

接下来看SocketProcessorrun()方法最终会调用doRun()方法,在doRun()里有如下代码:

        if (status == null) {
            state = handler.process(ka, SocketStatus.OPEN_READ);
       } else {
          state = handler.process(ka, status);
       }

接下来会调用到handlerprocess方法,这个handler就是在Http11NioProtocol的构造方法里由setHandler设置的handler,也就是Http11ConnectionHandler 继承了 AbstractConnectionHandler,接着来到 org.apache.coyote.AbstractProtocol.AbstractConnectionHandlerprocess方法,这里会对每个socket做处理。包括调用具体的servlet处理业务等等。

总结

tomcat在启动的时候,根据配置的protocol,启动不同的Endpoint中的Acceptor 和 Poller。Acceptor负责接受请求,Poller负责调用线程池执行业务。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,076评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,658评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,732评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,493评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,591评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,598评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,601评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,348评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,797评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,114评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,278评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,953评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,585评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,202评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,180评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,139评论 2 352

推荐阅读更多精彩内容