龙空技术网

tomcat是如何处理HTTP长连接的

IT知识分享官 169

前言:

而今兄弟们对“http的长连接”大致比较看重,看官们都需要知道一些“http的长连接”的相关资讯。那么小编在网络上搜集了一些关于“http的长连接””的相关内容,希望各位老铁们能喜欢,咱们快快来学习一下吧!

1、HTTP长连接

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

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

2、tomcat处理长连接

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

scss

复制代码

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 timeoutstimeout(keyCount,hasEvents); }getStopLatch().countDown();}

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

ini

复制代码

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 57943 log.warn(sm.getString("endpoint.nio.timeoutCme"), cme); } // For logging purposes only long prevExp = nextExpiration; // nextExpiration重新赋值 当前时间+1s,socketProperties.getTimeoutInterval()默认1000nextExpiration = 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已经默认开启长连接)

scss

复制代码

if (keptAlive) {// Haven't read any request data yet so use the keep-alive// timeout. wrapper.setReadTimeout(keepAliveTimeout);}

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

erlang

复制代码

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进行关闭

标签: #http的长连接