tcp 状态转换图以及问题点2

来源:互联网 发布:dnf战斗力查询 软件 编辑:程序博客网 时间:2024/06/05 14:52

这里主要为了将问题弄清楚,后续遇到问题会不断的增加,尽量希望能够把问题逐步搞清楚!

第一:tcp连接为什么需要三次握手?

      在谢希仁著《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。在另一部经典的《计算机网络》一书中讲“三次握手”的目的是为了解决“网络中存在延迟的重复分组”的问题。这两种不用的表述其实阐明的是同一个问题。
      谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”。主要目的防止server端一直等待,浪费资源。

第二:tcp断开为什么需要四次分手?

           原因是因为tcp是全双工模式,当接收到FIN时意味将对端没有数据再发来,但是本端可以继续发送数据。

(校注:这里是数据包,至于ack控制包不在此列)!

第二:四次分手过程详解如下?

image

FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方
FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点包需要传送给你(ACK信息),稍后再关闭连接。(主动方
TIME_WAIT表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方
CLOSING(比较少见): 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。(同时关闭的情况)
CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。被动方
LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方

CLOSED: 表示连接中断,实际中不存在。


第三:关于建连接时SYN超时问题

第四:关于SYN Flood攻击

第五:关于 MSL和TIME_WAIT

第六:关于关于TIME_WAIT数量太多
上面的问题都可以在这里网址找到比较靠谱的答案!

http://coolshell.cn/articles/11564.html/comment-page-1#comments


第七:tcp四次挥手的time_wait状态存在的理由:

第一:确保client的ack到达了server端,并且被接手!

第二:防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失),这里2MSL指的是报文在网络上存活的时间!


第八: 关于rst flag情况说明


第九: 关于close_wait的说明


       通过命令查询netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}' 
如果出现大量的close_wait,说明服务器的tcp 状态机有问题,解决的唯一方法就是查代码!

被动端收到FIN之后,会进入CLOSE_WAIT状态, 这个状态是干啥的呢其实就是wait to close,  也就是wait应用程序(被动关闭端)close socket,  至于应用程序什么时候close, 那完全取决于程序。所以, CLOSE_WAIT状态什么时候终结, 取决于应用程序什么时候来close socket,  所以, 从理论上来讲, 只要被动关闭端不断电, 进程不退出, 那么CLOSE_WAIT状态就会一直持续下去。


第十:关于TCP flag的说明


在TCP层,有个FLAGS字段,这个字段有以下几个标识:SYN, FIN, ACK, PSH, RST, URG.
其中,对于我们日常的分析有用的就是前面的五个字段。

它们的含义是:

URG:Urget pointer is valid (紧急指针字段值有效)

SYN: 表示建立连接,在wireshark的过滤栏位传入:tcp.connection.syn就可以

FIN: 表示关闭连接,tcp.connection.fin

ACK: 表示响应,tcp.connection.ack

PSH: 表示有 DATA数据传输 tcp.flags.push

RST: 表示连接重置 tcp.connection.rst



第十一:tcp三次握手和accpet()的顺序

     原因是:状态机的变化是由内核控制,APP的只是触发条件,三次握手都是connect的触发条件

    设想一个情景,若有10000个客户端都和该服务端进行连接,发送SYN,服务端收到之后,这些客户端却不再理会服务端的回复,然而此时服务端的资源却都用accept()分配了。这就是所谓的“DDOS攻击”。

    为了解决这个问题,accept()于是被放在三次握手之后。

    当然更加可信的demo,请参考这个链接! http://blog.csdn.net/stpeace/article/details/76140474

第十二:TCP网络编程中connect()、listen()和accept()三者之间的关系


具体的详见这篇csdn,http://blog.csdn.net/tennysonsky/article/details/45621341
我主要做一下总结:

1)connect函数为客户端主动连接服务器,建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知Linux内核,让linux内核自动完成TCP三次握手连接,最后把连接的结果返回给这个函数的返回值(成功连接为0,失败为-1)。
2)listen函数为将该套接字和套接字对应的连接队列长度告诉 Linux 内核,内核维护中两套链表,分别是sys_rcv和establish。
3)accept函数从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。


第十三. TCP通信中服务器处理客户端意外断开

      这一部分也是常需要考虑的细节,就是保活的功能!在之前rtsp server也是考虑到,

      由于那个client端是未定的,或者是client和server端是不同厂商负责,导致各自负责,

      没有办法统一从app角度使用,像这种心跳包在pppoe也有频繁的使用。下面转载一个,后续有深入的可以讨论!

      引用地址:http://blog.csdn.net/kkkkkxiaofei/article/details/12966407

      如果TCP连接被对方正常关闭,也就是说,对方是正确地调用了closesocket(s)或者shutdown(s)的话,那么上面的Recv或Send调用就能马上返回,并且报错。这是由于close socket(s)或者shutdown(s)有个正常的关闭过程,会告诉对方“TCP连接已经关闭,你不需要再发送或者接受消息了”。

但是,如果意外断开,客户端(3g的移动设备)并没有正常关闭socket。双方并未按照协议上的四次挥手去断开连接。

那么这时候正在执行Recv或Send操作的一方就会因为没有任何连接中断的通知而一直等待下去,也就是会被长时间卡住。

       像这种如果一方已经关闭或异常终止连接,而另一方却不知道,我们将这样的TCP连接称为半打开的。

       解决意外中断办法都是利用保活机制。而保活机制分又可以让底层实现也可自己实现。

     1、自己编写心跳包程序

     简单的说也就是在自己的程序中加入一条线程,定时向对端发送数据包,查看是否有ACK,如果有则连接正常,没有的话则连接断开

     2、启动TCP编程里的keepAlive机制


一、双方拟定心跳(自实现)

     一般由客户端发送心跳包,服务端并不回应心跳,只是定时轮询判断一下与上次的时间间隔是否超时(超时时间自己设定)。服务器并不主动发送是不想增添服务器的通信量,减少压力。

但这会出现三种情况:

情况1.

       客户端由于某种网络延迟等原因很久后才发送心跳(它并没有断),这时服务器若利用自身设定的超时判断其已经断开,而后去关闭socket。若客户端有重连机制,则客户端会重新连接。若不确定这种方式是否关闭了原本正常的客户端,则在ShutDown的时候一定要选择send,表示关闭发送通道,服务器还可以接收一下,万一客户端正在发送比较重要的数据呢,是不?

情况2.

       客户端很久没传心跳,确实是自身断掉了。在其重启之前,服务端已经判断出其超时,并主动close,则四次挥手成功交互。

情况3.

      客户端很久没传心跳,确实是自身断掉了。在其重启之前,服务端的轮询还未判断出其超时,在未主动close的时候该客户端已经重新连接。

       这时候若客户端断开的时候发送了FIN包,则服务端将会处于CLOSE_WAIT状态;

       这时候若客户端断开的时候未发送FIN包,则服务端处还是显示ESTABLISHED状态;

       而新连接上来的客户端(也就是刚才断掉的重新连上来了)在服务端肯定是ESTABLISHED;这时候就有个问题,若利用轮询还未检测出上条旧连接已经超时(这很正常,timer总有个间隔吧),而在这时,客户端又重复的上演情况3,那么服务端将会出现大量的假的ESTABLISHED连接和CLOSE_WAIT连接。

        最终结果就是新的其他客户端无法连接上来,但是利用netstat还是能看到一条连接已经建立,并显示ESTABLISHED,但始终无法进入程序代码。个人最初感觉导致这种情况是因为假的ESTABLISHED连接和CLOSE_WAIT连接会占用较大的系统资源,程序无法再次创建连接(因为每次我发现这个问题的时候我只连了10个左右客户端却已经有40多条无效连接)。而最近几天测试却发现有一次程序内只连接了2,3个设备,但是有8条左右的虚连接,此时已经连接不了新客户端了。这时候我就觉得我想错了,不可能这几条连接就占用了大量连接把,如果说几十条还有可能。但是能肯定的是,这个问题的产生绝对是设备在不停的重启,而服务器这边又是简单的轮询,并不能及时处理,暂时还未能解决。


二、利用KeepAlive

          其实keepalive的原理就是TCP内嵌的一个心跳包,

         以服务器端为例,如果当前server端检测到超过一定时间(默认是 7,200,000 milliseconds,也就是2个小时)没有数据传输,那么会向client端发送一个keep-alive packet(该keep-alive packet就是ACK和当前TCP序列号减一的组合),此时client端应该为以下三种情况之一:

        1. client端仍然存在,网络连接状况良好。此时client端会返回一个ACKserver端接收到ACK后重置计时器(复位存活定时器),在2小时后再发送探测。如果2小时内连接上有数据传输,那么在该时间基础上向后推延2个小时。

        2. 客户端异常关闭,或是网络断开。在这两种情况下,client端都不会响应。服务器没有收到对其发出探测的响应,并且在一定时间(系统默认为1000 ms)后重复发送keep-alive packet,并且重复发送一定次数(2000 XP 2003 系统默认为5Vista后的系统默认为10次)。

         3. 客户端曾经崩溃,但已经重启。这种情况下,服务器将会收到对其存活探测的响应,但该响应是一个复位,从而引起服务器对连接的终止。

       对于应用程序来说,2小时的空闲时间太长。因此,我们需要手工开启Keepalive功能并设置合理的Keepalive参数。

全局设置可更改/etc/sysctl.conf,加上:
net.ipv4.tcp_keepalive_intvl = 20
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_time = 60

在程序中设置如下:

  1. #include <sys/socket.h>  
  2.  #include <netinet/in.h>  
  3.  #include <arpa/inet.h>  
  4.  #include <sys/types.h>  
  5.  #include <netinet/tcp.h>  
  6.   
  7.  int keepAlive = 1; // 开启keepalive属性  
  8.  int keepIdle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测   
  9.  int keepInterval = 5; // 探测时发包的时间间隔为5 秒  
  10.  int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.  
  11.   
  12.  setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));  
  13.  setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));  
  14.  setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));  
  15.  setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));   
在程序中表现为,当tcp检测到对端socket不再可用时(不能发出探测包,或探测包没有收到ACK的响应包),select会返回socket可读,并且在recv时返回-1,同时置上errno为ETIMEDOUT.


第十四:深入理解send函数

       send函数并没有发送数据的能力, send函数的作用仅仅是把应用程序的数据拷贝到发送端的内核缓冲区中, 只要有足够的空间, send函数就不会阻塞, 就会返回成功。 至于内核缓冲区中的数据是否发送, 什么时候发送, 那是协议栈的事情,  跟send函数没有半毛钱的关系。下面用的图形就比较清晰了!
     

第十五:如何设置连接建立和断开的参数值设置

连接建立的相关参数设定:
这里面的backlog,其实其实每次listen函数传入的最后一个变量,它的值可以粗略定义为:
1.x = sys_rvcd队列的数目
2.y = established队列的数目
那么值就是x+y的总和。
echo 8192 > /proc/sys/net/ipv4/tcp_max_syn_backlogecho 2 > /proc/sys/net/ipv4/tcp_syn_retriesecho 2 > /proc/sys/net/ipv4/tcp_synack_retriestcp_max_syn_backlog  SYN队列的长度,时常称之为未建立连接队列。系统内核维护着这样的一个队列,用于容纳状态为SYN_RECV的TCP连接(half-open connection),即那些依然尚未得到客户端确认(ack)的TCP连接请求。加大该值,可以容纳更多的等待连接的网络连接数。tcp_syn_retries  新建TCP连接请求,需要发送一个SYN包,该值决定内核需要尝试发送多少次syn连接请求才决定放弃建立连接。默认值是5. 对于高负责且通信良好的物理网络而言,调整为2tcp_synack_retries  对于远端SYN连接请求,内核会发送SYN+ACK数据包来确认收到了上一个SYN连接请求包,然后等待远端的确认(ack数据包)。该值则指定了内核会向远端发送tcp_synack_retires次SYN+ACK数据包。默认设定值是5,可以调整为2
echo 0 > /proc/sys/net/ipv4/tcp_syncookies在CentOS5.3中,该选项默认值是1,即开启SYN Cookies功能。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击。我们建议先关闭,直到确定受到syn flood攻击的时候再开启syn cookies功能,有效地防止syn flood攻击。也可以通过iptables规则拒绝syn flood攻击。

断开连接时候参数设定:
echo 30 >  /proc/sys/net/ipv4/tcp_fin_timeoutecho 15000 > /proc/sys/net/ipv4/tcp_max_tw_bucketsecho 1 > /proc/sys/net/ipv4/tcp_tw_reuseecho 1 >  /proc/sys/net/ipv4/tcp_tw_recycletcp_fin_timeout 对于由本端主动断开连接的TCP连接,本端会主动发送一个FIN数据报,在收到远端ACK后,且并没有收到远端FIN包之前,该TCP连接的状态是FIN_WAIT_2状态,此时当远端关闭了应用,网络不可达(拔网张),程序不可断僵死等等,本端会一直保留状态为FIN_WAIT_2状态的TCP连接,该值tcp_fin_timeout则指定了状态为FIN_WAIT_2的TCP连接保存多长时间,一个FIN_WAIT_2的TCP连接最多占1.5k内存。系统默认值是60秒,可以将此值调整为30秒,甚至10秒。tcp_max_tw_buckets 系统同时处理TIME_WAIT sockets数目。如果一旦TIME_WAIT tcp连接数超过了这个数目,系统会强制清除并且显示警告消息。设立该限制,主要是防止那些简单的DoS攻击,加大该值有可能消耗更多的内存资源。如果TIME_WAIT socket过多,则有可能耗尽内存资源。默认值是18w,可以将此值设置为5000~30000 tcp_tw_resue 是否可以使用TIME_WAIT tcp连接用于建立新的tcp连接。tcp_tw_recycle 是否开启TCP连接中TIME-WAIT sockets的快速回收功能。为了对NAT设备更友好,建议设置为0

总结:如何查看状态迁移,进行分析?


常用的工具,netstat,大多数使用命令如下:

netstat -anptsnetstat -n | grep “TIME”
下面是运行上一条语句打印的统计信息,可以看到!

IcmpMsg:    InType0: 3    InType3: 763    InType8: 1324    OutType0: 1324    OutType3: 79    OutType8: 3Tcp:    38848 active connections openings    3829 passive connection openings    256 failed connection attempts    3246 connection resets received    98 connections established    2697775547 segments received    3749622382 segments send out    713569 segments retransmited    797 bad segments received.    1054 resets sentUdpLite:TcpExt:    137 invalid SYN cookies received //无效的syn包    4 resets received for embryonic SYN_RECV sockets    1384 packets pruned from receive queue because of socket buffer overrun    35920 TCP sockets finished time wait in fast timer    4604194 delayed acks sent    2912 delayed acks further delayed because of locked socket    Quick ack mode was activated 66379 times    46082980 packets directly queued to recvmsg prequeue.    157616979 bytes directly in process context from backlog    489222304 bytes directly received in process context from prequeue    148 packets dropped from prequeue    1722022072 packet headers predicted    45233809 packets header predicted and directly queued to user    137598131 acknowledgments not containing data payload received    1456859456 predicted acknowledgments    63008 times recovered from packet loss by selective acknowledgements    16 bad SACK blocks received    Detected reordering 6 times using FACK    Detected reordering 7 times using SACK    Detected reordering 32 times using time stamp    38 congestion windows fully recovered without slow start    26 congestion windows partially recovered using Hoe heuristic    1637 congestion windows recovered without slow start by DSACK    12046 congestion windows recovered without slow start after partial ack    TCPLostRetransmit: 36117    1701 timeouts after SACK recovery    86 timeouts in loss state    654771 fast retransmits    6216 forward retransmits    25907 retransmits in slow start    13344 other TCP timeouts    TCPLossProbes: 600665    TCPLossProbeRecovery: 460323    2056 SACK retransmits failed    8515 packets collapsed in receive queue due to low socket buffer    68131 DSACKs sent for old packets    223 DSACKs sent for out of order packets    521780 DSACKs received    210 DSACKs for out of order packets received    45 connections reset due to unexpected data    20 connections reset due to early user close    714 connections aborted due to timeout    TCPDSACKIgnoredOld: 2424    TCPDSACKIgnoredNoUndo: 310510    TCPSpuriousRTOs: 929    TCPSackShifted: 636029    TCPSackMerged: 613319    TCPSackShiftFallback: 343428    TCPBacklogDrop: 696    TCPRetransFail: 244    TCPRcvCoalesce: 54141309    TCPOFOQueue: 18593353    TCPOFOMerge: 211    TCPChallengeACK: 815    TCPSYNChallenge: 803    TCPSpuriousRtxHostQueues: 28IpExt:    InBcastPkts: 4134951    OutBcastPkts: 33492    InOctets: -550766607    OutOctets: 2039824407    InBcastOctets: 817912422    OutBcastOctets: 3173434    InNoECTPkts: -895991522