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

2024-03-01 0 846
目录
  • 1、HTTP长连接
  • 2、tomcat处理长连接
  • 3、总结

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) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悠久资源 Tomcat 一文详解tomcat是如何处理HTTP长连接的 https://www.u-9.cn/server/tomcat/175322.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务