当前位置: 代码网 > 服务器>服务器>Tomcat > 一文详解tomcat是如何处理HTTP长连接的

一文详解tomcat是如何处理HTTP长连接的

2024年05月14日 Tomcat 我要评论
1、http长连接http长连接,也称为持久连接,是一种使用同一个tcp连接来发送和接收多个http请求/应答的方法,而不是为每一个新的请求/应答打开新的tcp连接。这种方式由于通信连接一直存在,因此

1、http长连接

http长连接,也称为持久连接,是一种使用同一个tcp连接来发送和接收多个http请求/应答的方法,而不是为每一个新的请求/应答打开新的tcp连接。这种方式由于通信连接一直存在,因此可以减少建立和关闭连接的开销,提高通信效率。因为http长连接的本质就是保持tcp的连接在每次请求响应之后不断开,与其说是http长连接,不如说是tcp的长连接。

那么tomcat作为最常用的web容器,是怎么处理http的长连接呢?

2、tomcat处理长连接

在tomcat的poller线程中,监听已连接套接字以保持连接,并轮询以检查数据是否可用。具体来说,poller线程使用nio架构,通过内部的selector对象向内核查询channel的状态,一旦发现可读事件,就会生成任务类socketprocessor,并将其交给executor去处理。

public void run() {
    // loop until destroy() is called
    while (true) {

        boolean hasevents = false;

        try {
            if (!close) {
                hasevents = events();
                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();
            niosocketwrapper socketwrapper = (niosocketwrapper) sk.attachment();
            // attachment may be null if another thread has called
            // cancelledkey()
            if (socketwrapper != null) {
                processkey(sk, socketwrapper);
            }
        }

        // process timeouts
        timeout(keycount,hasevents);
    }

    getstoplatch().countdown();
}

poller线程的run方法是while(true)死循环,主要监听注册的socket上是否有已就绪事件,如果有的话就调用processkey(sk, socketwrapper)方法交由线程池处理,最后调用了timeout方法。

protected void timeout(int keycount, boolean hasevents) {
    long now = system.currenttimemillis();
    // nextexpiration初始化是0
    if (nextexpiration > 0 && (keycount > 0 || hasevents) && (now < nextexpiration) && !close) {
        return;
    }
    int keycount = 0;
    try {
        // 遍历注册到selector上所有的socket
        for (selectionkey key : selector.keys()) {
            keycount++;
            niosocketwrapper socketwrapper = (niosocketwrapper) key.attachment();
            try {
                if (socketwrapper == null) {
                    // we don't support any keys without attachments
                    if (key.isvalid()) {
                        key.cancel();
                    }
                } else if (close) {
                    key.interestops(0);
                    // avoid duplicate stop calls
                    socketwrapper.interestops(0);
                    socketwrapper.close();
                // 如果注册的事件是读写事件
                } else if (socketwrapper.interestopshas(selectionkey.op_read) ||
                          socketwrapper.interestopshas(selectionkey.op_write)) {
                    boolean readtimeout = false;
                    boolean writetimeout = false;
                    // 检查读超时
                    if (socketwrapper.interestopshas(selectionkey.op_read)) {
                        // 用当前时间-上次读时间
                        long delta = now - socketwrapper.getlastread();
                        long timeout = socketwrapper.getreadtimeout();
                        if (timeout > 0 && delta > timeout) {
                            readtimeout = true;
                        }
                    }
                    // check for write timeout
                    if (!readtimeout && socketwrapper.interestopshas(selectionkey.op_write)) {
                        long delta = now - socketwrapper.getlastwrite();
                        long timeout = socketwrapper.getwritetimeout();
                        if (timeout > 0 && delta > timeout) {
                            writetimeout = true;
                        }
                    }
                    // 如果已经超时
                    if (readtimeout || writetimeout) {
                        key.interestops(0);
                        // avoid duplicate timeout calls
                        socketwrapper.interestops(0);
                        socketwrapper.seterror(new sockettimeoutexception());
                        if (readtimeout && socketwrapper.readoperation != null) {
                            if (!socketwrapper.readoperation.process()) {
                                socketwrapper.close();
                            }
                        } else if (writetimeout && socketwrapper.writeoperation != null) {
                            if (!socketwrapper.writeoperation.process()) {
                                socketwrapper.close();
                            }
                        // processsocket中对将socket进行关闭
                        } else if (!processsocket(socketwrapper, socketevent.error, true)) {
                            socketwrapper.close();
                        }
                    }
                }
            } catch (cancelledkeyexception ckx) {
                if (socketwrapper != null) {
                    socketwrapper.close();
                }
            }
        }
    } catch (concurrentmodificationexception cme) {
        // see https://bz.apache.org/bugzilla/show_bug.cgi?id=57943
        log.warn(sm.getstring("endpoint.nio.timeoutcme"), cme);
    }
    // for logging purposes only
    long prevexp = nextexpiration;
    // nextexpiration重新赋值 当前时间+1s,socketproperties.gettimeoutinterval()默认1000
    nextexpiration = system.currenttimemillis() +
            socketproperties.gettimeoutinterval();
    if (log.istraceenabled()) {
        log.trace("timeout completed: keys processed=" + keycount +
                "; now=" + now + "; nextexpiration=" + prevexp +
                "; keycount=" + keycount + "; hasevents=" + hasevents +
                "; eval=" + ((now < prevexp) && (keycount>0 || hasevents) && (!close) ));
    }

}

timeout方法主要做了以下事:

  • 判断是否要进行轮询所有socket进行超时判断
  • 遍历所有socket,拿到上次读写的事件,与当前时间对比,是否已超时
  • 如果已超时,对相关socket进行关闭处理
  • 重置nextexpiration值,默认每秒都会对所有socket进行超时轮询判断

在进行对socket读取时会把keepalivetimeout参数赋值给readtimeout(前提,开启长连接,tomcat已经默认开启长连接)

if (keptalive) {
    // haven't read any request data yet so use the keep-alive
    // timeout.
    wrapper.setreadtimeout(keepalivetimeout);
}

每次对socket进行读取后,也会调用updatelastread方法更新上次读取时间

if (to.remaining() >= limit) {
    to.limit(to.position() + limit);
    nread = fillreadbuffer(block, to);
    if (log.isdebugenabled()) {
        log.debug("socket: [" + this + "], read direct from socket: [" + nread + "]");
    }
    updatelastread();
}

3、总结

tomcat处理http长连接是在poller线程中的timeout方法,最长每秒都会对所有的socket进行遍历,上次读写数据的时间与当前时间和参数配置的keep-alive-timeout时间进行判断是否已经超时(前提开启长连接),如果已经超时则对相应的socket进行关闭

以上就是一文详解tomcat是如何处理http长连接的的详细内容,更多关于tomcat处理http长连接的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com