• 彻底掌握网络通信(七)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
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    451. Sort Characters By Frequency
    424. Longest Repeating Character Replacement
    68. Text Justification
    44. Wildcard Matching
    160. Intersection of Two Linked Lists
    24. Swap Nodes in Pairs
    93. 递归实现组合型枚举
    98. 分形之城
    97. 约数之和
    96. 奇怪的汉诺塔
  • 原文地址:https://www.cnblogs.com/softidea/p/11099424.html
Copyright © 2020-2023  润新知