Jetty 8 长连接的超时断开连接的机制:超时连接机制针对IO传输过程中的数据阻塞时间超过一定阈值时,断开该连接。阻塞指当前处于数据传输阶段,但是连续指定时间内都没有发出或者接收到任何数据时,Jetty系统断开该连接。强调一下,只有在数据传输过程中才会有超时机制。在服务端处理已经收到的数据时是不会检测该超时时间的。

下面看一下具体的代码实现。在jetty 8.1.17版本中,由以下代码控制一个连接的空闲、非空闲和断开检查方法,在SelectChannelEndpoint类中:

/* ------------------------------------------------------------ */publicvoid setCheckForIdle(boolean check){if (check)    {        _idleTimestamp=System.currentTimeMillis();        _checkIdle=true;    }else        _checkIdle=false;}

/* ------------------------------------------------------------ */publicboolean isCheckForIdle(){return _checkIdle;}

/* ------------------------------------------------------------ */protectedvoid notIdle(){    _idleTimestamp=System.currentTimeMillis();}

/* ------------------------------------------------------------ */publicvoid checkIdleTimestamp(long now){if (isCheckForIdle() && _maxIdleTime>0)    {finallong idleForMs=now-_idleTimestamp;

if (idleForMs>_maxIdleTime)        {// Don't idle out again until onIdleExpired task completes.            setCheckForIdle(false);            _manager.dispatch(new Runnable()            {publicvoid run()                {try                    {                        onIdleExpired(idleForMs);                    }finally                    {                        setCheckForIdle(true);                    }                }            });        }    }}

几个关键点地方:当数据传输的过程中,发现无法接收到和写出数据时,会调用setCheckForIdle(true)方法,从当前时间点开始计时,当后台select线程发现该连接的空闲时间达到阈值时,则调用onIdleExpired方法。还有一种场景是,在一个请求结束后,立即将该请求置为空闲状态。直到连接关闭或者该连接上面来了新的请求。另外,每个新的连接建立时,会在构造函数中默认调用一次该方法设置连接为空闲状态。

在哪些情况下会调用相反的设置呢,即将该连接置为非空闲状态的setCheckForIdle(false)方法,和刷新当前的idle时间方法notIdle()呢?第一个方法每次收到一个请求的数据提交后端的servlet的时候调用,后一个方法在每次刷出或者读到数据时调用。这样确保后端的servlet在处理数据时,不至于因为处理时间过长而被自己的select线程给关闭了。

这一次jetty的bug正是出在上述的每个请求的数据收集完成进入后端处理之前发生的。看如下代码:

AsyncHttpConnection类中,handle方法:

@Overridepublic Connection handle() throws IOException{    Connection connection = this;boolean some_progress=false;boolean progress=true;

try    {        setCurrentConnection(this);

// don't check for idle while dispatched (unless blocking IO is done).        _asyncEndp.setCheckForIdle(false);

// While progress and the connection has not changedwhile (progress && connection==this)        {            progress=false;try            {// Handle resumed requestif (_request._async.isAsync())                {if (_request._async.isDispatchable())                       handleRequest();                }// else Parse more inputelseif (!_parser.isComplete() && _parser.parseAvailable())                    progress=true;

// Generate more outputif (_generator.isCommitted() && !_generator.isComplete() && !_endp.isOutputShutdown() && !_request.getAsyncContinuation().isAsyncStarted())if (_generator.flushBuffer()>0)                        progress=true;

// Flush output                _endp.flush();

// Has any IO been done by the endpoint itself since last loopif (_asyncEndp.hasProgressed())                    progress=true;            }catch (HttpException e)            {if (LOG.isDebugEnabled())                {                    LOG.debug("uri="+_uri);                    LOG.debug("fields="+_requestFields);                    LOG.debug(e);                }                progress=true;                _generator.sendError(e.getStatus(), e.getReason(), null, true);            }finally            {                some_progress|=progress;//  Is this request/response round complete and are fully flushed?boolean parserComplete = _parser.isComplete();boolean generatorComplete = _generator.isComplete();boolean complete = parserComplete && generatorComplete;if (parserComplete)                {if (generatorComplete)                    {// Reset the parser/generator                        progress=true;

// look for a switched connection instance?if (_response.getStatus()==HttpStatus.SWITCHING_PROTOCOLS_101)                        {                            Connection switched=(Connection)_request.getAttribute("org.eclipse.jetty.io.Connection");if (switched!=null)                                connection=switched;                        }

                        reset();

// TODO Is this still required?if (!_generator.isPersistent() && !_endp.isOutputShutdown())                        {                            LOG.warn("Safety net oshut!!!  IF YOU SEE THIS, PLEASE RAISE BUGZILLA");                            _endp.shutdownOutput();                        }                    }else                    {// We have finished parsing, but not generating so// we must not be interested in reading until we// have finished generating and we reset the generator                        _readInterested = false;                        LOG.debug("Disabled read interest while writing response {}", _endp);                    }                }

if (!complete && _request.getAsyncContinuation().isAsyncStarted())                {// The request is suspended, so even though progress has been made,// exit the while loop by setting progress to false                    LOG.debug("suspended {}",this);                    progress=false;                }            }        }    }finally    {        setCurrentConnection(null);

// If we are not suspendedif (!_request.getAsyncContinuation().isAsyncStarted())        {// return buffers            _parser.returnBuffers();            _generator.returnBuffers();

// reenable idle checking unless request is suspended            _asyncEndp.setCheckForIdle(true);        }

// Safety net to catch spinningif (some_progress)            _total_no_progress=0;else        {            _total_no_progress++;if (NO_PROGRESS_INFO>0 && _total_no_progress%NO_PROGRESS_INFO==0 && (NO_PROGRESS_CLOSE<=0 || _total_no_progress< NO_PROGRESS_CLOSE))                LOG.info("EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this);if (NO_PROGRESS_CLOSE>0 && _total_no_progress==NO_PROGRESS_CLOSE)            {                LOG.warn("Closing EndPoint making no progress: "+_total_no_progress+" "+_endp+" "+this);if (_endp instanceof SelectChannelEndPoint)                    ((SelectChannelEndPoint)_endp).getChannel().close();            }        }    }return connection;}

可以看到,在handle方法进入时,调用了一次:

// don't check for idle while dispatched (unless blocking IO is done)._asyncEndp.setCheckForIdle(false);

如果当前连接是一个短连接,那么这里调用完全没问题。请求处理完成后本来就可能立即断开连接。但是如果是一个长连接,该连接在处理完请求后,可能“休息”一段时间继续处理新的请求,那么就问题就来了,从该代码看,jetty在handle方法的while循环中处理多个请求,这样可以避免同一个连接上面的多个请求被分到不同的线程中处理,而是绑定在一个线程上面处理,当长连接上面的请求比较“密集”(请求之间间隔极短)时,该while会循环多次,有两种情况会进入该请求:1、一个请求上面的数据没有处理完,即

// else Parse more inputelseif (!_parser.isComplete() && _parser.parseAvailable()) progress=true;

这个代码控制的。

另外当一个请求处理完了,也会在finally里面走到progess=true上面。

//  Is this request/response round complete and are fully flushed?boolean parserComplete = _parser.isComplete();boolean generatorComplete = _generator.isComplete();boolean complete = parserComplete && generatorComplete;if (parserComplete){if (generatorComplete)    {// Reset the parser/generator        progress=true;
 

由这个控制。

问题出在第二个上面,当一个请求处理完成后,连接会被置为空闲状态。但是这里将progess设置为true,那么while循环立即准备读取下一个请求的数据,但是并没有将连接置为非空闲状态,此时如果服务端进入耗时较长的处理流程,那么可能不等到客户端超时,连接就被后台检查空闲连接的线程断开了。

因此这里很明显,jetty有bug,应该在最后的这段代码出补充

// don't check for idle while dispatched (unless blocking IO is done)._asyncEndp.setCheckForIdle(false);

这个调用。或者是在每次进入while循环时调用,而不是只在进入handle时调用。

该问题发生有几个关键点:长连接上面持续不断有新请求过来,并且新请求发起的时间距离上一个请求完成的时间间隔非常短。经过实测,python的http客户端在处理长连接上面,请求间隔非常短。而其他语言和库编写的客户端测试程序都有比较长的间隔,导致问题不易重现。附一个jetty的简易http长连接测试程序:

import httplib  

count=0conn = httplib.HTTPConnection("127.0.0.1", timeout=600)  while (count < 1000000):    conn.request("PUT","/")    res = conn.getresponse()      print res.status, res.reason      print res.read()        count += 1

在jetty上面讲超时时间配置尽可能短,在servlet里面处理请求时休眠一个大于等于超时时间的值,配合上述客户端,很容易重现问题。

Jetty 8长连接上的又一个坑的更多相关文章

  1. Win7上的ASP.NET MVC3项目在Win10上运行的一个坑

    先解释一下问题:我原来的电脑环境是Win7+VS2015,因为新换了个电脑环境变成Win10+VS2015了,所以就把原先的项目复制到新的机器上,那么问题来了,原先的一个项目在VS2015上打开竟然直 ...

  2. 文件上传的一个坑 Apache上传组件和SpringMVC自带上传冲突

    List list = upload.parseRequest(request); 接受不到数据,size=0; 原因就是下面这货造成的 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ bean id=&qu ...

  3. Comet:基于 HTTP 长连接的“服务器推”技术解析

    原文链接:http://www.cnblogs.com/deepleo/p/Comet.html 一.背景介绍 传统web请求,是显式的向服务器发送http Request,拿到Response后显示 ...

  4. Comet:基于 HTTP 长连接的“服务器推”技术

    “服务器推”技术的应用 请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档.教程.论坛.blog.wiki 和新闻.任何 Ajax 的新信息都能在这里找到. c ...

  5. Comet技术详解:基于HTTP长连接的Web端实时通信技术

    前言 一般来说,Web端即时通讯技术因受限于浏览器的设计限制,一直以来实现起来并不容易,主流的Web端即时通讯方案大致有4种:传统Ajax短轮询.Comet技术.WebSocket技术.SSE(Ser ...

  6. 转载:Comet:基于 HTTP 长连接的“服务器推”技术

    转自:http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ 很多应用譬如监控.即时通信.即时报价系统都需要将后台发生的变化实时传送到客户端而无须客 ...

  7. 也谈---基于 HTTP 长连接的“服务(转载)

    这里指讨论基于HTTP的推技术, 诸如flash,applet之类的东西不作分析, 他们就不能说是"纯粹"的浏览器应用了. 首先是一点背景知识, 大家都知道长连接避免了tcp连接的 ...

  8. [转载] Comet:基于 HTTP 长连接的“服务器推”技术

    转载自http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ “服务器推”技术的应用 传统模式的 Web 系统以客户端发出请求.服务器端响应的方式工 ...

  9. Comet:基于 HTTP 长连接的“服务器推”技术(转载)

    “服务器推”技术的应用 传统模式的 Web 系统以客户端发出请求.服务器端响应的方式工作.这种方式并不能满足很多现实应用的需求,譬如: 监控系统:后台硬件热插拔.LED.温度.电压发生变化: 即时通信 ...

随机推荐

  1. SQL 返回数量一定的行

    1. 限制返回的行 select top 10 * from tablename 2. 返回随机n行 select top n * from tablename order by newid()

  2. vs2013下使用Assist X的破解方法

    Assist X的破解下载:http://pan.baidu.com/s/1kTnDH23 密码:j9jp 01.安装,点击VA_X_Setup2042.exe 安装 02.破解 找到这样的目录:C: ...

  3. Seven Python Tools All Data Scientists Should Know How to Use

    Seven Python Tools All Data Scientists Should Know How to Use If you’re an aspiring data scientist, ...

  4. location.hash 详解

    前年9月twitter改版. 一个显著变化,就是URL加入了"#!"符号.比如,改版前的用户主页网址为 http://twitter.com/username 改版后,就变成了 h ...

  5. 独立两套DJANGO+CELERY配置(生产+测试)时要注意的一些细节

    1,生产的NGINX环境,要指定自己的目录,而不是PROJ默认的. upstream ism_host { server ; } server { listen ; server_name local ...

  6. SpringMVC与Struts2关于controller线程安全问题

    SpringMVC的controller是单例的,因此springMVC的controller不是线程安全的,在使用的时候要谨慎添加成员变量,因为所有的请求都会共享这个变量. 与springMVC不同 ...

  7. UL LI 布局 TAB 切换条

    web页面实现tab的功能有几种实现方式,下面是使用UL LI DIV方式实现的tab. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Tr ...

  8. Ubuntu10.10 安装scim

    Ubuntu10.10 上没有找到默认的输入法,所以要安装一个中文输入法,网上好多介绍的,但都 不怎么好用,下面参考http://blog.csdn.net/caodesheng110/article ...

  9. 【Linux安全】文件或目录权限设置

    实例演示: 要求:新建文件夹,名称为secure,要求改文件夹只能被创建者deadmin以及与deadmin同组用户进行读.写.执行的权限, 其他用户均只有读的权限. 查看 一下secure原本的权限 ...

  10. absolute和relative的几个Demo

    这些例子最好通过FireFox结合FireBug调试查看 1.absolute让元素inline-block化 <!DOCTYPE html> <html xmlns="h ...