https://yq.aliyun.com/articles/11332?spm=5176.8091938.0.0.uEdHju

TCP建立连接时,为什么要进行三次挥手?

每一次TCP连接都需要三个阶段:连接建立、数据传送和连接释放。

三次握手就发生在连接建立阶段。

解释一 1.为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。2.为了解决网络中存在延迟的重复分组的问题。 client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用三次握手,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用三次握手的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。
有趣解释二 问题的本质是,信道不可靠, 但是通信双发需要就某个问题达成一致. 而要解决这个问题, 无论你在消息中包含什么信息, 三次通信是理论上的最小值. 所以三次握手不是TCP本身的要求, 而是为了满足"在不可靠信道上可靠地传输信息"这一需求所导致的 请注意这里的本质需求,信道不可靠, 数据传输要可靠.三次达到了, 那后面你想接着握手也好, 发数据也好, 跟进行可靠信息传输的需求就没关系了.因此,如果信道是可靠的, 即无论什么时候发出消息, 对方一定能收到, 或者你不关心是否要保证对方收到你的消息, 那就能像UDP那样直接发送消息就可以了. 。

举个打电话的例子

  A : 你好我是A,你听得到我在说话吗

  B : 听到了,我是B,你听到我在说话吗

  A : 嗯,听到了

  建立连接,开始聊天!

为什么TCP协议终止链接要四次?

1、当主机A确认发送完数据且知道B已经接受完了,想要关闭发送数据口(当然确认信号还是可以发),就会发FIN给主机B。

2、主机B收到A发送的FIN,表示收到了,就会发送ACK回复。

3、但这是B可能还在发送数据,没有想要关闭数据口的意思,所以FIN与ACK不是同时发送的,而是等到B数据

发送完了,才会发送FIN给主机A。

4、A收到B发来的FIN,知道B的数据也发送完了,回复ACK, A等待2MSL以后,没有收到B传来的任何消息,知道B已经收到自己的ACK了,A就关闭链接,B也关闭链接了。

  • 大家知道,由于socket是全双工的工作模式,一个socket的关闭,是需要四次握手来完成的。
  • 主动关闭连接的一方,调用close();协议层发送FIN包
  • 被动关闭的一方收到FIN包后,协议层回复ACK;然后被动关闭的一方,进入CLOSE_WAIT状态, 主动关闭的一方等待对方关闭,则进入FIN_WAIT_2状态;此时,主动关闭的一方 等待 被动关闭一方的应用程序,调用close操作
  • 被动关闭的一方在完成所有数据发送后,调用close()操作;此时,协议层发送FIN包给主动关闭的一方,等待对方的ACK,被动关闭的一方进入LAST_ACK状态
  • 主动关闭的一方收到FIN包,协议层回复ACK;此时,主动关闭连接的一方,进入TIME_WAIT状态;而被动关闭的一方,进入CLOSED状态
  • 等待2MSL时间,主动关闭的一方,结束TIME_WAIT,进入CLOSED状态
  • 通过上面的一次socket关闭操作,你可以得出以下几点:
    
    1.主动关闭连接的一方 – 也就是主动调用socket的close操作的一方,最终会进入TIME_WAIT状态
    2.被动关闭连接的一方,有一个中间状态,即CLOSE_WAIT,因为协议层在等待上层的应用程序,主动调用close操作后才主动关闭这条连接
    3.TIME_WAIT会默认等待2MSL时间后,才最终进入CLOSED状态;
    4.在一个连接没有进入CLOSED状态之前,这个连接是不能被重用的!
    
    所以,这里凭你的直觉,TIME_WAIT并不可怕(not really,后面讲),CLOSE_WAIT才可怕,因为CLOSE_WAIT很多,表示说要么是你的应用程序写的有问题,没有合适的关闭socket;要么是说,你的服务器CPU处理不过来(CPU太忙)或者你的应用程序一直睡眠到其它地方(锁,或者文件I/O等等),你的应用程序获得不到合适的调度时间,造成你的程序没法真正的执行close操作。
    
    这里又出现两个问题:
    
    上文提到的连接重用,那连接到底是个什么概念?
    协议层为什么要设计一个TIME_WAIT状态?这个状态为什么默认等待2MSL时间才会进入CLOSED
    

A为什么等待2MSL,从TIME_WAIT到CLOSE?

在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MS

L的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,

那么Client推断ACK已经被成功接收,则结束TCP连接。

这个网上转载的例子不错:

三次握手:

A:“喂,你听得到吗?”A->SYN_SEND

B:“我听得到呀,你听得到我吗?”应答与请求同时发出 B->SYN_RCVD | A->ESTABLISHED

A:“我能听到你,今天balabala……”B->ESTABLISHED

四次挥手:

A:“喂,我不说了。”A->FIN_WAIT1

B:“我知道了。等下,上一句还没说完。Balabala…..”B->CLOSE_WAIT | A->FIN_WAIT2

B:”好了,说完了,我也不说了。”B->LAST_ACK

A:”我知道了。”A->TIME_WAIT | B->CLOSED

A等待2MSL,保证B收到了消息,否则重说一次”我知道了”,A->CLOSED

API Gateway内部HttpClient调优:

<backends>
    <hc>
        <global>
            <maxTotal>200</maxTotal>
            <maxPerRoute>100</maxPerRoute>
            <connectionTimeout>1000</connectionTimeout>
            <connectTimeout>5000</connectTimeout>
            <soTimeout>6000</soTimeout>
            <keepAlive>3000</keepAlive>
        </global>
        <route host="127.0.0.1" port="8080">
            <max>100</max>
        </route>
    </hc>
</backends>

1.Time_Wait问题修复route的KeepAlive实效问题,提前到1分钟结束TimeWait,高并发情况下,重用速度很快(下面不需要,连接池会自己做)

#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout
net.ipv4.tcp_keepalive_*
这几个参数。

net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle的开启都是为了回收处于TIME_WAIT状态的资源。
net.ipv4.tcp_fin_timeout这个时间可以减少在异常情况下服务器从FIN-WAIT-2转到TIME_WAIT的时间。
net.ipv4.tcp_keepalive_*一系列参数,是用来设置服务器检测连接存活的相关配置。
TIME_WAIT很多,可怕吗?

如果你通过 ss -tan state time-wait | wc -l 发现,系统中有很多TIME_WAIT,很多人都会紧张。多少算多呢?几百几千?如果是这个量级,其实真的没必要紧张。第一,这个量级,因为TIME_WAIT所占用的内存很少很少;因为记录和寻找可用的local port所消耗的CPU也基本可以忽略。

会占用内存吗?当然!任何你可以看到的数据,内核里都需要有相关的数据结构来保存这个数据啊。一条Socket处于TIME_WAIT状态,它也是一条“存在“的socket,内核里也需要有保持它的数据:

内核里有保存所有连接的一个hash table,这个hash table里面既包含TIME_WAIT状态的连接,也包含其他状态的连接。主要用于有新的数据到来的时候,从这个hash table里快速找到这条连接。不同的内核对这个hash table的大小设置不同,你可以通过dmesg命令去找到你的内核设置的大小:

还有一个hash table用来保存所有的bound ports,主要用于可以快速的找到一个可用的端口或者随机端口:

由于内核需要保存这些数据,必然,会占用一定的内存。

会消耗CPU吗?当然!每次找到一个随机端口,还是需要遍历一遍bound ports的吧,这必然需要一些CPU时间。

TIME_WAIT很多,既占内存又消耗CPU,这也是为什么很多人,看到TIME_WAIT很多,就蠢蠢欲动的想去干掉他们。其实,如果你再进一步去研究,1万条TIME_WAIT的连接,也就多消耗1M左右的内存,对现代的很多服务器,已经不算什么了。至于CPU,能减少它当然更好,但是不至于因为1万多个hash item就担忧。

2.清空失效连接和定制长连接策略

http://blog.csdn.net/u014704496/article/details/40863045

http://blog.csdn.net/wanghao109/article/details/53121436

IdleConnectionMonitorThread类负责监控httpclient中的连接,进行清理操作。
// 启动定时清楚失效连接进程
        IdleConnectionMonitorThread thread = new IdleConnectionMonitorThread(cm);
        thread.start();
// 这个线程负责使用连接管理器清空失效连接和过长连接
    public static class IdleConnectionMonitorThread extends Thread {

        private final HttpClientConnectionManager connMgr;
        private volatile boolean shutdown;

        public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr) {
            super();
            this.connMgr = connMgr;
        }

        @Override
        public void run() {
            try {
                while (!shutdown) {
                    synchronized (this) {
                        wait(1000);
                        System.out.println("清空失效连接...");
                        // Close expired connections
                        connMgr.closeExpiredConnections();
                        // Optionally, close connections
                        // that have been idle longer than 30 sec
                        // 关闭空闲超过30秒的连接
                        connMgr.closeIdleConnections(30, TimeUnit.SECONDS);
                    }
                }
            } catch (InterruptedException ex) {
                // terminate
            }
        }

        public void shutdown() {
            shutdown = true;
            synchronized (this) {
                // 让run方法不再wait
                notifyAll();
            }
        }

    }
 /** 
     *  
     * http长连接策略: 
     * 可以根据须要定制所须要的长连接策略,可根据服务器指定的超时时间,也可根据主机名自己指定超时时间;
     */
private static void test11(){        
        //参考第一章的DefaultConnectionKeepAliveStrategy类  
        ConnectionKeepAliveStrategy myStrategy = new ConnectionKeepAliveStrategy() {  
            public long getKeepAliveDuration(HttpResponse response,HttpContext context) {  
                // 遍历response的header  
                HeaderElementIterator it = new BasicHeaderElementIterator(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")) {//如果头部包含timeout信息,则使用  
                        try {  
                            //超时时间设置为服务器指定的值  
                            return Long.parseLong(value) * 1000;  
                        } catch (NumberFormatException ignore) {  
                        }  
                    }  
                }  
                //获取主机  
                HttpHost target = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST);  
                if ("webservice.webxml.com.cn".equalsIgnoreCase(target.getHostName())) {  
                    // 如果访问webservice.webxml.com.cn主机则设置长连接时间为5秒  
                    return 5* 1000;  
                } else {  
                    // 其他为30秒  
                    return 30 * 1000;  
                }  
            }  
        };  
        CloseableHttpClient client = HttpClients.custom().setKeepAliveStrategy(myStrategy).build();

3.Close_Wait,代码不严谨未主动关闭流,导致一直在Clost_Wait

finally {
  if(response != null) { 
    EntityUtils.consume(response.getEntity()); //会自动释放连接
  }
  //如下方法也是可以的,但是存在一些风险;不要用
  //InputStream is = response.getEntity().getContent();
  //is.close();
}

http://blog.csdn.net/kobejayandy/article/details/17655739

http://blog.oldboyedu.com/tcp-wait/

https://www.8090st.com/server-time_wait-close_wait.html

http://blog.csdn.net/shootyou/article/details/6615051

http://jinnianshilongnian.iteye.com/blog/2089792

results matching ""

    No results matching ""