一.TCP四次挥手
A:B啊,我不想玩了。
B:哦,你不想玩了啊,我知道了。
这个时候,还只是A不想玩了,也即A不会再发送数据,但是B能不能在ACK的时候,直接关闭呢?当然不可以了,很有可能A是发完了最后的数据就准备不玩了,但是B还没做完自己的事情,还是可以发送数据的,所以称为半关闭的状态。
这个时候A可以选择不再接收数据了,也可以选择最后再接收一段数据,等待B也主动关闭。
B:A啊,好吧,我也不玩了,拜拜。
A:好的,拜拜。
A B
(1)当A说“不玩了”,就进入FIN_WAIT_1的状态,B收到“A不玩”的消息后,发送知道了,就进入CLOSE_WAIT的状态
(2)A收到“B说知道了”,就进入FIN_WAIT_2的状态,如果这个时候B直接跑路,则A将永远在这个状态。TCP协议里面并没有对这个状态的处理,但是Linux有,可以调整tcp_fin_timeout这个参数,设置一个超时时间。
(3)如果B没有跑路,发送了“B也不玩了”的请求到达A时,A发送“知道B也不玩了”的ACK后,从FIN_WAIT_2状态结束,按说A可以跑路了,但是最后的这个ACK万一B收不到呢?则B会重新发一个“B不玩了”,这个时候A已经跑路了的话,B就再也收不到ACK了,因而TCP协议要求A最后等待一段时间TIME_WAIT,这个时间要足够长,长到如果B没收到ACK的话,“B说不玩了”会重发的,A会重新发一个ACK并且足够时间到达B
(为什么需要TIME-WAIT?【1】为了保证客户端发送的最后一个ACK报文段能够达到服务器,这个ACK报文段可能丢失,因而使处在LAST_ACK状态的服务器收不到确认。这样的话, 服务器会超时重传FIN+ACK报文段,客户端就能在2MSL时间内收到这个重传的FIN+ACK报文段,接着客户端重传一次确认,重启计时器。最后,客户端和服务器都正常进入到CLOSED状态
如果客户端在TIME_WAIT状态不等待一段时间,而是在发送完ACK报文后立即释放连接,那么就无法收到服务器重传的FIN+ACK报文段,因而也不会再发送一次确认报文。这样,服务器就无法按照正常步骤进入CLOSED状态。)
(4)A直接跑路还有一个问题是,A的端口就直接空出来了,但是B不知道,B原来发过的很多包很可能还在路上,如果A的端口被一个新的应用占用了,这个新的应用会收到上个连接中B发过来的包,虽然序列号是重新生成的,但是这里要上一个双保险,防止产生混乱,因而也需要等足够长的时间,等到原来B发送的所有的包都死翘翘,再空出端口来
(【2】防止已失效的连接请求报文段出现在本连接中。客户端在发送完最后一个ACK确认报文段后,再经过时间2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段)
(5)异常情况就是,B超过了2MSL的时间,依然没有收到它发的FIN的ACK,怎么办呢?按照TCP的原理,B当然还会重发FIN,这个时候A再收到这个包之后,A就表示,我已经在这里等了这么长时间了,已经仁至义尽了,之后的我就都不认了,于是就直接发送RST,B就知道A早就跑了。
异常例子:https://blog.csdn.net/INGNIGHT/article/details/73694897
client已经close,关闭读写
server第一次向client发送请求AAAA,client已经关闭,client发送RST报文到server
server第二次发送请求BBBB,server端产生SIGPIPIE信号
二、TCP三次握手
A:您好,我是A。
B:您好A,我是B。
A:您好B。

为什么需要采用三次握手?
主要是为了防止两次握手情况下已失效的连接请求报文段突然又传送到服务端,而产生的错误。举例如下:
客户A向服务器B发出TCP连接请求,第一个连接请求报文在网络的某个节点长时间滞留,A超时后认为报文丢失,于是再重传一次连接请求,B收到后建立连接。数据传输完毕后双方断开连接。而此时,前一个滞留在网络中的连接请求到达了服务端B,而B认为A又发来连接请求,若采用的是“两次握手”,则这种情况下B认为传输连接已经建立,并一直等待A传输数据,而A此时并无连接请求,因此不予理睬,这样就造成了B的资源白白浪费了;但此时若是使用“三次握手”,则B向A返回确认报文段,由于是一个失效的请求,因此A不予理睬,建立连接失败。第三次握手的作用:防止已失效的连接请求报文段突然又传送到了服务器。

当服务器绑定、监听了指定端口后,内核通常会为每一个LISTEN状态的socket维护两个队列:
(1)SYN队列(半连接队列):由/proc/sys/net/ipv4/tcp_max_syn_backlog指定,表示处于 SYN_RECV 状态的队列;
(2)ACCEPT队列(全连接队列):listen()函数的第二个参数 backlog 指定,内核硬限制由 net.core.somaxconn(/proc/sys/net/core/somaxconn) 限制,即实际的值由min(backlog,somaxconn) 来决定。表示已完成连接的队列,等待被 accept系统调用取走。
(3)syn floods 攻击就是针对半连接队列的,攻击方不停地建连接,但是建连接的时候只做第一步,第二步中攻击方收到server的syn+ack后故意扔掉什么也不做,导致server上这个队列满其它正常请求无法进来。
[1] 如何确认系统是否处于dos攻击?可以判断系统处于SYN_RCVD的有多少,
netstat -anp |awk '{print $6}'|sort|uniq -c |sort -rn
[2]如何确定哪一个ip发的SYN?
netstat -an | grep SYN_RECV | awk '{print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -nr | more

三、滑动窗口Advertised window
(1)发送端缓存的数据结构

-
LastByteAcked:第一部分和第二部分的分界线
-
LastByteSent:第二部分和第三部分的分界线
-
LastByteAcked + AdvertisedWindow(滑动窗口大小):第三部分和第四部分的分界线
第一部分:发送了并且已经确认的。这部分就是你交代下属的,并且也做完了的,应该划掉的。
第二部分:发送了并且尚未确认的。这部分是你交代下属的,但是还没做完的,需要等待做完的回复之后,才能划掉。
第三部分:没有发送,但是已经等待发送的。这部分是你还没有交代给下属,但是马上就要交代的。
第四部分:没有发送,并且暂时还不会发送的。这部分是你还没有交代给下属,而且暂时还不会交代给下属的。
Advertised window滑动窗口的大小应该等于上面的第二部分加上第三部分(也是接收端等待接收未确认的大小),就是已经交代了没做完的加上马上要交代的。超过这个窗口的,接收端做不过来,就不能发送了。
(2)接收端缓存的数据结构
第一部分:接受并且确认过的。也就是我领导交代给我,并且我做完的。
第二部分:还没接收,但是马上就能接收的。也即是我自己的能够接受的最大工作量。
第三部分:还没接收,也没法接收的。也即超过工作量的部分,实在做不完。
对应的数据结构就像这样。 
-
MaxRcvBuffer:最大缓存的量(接收缓存区大小);
-
LastByteRead之后是已经接收了,但是还没被应用层读取的;
-
NextByteExpected是第一部分和第二部分的分界线。
第二部分的窗口有多大呢?
NextByteExpected和LastByteRead的差其实是还没被应用层读取的部分占用掉的MaxRcvBuffer的量,我们定义为A。
AdvertisedWindow其实是MaxRcvBuffer减去A。(等待接收未确认的大小)
也就是:AdvertisedWindow=MaxRcvBuffer-((NextByteExpected-1)-LastByteRead)。
那第二部分和第三部分的分界线在哪里呢?NextByteExpected加AdvertisedWindow就是第二部分和第三部分的分界线,其实也就是LastByteRead加上MaxRcvBuffer。
其中第二部分里面,由于受到的包可能不是顺序的,会出现空挡,只有和第一部分连续的,可以马上进行回复,中间空着的部分需要等待,哪怕后面的已经来了。
四、流量控制问题
我们再来看流量控制机制,在对于包的确认中,同时会携带一个窗口的大小。
我们先假设窗口不变的情况,窗口始终为9。4的确认来的时候,会右移一个,这个时候第13个包也可以发送了。

这个时候,假设发送端发送过猛,会将第三部分的10、11、12、13全部发送完毕,之后就停止发送了,未发送可发送部分为0。

当对于包5的确认到达的时候,在客户端相当于窗口再滑动了一格,这个时候,才可以有更多的包可以发送了,例如第14个包才可以发送。

如果接收方实在处理的太慢,导致缓存中没有空间了,可以通过确认信息修改窗口的大小,甚至可以设置为0,则发送方将暂时停止发送。
我们假设一个极端情况,接收端的应用一直不读取缓存中的数据,当数据包6确认后,窗口大小就不能再是9了,就要缩小一个变为8。

这个新的窗口8通过6的确认消息到达发送端的时候,你会发现窗口没有平行右移,而是仅仅左面的边右移了,窗口的大小从9改成了8。

如果接收端还是一直不处理数据,则随着确认的包越来越多,窗口越来越小,直到为0。

当这个窗口通过包14的确认到达发送端的时候,发送端的窗口也调整为0,停止发送。

如果这样的话,发送方会定时发送窗口探测数据包,看是否有机会调整窗口的大小。当接收方比较慢的时候,要防止低能窗口综合征,别空出一个字节来就赶快告诉发送方,然后马上又填满了,可以当窗口太小的时候,不更新窗口,直到达到一定大小,或者缓冲区一半为空,才更新窗口。
五、TCP如何保证可靠性
序号: TCP连接中传送的数据流中的每一个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号;
确认: TCP首部的确认号是期望收到对方的下一个报文段数据的第一个字节的序号。当数据发送出去之后, 发送方缓存区会继续存储那些已经发送但未收到确认的报文段,以便在需要的时候重传。TCP默认使用累计确认,即TCP只确认数据流中至第一个丢失字节为止的字节。
重传: 有两种事件会导致TCP对报文段进行重传:超时和冗余ACK。
1)超时: TCP每发送一个报文段,就对这个报文段设置一次计时器。只要计时器设置的重传时间到期但还没有收到确认,就要重传这一报文段。
2)冗余ACK(冗余确认): 冗余ACK就是再次确认某个报文段的ACK,而发送方先前已经收到过该报文段的确认; TCP规定当发送方收到对同一个报文段的3个冗余ACK时,就可以认为跟在这个被确认报文段之后的报文段已经丢失;
校验: TCP将保持它首部和数据的校验和。这是一个端到端的校验和,目的是检测数据在传输过程中的任何变化。如果收到段的校验和有差错,TCP将丢弃这个报文段并且不确认(导致对方超时重传);
重排: TCP承载于IP数据报来传输,而IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。TCP将对收到的数据进行重新排序。IP数据报会发生重复,TCP的接收端会丢弃重复的数据。
流量控制: TCP还能提供流量控制。TCP连接的每一方都有一定大小的缓冲空间。
分割: 应用数据被分割成TCP认为最适合发送的数据块,称为TCP报文段传递给IP层。
参考:
https://blog.csdn.net/MOU_IT/article/details/88813015
https://blog.csdn.net/Tanswer_/article/details/78375317
http://jm.taobao.org/2017/05/25/525-1/
本文深入解析TCP协议的四次挥手、三次握手、滑动窗口、流量控制及可靠性保证机制,阐述其在网络通信中的作用。
140

被折叠的 条评论
为什么被折叠?



