TCP那些事儿

1、三次握手和四次挥手

2、为什么需要三次握手

3、为什么需要TIME_WAIT

4、CLOSE_WAIT产生的原因

5、TCP之Keepalive


1、三次握手和四次挥手

    三次握手和四次挥手的状态转移图如下所示:

 (1)三次握手状态

            

  • LISTEN :Server调用listen函数后,开始进入LISTEN状态;
  • SYN_SENT :Client调用connect函数之后,并在connect函数返回前进入SYN-SENT状态;
  • SYN_RECEIVED:Server在收到SYN和发送一个ACK后,此时进入SYN_RECEIVED状态; 
  • ESTABLISHED:代表一个打开的连接。Client的Connect函数返回后进入ESTABLISHED状态,Server收到Client的ACK确认后进入ESTABLISHED状态。

(2)四次挥手状态

                   

  • FIN_WAIT1 :当Client调用close函数后,进入FIN_WAIT1状态;
  • FIN_WAIT2 :当Client的close函数返回后,进入FIN_WAIT2的状态;
  • CLOSE_WAIT :Server端在收到Client的FIN请求后进入CLOSE_WAIT状态,该状态持续到Server调用close函数;
  • LAST_ACK :Server调用close函数后进入LAST_ACK状态;
  • TIME_WAIT :Client收到Server发来的关闭连接请求后进入TIME_WAIT状态,然后回复一个ACK给Server;
  • CLOSED :Client等待2MSL后进入CLOSED状态,Server收到Client的ACK确认后进入CLOSED状态。

2、为什么需要三次握手

    三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”:

    这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。

3、为什么需要TIME_WAIT

(1)保证TCP协议的全双工连接能够可靠关闭

    先说第一点,如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。

(2)保证这次连接的重复数据段从网络中消失

    再说第二点,如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。

4、CLOSE_WAIT产生的原因

  • CLOSE_WAIT出现在什么时候:在Sever端收到Client的FIN消息之后;
  • CLOSE_WAIT在什么时候转换成下一个状态:在Server端向Client发送FIN消息之后;

    如果Server端一直没有向client端发送FIN消息(调用close() API),那么这个CLOSE_WAIT会一直存在下去。可以看到出现CLOSE_WAIT,这基本上是用户server端程序的问题了;通常情况下,Server都是等待Client访问,如果Client退出请求关闭连接,server端自觉close()对应的连接。

    解决方法:

  • 当服务器读写失败时,可以选择关闭连接;
  • 定期向连接发送询问数据,检查收到的回复数据包(Heart-Beat线程发送指定格式的心跳数据包);
  • 修改keep-live参数(超时时间,tcp检查间隔时间:keeplive探测包发送的间隔,tcp检查次数:如果对方不予应答,探测包发送的次数);

5、TCP之Keepalive

    采用TCP连接的C/S模式软件,连接的双方在连接空闲状态时,如果任意一方意外崩溃、当机、网线断开或路由器故障,另一方无法得知TCP连接已经失效,除非继续在此连接上发送数据导致错误返回。很多时候,这不是我们需要的。我们希望服务器端和客户端都能及时有效地检测到连接失效,然后优雅地完成一些清理工作并把错误报告给用户。如何及时有效地检测到一方的非正常断开,一直有两种技术可以运用。一种是由TCP协议层实现的Keepalive,另一种是由应用层自己实现的心跳包。

    A和B两边通过三次握手建立好TCP连接,然后突然间B就宕机了,之后时间内B再也没有起来。如果B宕机后A和B一直没有数据通信的需求,A就永远都发现不了B已经挂了,那么A的内核里还维护着一份关于A&B之间TCP连接的信息,浪费系统资源。于是在TCP层面引入了keepalive的机制,A会定期给B发空的数据包,通俗讲就是心跳包,一旦发现到B的网络不通就关闭连接。这一点在LVS内尤为明显,因为LVS维护着两边大量的连接状态信息,一旦超时就需要释放连接。TCP默认并不开启Keepalive功能,因为开启Keepalive功能需要消耗额外的宽带和流量,尽管这微不足道,但在按流量计费的环境下增加了费用,另一方面,Keepalive设置不合理时可能会因为短暂的网络波动而断开健康的TCP连接。

    Linux内核对于tcp keepalive的调整主要有以下三个参数:

$ cat /proc/sys/net/ipv4/tcp_keepalive_time
  7200
$ cat /proc/sys/net/ipv4/tcp_keepalive_intvl
  75
$ cat /proc/sys/net/ipv4/tcp_keepalive_probes
  9

    当tcp发现有tcp_keepalive_time(7200)秒未收到对端数据后,开始以间隔tcp_keepalive_intvl(75)秒的频率发送的空心跳包,如果连续tcp_keepalive_probes(9)次以上未响应代码对端已经down了,close连接。

猜你喜欢

转载自blog.csdn.net/MOU_IT/article/details/114381060