硬核之TCP协议整理
一、TCP首部
源端口:占用2个字节,标记发送端的端口号
目标端口:占用2个字节,标记接收端的端口号
序号:占用4个字节,在建立连接后,每次发送TCP数据部分的第一个字节的编号。
确认号:占用4个字节,在建立连接后,期望对方下一次传过来的TCP数据部分的第一个字节的编号。
数据偏移:占4位,数据偏移*4等于TCP首部长度,首部长度为20-60个字节。
保留:占用6位,目前全为0
标记位:占用6位
URG:紧急位,一旦值为1,代表紧急指针有效。
ACK:确认位,一旦值为1,确认号有效。
PSH:接收方应该尽快将这个报文段交给应用层。一般用不上。
RST:重置位,一旦值为1,表示连接中出现严重错误,必须释放连接,然后再重新建立连接。
SYN:同步位,用于发送一个连接,当SYN=1,ACK=0,表明这是一个建立连接的请求。
FIN:释放位,一旦值为1,表明数据已经发送完毕,要求释放连接。
窗口:可靠连接和流量控制中所用到的窗口大小。
检验和:占2个字节,伪首部+首部+数据,伪首部仅在计算检验和起作用,不会传递给网络层
紧急指针:紧急数据,当URG为1时,应当快速处理
二、TCP连接管理
第一次握手:客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号 seq=x,以及标志位SYN=1,ACK=0。请求发送后,客户端便进入 SYN-SENT 状态。
第二次握手:服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯seq=y,ack=x+1,以及标志位SYN=1,ACK=1。发送完成后便进入 SYN-RECEIVED 状态。
第三次握手:当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。该确认报文包含自身的数据通讯序列号seq=x+1,ack=y+1,以及标志位ACK=1。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。
疑问:为什么建立连接需要3次握手?2次不行吗?
1、防止已过期的连接请求报文突然又传送到服务器,因而产生错误在双方两次握手即可建立连接的情况下,假设客户端发送 A 报文段请求建立连接,由于网络原因造成 A 暂时无法到达服务器,服务器接收不到请求报文段就不会返回确认报文段,客户端在长时间得不到应答的情况下重新发送请求报文段 B,这次 B 顺利到达服务器,服务器随即返回确认报文并进入 ESTABLISHED 状态,客户端在收到 确认报文后也进入 ESTABLISHED 状态,双方建立连接并传输数据,之后正常断开连接。此时姗姗来迟的 A 报文段才到达服务器,服务器随即返回确认报文并进入 ESTABLISHED 状态,但是已经进入 CLOSED 状态的客户端无法再接受确认报文段,更无法进入 ESTABLISHED 状态,这将导致服务器长时间单方面等待,造成资源浪费。
2、三次握手才能让双方均确认自己和对方的发送和接收能力都正常
第一次握手:客户端只是发送处请求报文段,什么都无法确认,而服务器可以确认自己的接收能力和对方的发送能力正常;第二次握手:客户端可以确认自己发送能力和接收能力正常,对方发送能力和接收能力正常;第三次握手:服务器可以确认自己发送能力和接收能力正常,对方发送能力和接收能力正常;可见三次握手才能让双方都确认自己和对方的发送和接收能力全部正常,这样就可以愉快地进行通信了
3、告知对方自己的初始序号值,并确认收到对方的初始序号值TCP 实现了可靠的数据传输,原因之一就是 TCP 报文段中维护了序号字段和确认序号字段,也就是图中的 seq 和 ack,通过这两个字段双方都可以知道在自己发出的数据中,哪些是已经被对方确认接收的。这两个字段的值会在初始序号值得基础递增,如果是两次握手,只有发起方的初始序号可以得到确认,而另一方的初始序号则得不到确认
疑问:第三次握手失败了会怎么处理?
此时服务器的状态为SYN-RCVD,若等不到客户端的ACK,服务器会重新发送SYN+ACK包,如果服务器多次重发SYN+ACK都等不到客户端的ACK,就会发送RST包,强制关闭连接。
疑问:TCP 建立连接为什么要三次握手而不是四次?
因为三次握手已经可以确认双方的发送接收能力正常,双方都知道彼此已经准备好,而且也可以完成对双方初始序号值得确认,也就无需再第四次握手了
第一次挥手:客户端认为数据发送完成,它需要向服务端发送连接释放请求。该报文段中包含自身的数据通讯初始序号 seq=u,ack=v 以及标志位FIN=1,ACK=1,发送成功后客户端处于FIN-WAIT-1终止等待。
第二次挥手:服务端收到连接释放请求后。给客户端发送确认报文,该报文包含自身的数据通讯初始号seq=v,ack=u+1以及标志位ACK=1。并进入 CLOSE_WAIT 状态,此时表明 客户端到服务器的连接已经释放,不再接收 A 发的数据了。
第三次挥手:服务器确认数据以及发送完成后会向客户端发送连接释放请求报文,该报文包含自身的数据通讯初始号seq=w,ack=u+1,以及标志位FIN=1,ACK=1。然后服务器便进入 LAST-ACK 状态。
第四次挥手:客户端收到释放请求后,向服务器发送确认应答,该报文包含自身的数据通讯初始号seq=u+1,ack=w+1以及标志位ACK=1。此时客户端进入TIME-WAIT状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃)时间,若该时间段内没有服务器的重发请求的话,就进入CLOSED 状态。当服务器收到确认应答后,也便进入 CLOSED 状态。
疑问:为什么释放连接需要进行4次挥手?
第一次挥手:当客户端发出FIN报文时,告诉服务器已经没有数据要发送了,但是还能接收到来自服务器的数据。第二次挥手:当服务器返回ACK报文时,表示服务器已经知道客户端没有数据发送,但是服务器还是可以发送数据到客户端。第三次挥手:当服务器也发送FIN报文时,告诉客户端,服务器已经没有数据要发送了。第四次挥手:当客户端发送ACK报文时,告诉服务器,已经知道没有数据发送了。随后正式断开整个TCP连接。
疑问:客户端为什么需要在 TIME-WAIT 状态等待 2MSL 时间才能进入 CLOSED 状态?
按照常理,在网络正常的情况下,四个报文段发送完后,双方就可以关闭连接进入 CLOSED 状态了,但是网络并不总是可靠的,如果客户端发送的 ACK 报文段丢失,服务器在接收不到 ACK 的情况下会一直重发 FIN 报文段,这显然不是我们想要的。因此客户端为了确保服务器收到了 ACK,会设置一个定时器,并在 TIME-WAIT 状态等待 2MSL 的时间,如果在此期间又收到了来自服务器的 FIN 报文段,那么客户端会重新设置计时器并再次等待 2MSL 的时间,如果在这段时间内没有收到来自服务器的 FIN 报文,那就说明服务器已经成功收到了 ACK 报文,此时客户端就可以进入 CLOSED 状态了
三、TCP之可靠传输
ARQ(超时重传):在发送数据时,设定一个定时器,当超过指定的时间后,没有接收到对方的ACK就会重发该数据报文。
停止等待ARQ(超时重传协议):即发送方发送一个帧后,必须接收到一个确认帧才能发送下一个。优点是简单,缺点是较长的等待时间,使得数据传输速度低,低速传输时频道利用率较高,高速传输时频道利用率较低。
连续ARQ协议和滑动窗口协议:发送端拥有一个发送窗口,可以在没有收到应答的情况下持续发送窗口内的数据,相比停止等待ARQ来说减少了等待时间,提高了效率
选择确认(SACK):在tcp通信过程中,如果发送序列中间某个数据包丢失,此时tcp会通过重传导致前面没有丢失的包,重复发送。SACK技术会告诉发送方那些数据丢失了,那些数据已经发送了,有针对性的只发送丢失的数据包。避免了重复发送。此时在tcp头部的选项字段中会告知发送端丢失的数据包。
疑问:为什么在传输层将数据报文分成多个片段,而不是在网络层在分片传递给数据链路层?
假设在传输层不进行切割分片,而是在网络层进行分片,向数据链路层传递报文,如果在网络传输过程中,某个分片丢失了,这时在网络层和数据链路层并没有可靠传输,而导致接收端数据缺失,没法给到发送端应答报文,从而导致发送端会将整个文件报文进行超时重传,降低性能。而在传输层则只需要把丢失部分的报文重传即可。
四、TCP之流量控制
tcp流量控制简单来说就是要通过tcp首部窗口字段来控制tcp发送的速率,不能发送太快以免数据来不及处理,也不能发送太慢以免浪费资源。此时,发送端和接收端必须维护着对应的窗口:发送窗口(rwnd)和接收窗口(cwnd)。在tcp建立连接的时候接收端会告诉发送端相应窗口大小,发送端根据该值和当时的网络拥塞情况来设置发送窗口大小,因此发送窗口与接收窗口大小是不断变化的。当接收窗口大小为0时,发送窗口将停止发送,直到接收窗口大小非0时告诉发送端,让其发送报文。有一种特殊情况,一开始接收端给发送端发送了0窗口大小的报文,后面接收端又有了非0窗口大小,但此时在给发送端发送报文的过程中丢失了,此时发送端的发送窗口一直为0,双方陷入僵局。解决方案是当发送端收到0窗口通知时,发送端停止发送报文,并且同时开启一个定时器,隔一段时间就发个测试报文询问接收端窗口大小,如果接收端窗口还是为0,则发送端再次刷新启动定时器直到接收端窗口大小非0,在发送报文。
五、TCP之拥塞控制
拥塞控制作用于网络,目的是防止过多的数据拥塞网络,避免出现网络负载过大,带宽被大量占用的情况。常见的避免tcp拥塞控制的方法有一下四种:
慢开始:就是在传输开始时将发送窗口慢慢的指数级别扩大,从而避免一开始就传输大量数据导致网络拥塞。具体步骤:1)连接开始时设置拥塞窗口为1MSS。2)每过一个RTT就将窗口大小乘以2。3)指数级增长肯定不能没有限制,所以有一个阈值(ssthresh)限制,当窗口大小大于阈值时就会启动拥塞避免算法。
拥塞避免:当慢开始到达阈值时,启动拥塞避免算法,即每过一个RTT,窗口大小只加1(线性增长),这样能够避免指数级增长导致网络拥塞,慢慢将大小调整到最佳值。当一直增长到一定程度时,网络就会慢慢进入到拥塞的状况,于是就会出现丢包的现象,这时候就需要对丢失的数据包进行重传,当触发到重传机制,也就进入到了快速重传的算法。
快速重传+快速恢复:当经过拥塞算法发现丢包现象时,立即进入快速重传和快速恢复算法,这两个一般同时使用,快速重传认为当接收方发现丢了一个中间包的时候,发送三次前一个包的ACK,于是发送端就会快速的重传该丢失的包,而不会像超时重传那样等待RTO才重传。此时cwnd=cwnd/2,ssthresh=cwnd,进入到快速恢复算法。在快恢复算法中,拥塞窗口cwnd=ssthresh+3(3的意思是确认有3个数据包被收到了),此时重传丢失的包。如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。
六、专业名词
MSS(Maximum segment size):每个段(传输层数据)最大的数据部分大小,在建立连接时确定,放在tcp首部的选项部分,发送端与接收端该参数不一定一致,双方协商以数据小的为准。
cwnd:拥塞窗口
rwnd:接收窗口
swnd:发送窗口,swnd = min(cwnd,rwnd)
RTT:就是数据从网络一端传送到另一端所需要的时间,也就是包的往返时间。
RTO:超时重传时间,应该略大于报文往返时间(RTT)
ssthresh:慢开始阈值,cwnd达到阈值时,以线性方式增加,一般来说ssthresh的大小是65535字节。
MSL:报文最大生存时间。