TCP工作机制详解

1TCP的特点:

基于字节流
面向连接
可靠传输
缓冲传输
全双工
流量控制

 

2、头部格式和说明

图源百度。如下图示,就是TCP包的头部结构。可以看到这个头部最少有4x5=20个字节。

另外还需要理解TCP协议是承载在IP协议中的。关于IP协议可以参考:http://www.cnblogs.com/xcywt/p/8067521.html

https://images2017.cnblogs.com/blog/822287/201712/822287-20171220194113475-1709447191.png

源端口号和目的端口号:再加上Ip首部的源IP地址和目的IP地址可以唯一确定一个TCP连接
数据序号:表示在这个报文段中的第一个数据字节序号
确认序号:仅当ACK标志为1时有效。确认号表示期望收到的下一个字节的序号(这个下面再详细分析)
偏移:就是头部长度,有4位,跟IP头部一样,以4字节为单位。最大是60个字节
保留位:6位,必须为0
6个标志位:
URG-紧急指针有效
ACK-确认序号有效
PSH-接收方应尽快将这个报文交给应用层
RST-连接重置
SYN-同步序号用来发起一个连接
FIN-终止一个连接

窗口字段:16位,代表的是窗口的字节容量,也就是TCP的标准窗口最大为2^16 - 1 = 65535个字节(这个下面再详细分析)

校验和:源机器基于数据内容计算一个数值,收信息机要与源机器数值 结果完全一样,从而证明数据的有效性。检验和覆盖了整个的TCP报文段:这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证的。

紧急指针:是一个正偏移量,与序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式
选项与填充(必须为4字节整数倍,不够补0):
最常见的可选字段的最长报文大小MSSMaximum Segment Size),每个连接方通常都在一个报文段中指明这个选项。它指明本端所能接收的最大长度的报文段。
该选项如果不设置,默认为53620+20+536=576字节的IP数据报)

 

3TCP如何保证可靠性

1)应用数据被分割成TCP认为最合适发送的数据块。称为段(Segment)传递给IP
2)当TCP发出一个段后,它会启动一个定时器,等待目的端确认收到这个报文段。若没有及时收到确认,将重新发送这个报文段
3)当TCP收到发自TCP连接另一端的数据,它将发送一个确认。这个确认不是立即发送的,通常将推迟几分之一秒。
4TCP将保持它首部和数据的校验和,这是一个端到端的校验和,目的是检测数据在传输过程中的任何变化。如果收到段的校验和有差错,TCP将丢弃这个报文也不进行确认(对方就会重复发送了)。
5TCP承载与IP数据报来传输,而IP数据报可能会失序,所以TCP的报文段到达时也可能会失序。但是TCP收到数据后会重新排序到正确的顺序(通过序号)。
6IP数据报会发生重复,TCP的接收端必须丢弃重复是数据
7TCP还能提供流量控制,TCP连接的每一方都有一定大小的缓冲空间

 

 4、滑动窗口协议(也就是对包头中窗口字段的理解)

参考1https://www.cnblogs.com/ulihj/archive/2011/01/06/1927613.html

参考2http://blog.chinaunix.net/uid-26275986-id-4109679.html

先上两个概念:
通告接收窗口(rwnd):预防应用程序发送的数据超过对方的缓冲区,接收方使用的流量控制。
拥塞窗口(cwnd):预防应用程序发送的数据超过了网络所能承载的能力。发送方使用的流量控制。
发送窗口:就是指上面两者的较小值

由于TCP的全双工的,所以其实TCP双方各自都维护一个发送窗口和接收窗口。

假设是主机A发送给主机B
A
B都会维护一个数据帧的序列,这个序列称为窗口。发送方的窗口大小由接收方确定。目的在于控制发送速度。以免接收方的缓存不够大而导致溢出,同时流量控制也可以避免网络拥塞。
这里其实是指A的发送窗口。

https://images2017.cnblogs.com/blog/822287/201712/822287-20171220195413256-790689929.png


假设A发送了很多段给B,序号是1-10.这些段会处于种状态:
1)已发送,已确认
2)已发送,未确认
3)等待发送
4)不允许发送

正常情况下,每个段都会由4状态->3状态->2状态->1状态。而窗口就是指处于状态2和状态3的总数。
2状态->1状态的时候,窗口就会往后滑动一下,表示最近那个4状态的段可以变成3状态了。
如果接收方一直不确认,那么处于4状态的段将永远不会被发送。
当窗口满了的时候,4状态的段将不会变成3状态。从而达到了控制发送速度的作用。

https://images2017.cnblogs.com/blog/822287/201712/822287-20171220195424131-1217959153.png

就像上图一样,123处于1状态,456处于2状态,789处于3状态,10以后的处于4状态。而窗口就是指哪个框起来的。这里为6

随着发送段被逐一的确认,这个窗口会往右滑动。

就像一个水池,总体积V,进水速度是s1,出水速度s2。当水池满了就不能再注入了,强行注入会溢出丢失。窗口就是那个水池。

滑动窗口实现面向流的可靠性:

1)最基本的传输可靠性来源于确认重传机制
2)滑动窗口的可靠性也是建立在确认重传机制上的
3)发送窗口只有收到目的端口对本段发送窗口内字节的ACK确认,才会移动发送串口的左边界。
4)接收窗口只有在前面所有的段都确认的情况下才会移动左边界。当在前面还有段未收到确认,但是收到了后面段的情况下,窗口不会移动,也不对后续段进行确认。以此确保发送端会对这个数据重传。

 

5、关于包头中确认号ack的理解

 确认序号:仅当ACK标志为1时有效。确认号表示期望收到的下一个字节的序号

这里是拿三次握手之后,开始传输数据了进行分析。

服务器向客户端发送一个数据包后,客户端收到了这个数据包,会向服务器发送一个确认数据包。

传输数据的简要过程如下:

1)发送数据:服务器向客户端发送一个带有数据的数据包。该数据包中的序列号和确认号与建立连接第三步的数据包找那个的序列号和确认号相同。

2)确认收到:客户端收到该数据包,向服务器发送一个确认数据包。该数据包中,序列号是为上一个数据包中的确认号值。

而确认号为服务器发送的上一个数据包中的序列号+该数据包中所带数据的大小。

回复确认收到的ack = 收到了序列号 + 数据的大小(同时也表示下一次期望收到的序号)

这里我们直接拿Wireshark抓包进行分析:

实例1:客户端给服务器发送了”xcychongyong” 13个字节。

先看,服务器收到的,也就是客户端发送的:seq10,数据长度是13.

https://images2017.cnblogs.com/blog/822287/201712/822287-20171220200502693-252858890.png

再来看服务器发送给客户端的确认包:根据上面的说明。ack应该是10 + 13 = 23

 https://images2017.cnblogs.com/blog/822287/201712/822287-20171220200531162-619227875.png

实例2

如下图,208(就是192.168.0.208)一共向182(就是192.168.0.182)发送了6组数据。

过滤条件:tcp and (ip.src==192.168.0.182 or ip.dst==192.168.0.182)

对于182来说:

第一次回应时ack4,结果208下一次发送的序号就是4

第二次回应时ack10,结果208下一次发送的序号就是10

第三次回应时ack19,结果208下一次发送的序号就是19

以此类推

 https://images2017.cnblogs.com/blog/822287/201712/822287-20171220200639193-830189717.png

再来分析一个互相发送的:

如图,一共发送了5次:

第一次182发给208,发的长度是7seq1.所以208回复的ack8。也相当于告诉182“182,你下次发的时候,序号就从8开始。看第2个红框,seq就是8.

第二次208发给182,发的长度是11seq1,所以182回复的ack12。也相当于告诉208“208,你下次发的时候,序号就从12开始。看第2个绿框,seq就是12.

同理,

182再发送一次给208seq应该是17  

208再发送一次给182seq应该是33

 https://images2017.cnblogs.com/blog/822287/201712/822287-20171220200705850-519834105.png

ack表示期望下次接收到的序号。

那么ack是如何算出来的呢,就是通过收到的序号,和数据长度相加得来。

假设A收到B过来的数据(seq = 5len = 15)。len表示数据长度。

那么A就会回复B刚才的数据我已经收到了,你接下来就发序号为20的包给我吧。这样就保证了数据不会乱序。

 综上,确认号就是下一次将要收到包的序号。同时也等于发送方的序号+数据长度(确认号在ACK标志位有效时才有用。)

 

三次握手过程理解

 

 

 

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

 

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

 

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

 

四次挥手过程理解 

 

 

1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。

2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。

3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。

6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

————————————————

 

【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?

 

答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

 

【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

 

答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。

 

【问题3】为什么不能用两次握手进行连接?

 

答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

 

       现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

 

【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?

 

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

————————————————

版权声明:本文为CSDN博主「青柚_」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/qq_38950316/article/details/81087809

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值