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长连接的资料请关注代码网其它相关文章!
发表评论