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

 更新时间:2024年01月02日 11:36:55   作者:super凹凸曼  
HTTP长连接,也称为持久连接,是一种使用同一个TCP连接来发送和接收多个HTTP请求/应答的方法,那么tomcat作为最常用的WEB容器,是怎么处理HTTP的长连接呢,下面我们就来深入了解下吧
(福利推荐:【腾讯云】服务器最新限时优惠活动,云服务器1核2G仅99元/年、2核4G仅768元/3年,立即抢购>>>:9i0i.cn/qcloud

(福利推荐:你还在原价购买阿里云服务器?现在阿里云0.8折限时抢购活动来啦!4核8G企业云服务器仅2998元/3年,立即抢购>>>:9i0i.cn/aliyun

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长连接的资料请关注程序员之家其它相关文章!

相关文章

  • eclipse配置Tomcat和Tomcat出现无效端口解决办法

    eclipse配置Tomcat和Tomcat出现无效端口解决办法

    本文主要介绍了eclipse配置Tomcat和Tomcat出现无效端口解决办法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • 搭建Tomcat 8源码开发环境的步骤详解

    搭建Tomcat 8源码开发环境的步骤详解

    相信大家都知道开源软件tomcat目前几乎已经是Java web开发的必备软件了,目前有很多关于tomcat的书籍,已经通过配置对tomcat进行一些跟应用业务功能的调优,但感觉如果仅仅只是了解一些配置,可能稍微少了点什么,下面通过本文深入到源代码中进行学些和了解。
    2016-10-10
  • Eclipse创建tomcat实现过程原理详解

    Eclipse创建tomcat实现过程原理详解

    这篇文章主要介绍了Eclipse创建tomcat实现过程原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • Tomcat7.0安装配置详细(图文)

    Tomcat7.0安装配置详细(图文)

    这篇文章主要介绍了Tomcat7.0安装配置详细图文方法,需要的朋友可以参考下
    2014-07-07
  • Tomcat简单网站部署的三种方式小结

    Tomcat简单网站部署的三种方式小结

    本文主要介绍了Tomcat简单网站部署的三种方式小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • IDEA配置tomcat并发布web项目的超详细步骤

    IDEA配置tomcat并发布web项目的超详细步骤

    Tomcat是一个Java Web应用服务器,实现了多个Java EE规范(JSP、Java Servlet等),这篇文章主要给大家介绍了关于IDEA配置tomcat并发布web项目的超详细步骤,需要的朋友可以参考下
    2023-09-09
  • 解决tomcat启动报错:一个或多个listeners启动失败问题

    解决tomcat启动报错:一个或多个listeners启动失败问题

    这篇文章主要介绍了解决tomcat启动报错:一个或多个listeners启动失败问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-09-09
  • Tomcat在linux环境中开机自启(定时重启)的方法

    Tomcat在linux环境中开机自启(定时重启)的方法

    我们经常会遇到服务器断电或异常,而异常后tomcat中部署的web项目需要我手动去启动,为此,特别贡献出Linux环境中Tomcat开机自启的方式供学习使用,需要的朋友可以参考下
    2023-10-10
  • 修改Tomcat服务器默认端口号的实现方法

    修改Tomcat服务器默认端口号的实现方法

    这篇文章主要介绍了修改Tomcat服务器默认端口号的实现方法的相关资料,需要的朋友可以参考下
    2017-09-09
  • idea配置tomcat必坑指南图文详解

    idea配置tomcat必坑指南图文详解

    本文通过图文并茂的形式给大家介绍了idea配置tomcat必坑指南,对大家在日常工作学习有非常大的帮助,需要的朋友可以参考下
    2022-04-04

最新评论

?


http://www.vxiaotou.com