zoukankan      html  css  js  c++  java
  • 彻底掌握网络通信(七)ConnectionReuseStrategy,ConnectionKeepAliveStrategy解析

    网络通信系列文章序

    彻底掌握网络通信(一)Http协议基础知识
    彻底掌握网络通信(二)Apache的HttpClient基础知识
    彻底掌握网络通信(三)Android源码中HttpClient的在不同版本的使用
    彻底掌握网络通信(四)Android源码中HttpClient的发送框架解析
    彻底掌握网络通信(五)DefaultRequestDirector解析
    彻底掌握网络通信(六)HttpRequestRetryHandler解析
    彻底掌握网络通信(七)ConnectionReuseStrategy,ConnectionKeepAliveStrategy解析
    彻底掌握网络通信(八)AsyncHttpClient源码解读
    彻底掌握网络通信(九)AsyncHttpClient为什么无法用Fiddler来抓包
    彻底掌握网络通信(十)AsyncHttpClient如何发送JSON解析JSON,以及一些其他用法

    前面简单说了下HttpRequestRetryHandler,这篇主要分析下ConnectionReuseStrategy,ConnectionKeepAliveStrategy,连接的重用和长连接

    1:基础介绍
    1.1)Keep-Alive解析
    http协议作为上层应用层协议,其是基于TCP/IP协议,UDP协议传输层上进行的;http协议通过socket这个套接字完成客户端和服务端的通信;
    http协议目前主要有两个版本HTTP 1.0和HTTP 1.1,他们都是无状态协议

    在HTTP 1.0中,每一次请求响应之后,下一次的请求需要断开之前的连接,再重新开始;

    在HTTP 1.1中,使用keep-alive在一次TCP连接中可以持续发送多份数据而不会断开连接。通过使用keep-alive机制,可以减少tcp连接建立次数,也意味着可以减少TIME_WAIT状态连接,以此提高性能和提高httpd服务器的吞吐率(更少的tcp连接意味着更少的系统内核调用,socket的accept()和close()调用)。

    因此便出现了Connection: keep-alive的设置,用于建立长连接,即我们所说的Keep-Alive模式;

    如上图,左图是HTTP 1.0 ; 右图是HTTP 1.1

    http 1.0中默认是关闭的,需要在http头加入”Connection: Keep-Alive”,才能启用Keep-Alive;
    在1.0中,如果客户端浏览器支持Keep-Alive,那么就在HTTP请求头中添加一个字段 Connection: Keep-Alive,当服务器收到附带有Connection: Keep-Alive的请求时,它也会在响应头中添加一个同样的字段来使用Keep-Alive。这样一来,客户端和服务器之间的HTTP连接就会被保持,不会断开(超过Keep-Alive规定的时间,意外断电等情况除外),当客户端发送另外一个请求时,就使用这条已经建立的连接

    http 1.1中默认启用Keep-Alive,如果加入”Connection: close “,才关闭。
    Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间;
    一次完成的http请求是否能够保持,同时也要靠服务端是否具备Keep-Alive能力;

    1.2)如何判断客户端已经完整的接收到服务端的数据,针对HTTP 1.0和HTTP 1.1
    在java中,使用socket编程的时候,我们经常看到如下代码

    Socket socket = new Socket("localhost",10086);
    InputStream is = socket.getInputStream();
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    String info = null;
    while((info=br.readLine()) != -1){

    }
    1
    2
    3
    4
    5
    6
    7
    所以在普通的socket编程中,我们可以使用EOF(-1)来判断是否完整的接收到服务端的返回的数据;这是因为在一次普通的http请求中,即没有添加Connection: Keep-Alive属性的http请求中,服务端响应之后,会断开连接,顾使用EOF判断是准确的;

    但是这种方式针对添加Connection: Keep-Alive属性的http请求来说,就无法生效了;
    我们可以通过头消息中的Conent-Length字段来判断;但是对于动态页面或者zip格式的内容,服务端一般会采用Transfer-Encoding: chunked”这样的方式来代替Content-Length;

    因此
    在Http 1.0及之前版本中,content-length字段可有可无。
    在http1.1及之后版本。如果是keep alive,则content-length和chunk必然是二选一。若是非keep alive,则和http1.0一样。content-length可有可无。

    2:在DefaultRequestDirector.execute方法中有如下代码

    // The connection is in or can be brought to a re-usable state.
    reuse = reuseStrategy.keepAlive(response, context);
    if(reuse) {
    // Set the idle duration of this connection
    long duration = keepAliveStrategy.getKeepAliveDuration(response, context);
    managedConn.setIdleDuration(duration, TimeUnit.MILLISECONDS);
    }
    1
    2
    3
    4
    5
    6
    7
    从这段代码可以看出,当完成一次http请求之后,并没有立即关闭这个tcp连接和释放资源;而是通过可重用策略来判断这个连接能否被保持,以及保持多长时间;

    我们先分析下如何判断这个连接是否是可重用的,我们可以通过上面的keepAlive方法来进行具体分析,reuseStrategy的默认实现者为DefaultConnectionReuseStrategy,我们看一下keepAlive方法

    //该方法的作用就是在一次请求之后,这个连接能够被保持
    //如果返回false,则调用者应该立即关闭连接
    //如果返回true,则调用者应该保持这个连接从而可以应用于其他请求
    public boolean keepAlive(final HttpResponse response,
    final HttpContext context) {
    if (response == null) {
    throw new IllegalArgumentException
    ("HTTP response may not be null.");
    }
    if (context == null) {
    throw new IllegalArgumentException
    ("HTTP context may not be null.");
    }

    HttpConnection conn = (HttpConnection)
    context.getAttribute(ExecutionContext.HTTP_CONNECTION);

    //当一个连接没有建立的时候,当然是不可重用的,返回false
    if (conn != null && !conn.isOpen())
    return false;
    // do NOT check for stale connection, that is an expensive operation

    // Check for a self-terminating entity. If the end of the entity will
    // be indicated by closing the connection, there is no keep-alive.
    HttpEntity entity = response.getEntity();
    ProtocolVersion ver = response.getStatusLine().getProtocolVersion();
    //当返回的entity的长度小于0,并且http协议版本号小于1.0,返回false
    if (entity != null) {
    if (entity.getContentLength() < 0) {
    if (!entity.isChunked() ||
    ver.lessEquals(HttpVersion.HTTP_1_0)) {
    // if the content length is not known and is not chunk
    // encoded, the connection cannot be reused
    return false;
    }
    }
    }

    // Check for the "Connection" header. If that is absent, check for
    // the "Proxy-Connection" header. The latter is an unspecified and
    // broken but unfortunately common extension of HTTP.
    //HTTP.CONN_DIRECTIVE的值为Connection,即获取响应头信息中的Connection字段的内容
    HeaderIterator hit = response.headerIterator(HTTP.CONN_DIRECTIVE);
    //如果没有这个头信息,则寻找Proxy-Connection的头信息
    if (!hit.hasNext()){
    //Proxy-Connection是http1.0 时代的产物。老旧的代理,如果设置 connection: keepalive,
    //代理原样转发给服务器,服务器会以为要建立长久连接,但是代理并不支持,这样就出问题了。
    //所以改为设置 proxy-connection: keepalive,如果是新的代理,
    //支持 keepalive,它会认得这个头,并改成 connection: keepalive 转发给服务器,
    //顺利建立持久连接;如果是老的代理,它不认识,会原样转发,这时候服务器也不会建立持久连接
    hit = response.headerIterator("Proxy-Connection");
    }

    if (hit.hasNext()) {
    try {
    TokenIterator ti = createTokenIterator(hit);
    boolean keepalive = false;
    while (ti.hasNext()) {
    final String token = ti.nextToken();
    //如果Connection:close则返回false
    if (HTTP.CONN_CLOSE.equalsIgnoreCase(token)) {
    return false;
    }//如果Connection:Keep-Alive则返回true
    else if (HTTP.CONN_KEEP_ALIVE.equalsIgnoreCase(token)) {
    // continue the loop, there may be a "close" afterwards
    keepalive = true;
    }
    }
    if (keepalive)
    return true;
    // neither "close" nor "keep-alive", use default policy

    } catch (ParseException px) {
    // invalid connection header means no persistent connection
    // we don't have logging in HttpCore, so the exception is lost
    return false;
    }
    }

    // default since HTTP/1.1 is persistent, before it was non-persistent
    return !ver.lessEquals(HttpVersion.HTTP_1_0);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    总结
    1:该方法的作用就是在一次请求之后,这个连接能否被保持,如果返回false,则调用者应该立即关闭连接;如果返回true,则调用者应该保持这个连接从而可以应用于其他请求
    2:由于HTTP 1.0时代,还不能有效支持connection,顾多了一个Proxy-Connection头信息,该字段的出现的原因是: 在HTTP 1.0中老旧的代理,如果设置 connection: keepalive,代理原样转发给服务器,服务器会以为要建立长久连接,但是代理并不支持,这样就出问题了。所以改为设置 proxy-connection: keepalive,如果是新的代理,支持 keepalive,它会认得这个头,并改成 connection: keepalive 转发给服务器,顺利建立持久连接;如果是老的代理,它不认识,会原样转发,这时候服务器也不会建立持久连接

    当一个请求视为可重用之后,即keepAlive返回true,那这么链接能一直保持链接状态?会不会超过一定时间,连接就断开?
    答案是会的,当链接超过预设时间,会自动断开;

    当一个链接是可重用的,我们就可以通过如下代码获得这个链接可以存活的时间

    keepAliveStrategy.getKeepAliveDuration
    1
    keepAliveStrategy的实现为DefaultConnectionKeepAliveStrategy

    public class DefaultConnectionKeepAliveStrategy implements ConnectionKeepAliveStrategy {

    public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
    if (response == null) {
    throw new IllegalArgumentException("HTTP response may not be null");
    }
    HeaderElementIterator it = new BasicHeaderElementIterator(
    //HTTP.CONN_KEEP_ALIVE的为“Keep-Alive”
    response.headerIterator(HTTP.CONN_KEEP_ALIVE));
    while (it.hasNext()) {
    HeaderElement he = it.nextElement();
    String param = he.getName();
    String value = he.getValue();
    if (value != null && param.equalsIgnoreCase("timeout")) {
    try {
    return Long.parseLong(value) * 1000;
    } catch(NumberFormatException ignore) {
    }
    }
    }
    return -1;
    }

    }

    代码很简单,就是通过Keep-Alive头信息中,获得timeout的值,作为超时时间;单位毫秒;

    如请求头中 Keep-Alive: timeout=5, max=100
    ---------------------
    作者:yi_master
    来源:CSDN
    原文:https://blog.csdn.net/yi_master/article/details/80595372
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    《Hadoop实战》第一章
    找工作必备技能
    范数的深刻解读(转自知乎)
    贝叶斯定理(贝叶斯分类)
    什么是机器学习?
    线性可分 与线性不可分
    正则化和归一化
    过拟合问题是什么?
    CVPR 2016 paper reading (6)
    CVPR 2016 paper reading (3)
  • 原文地址:https://www.cnblogs.com/softidea/p/11099424.html
Copyright © 2011-2022 走看看