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. 【java】Servlet 工程 web.xml 中的 servlet 和 servlet-mapping 标签

    摘录某个工程的 web.xml 文件片段: 访问顺序为1—>2—>3—>4,其中2和3的值必须相同. url-pattern 标签中的值是要在浏览器地址栏中输入的 url,可以自己命 ...

  2. MVC-内容详情页显示内容

    @model InfoDataProvider.DataModel.FAQ_ContentUser 内容Content字段:如果里面有html标签. @Html.DisplayFor(p => ...

  3. LCA——倍增求解

    LCA,即最近公共祖先,用于解决树上两点的最近公共祖先问题. ; lca(1,2)=3;(原谅我的绘画水平) LCA的求解有三种算法(我知道的)——tarjan,倍增,线段树(我只会两种),NOIp之 ...

  4. .net entity framework 泛型 更新与增加记录

    static public bool SaveOrUpdate<T>(T entity) where T: class { bool result = false; using (wech ...

  5. 服务器环境搭建系列(四)-mysql篇

    1.按照上一篇服务器环境搭建系列(三)-JDK篇中的方法检查系统是否已经预装Mysql并卸载. 2.下载mysql,这里是MySQL-server-5.5.25-1.linux2.6.x86_64.r ...

  6. Python属性、方法和类管理系列之----__slots__属性

    一句话说明 __slots__是用来限制实例的属性的,__slots__可以规定实例是否应该有__dict__属性:__slots__不能限制类的属性. 只有__slots__列表内的这些变量名可赋值 ...

  7. Python的字符串操作和Unicode

    字符串类型 str:Unicode字符串.采用''或者r''构造的字符串均为str,单引号可以用双引号或者三引号来代替.无论用哪种方式进行制定,在Python内部存储时没有区别. bytes:二进制字 ...

  8. Git的一些基本概念

    Git的一些基本概念 设置自己的用户名和邮箱git config –global user.name "Your Name"git config –global user.emai ...

  9. matlab怎么同时显示imshow 两幅图片

    matlab怎么同时显示imshow 两幅图片 matlab怎么同时显示imshow 两幅图片 方法一:subplot()函数 subplot(2,1,1); subplot(2,1,2); 分上下或 ...

  10. Global中的事件执行顺序

    The Global.asax file, sometimes called the ASP.NET application file, provides a way to respond to ap ...