TCP提供了一种可靠的面向连接的字节流运输服务。该文将记录本人对TCP提供可靠连接服务的理解。
TCP数据是封装在IP数据包中的,TCP是传输层协议,上一层是网络层。
TCP 报文段由TCP首部和TCP数据组成,TCP通常20个字节,分为5行。结构如下:
URG::标识紧急指针是否有效 ACK: 标识确认序号是否有效
ACK:确认需要是否有效
PSH:用来提示接收端应用程序应该尽快将这个报文段交给应用层
RST:重新建立连接. 含有RST标识的报文称为复位报文段
SYN:同步序号用来发起一个连接, 含有SYN标识的报文称为同步报文段
FIN:结束标志, 发送端即将关闭,含有FIN标识的报文称为结束报文段
学习TCP 三次握手和四次挥手最好结合抓包工具验证理解。
tcpdump:这是了linux网络抓包命令
使用抓包工具fiddler、Wireshark工具抓取网络网络请求。教程网上搜索一下很多。
保证可靠性的机制
TCP首部校验和
序列号/确认应答
连接管理
滑动窗口
流量控制
拥塞控制
4种定时器
通过检验和的方式,接收端可以检测出来数据是否有差错和异常,假如有差错就会直接丢弃TCP段,重新发送。TCP在计算检验和时,会在TCP首部加上一个12字节的伪首部。检验和总共计算3部分:TCP首部、TCP数据、TCP伪首部
序列号:每个TCP首部都会生成一个序列号
确认应答:TCP传输的过程中,每次接收方收到数据后,都会对传输方进行确认应答。也就是发送ACK报文。这个ACK报文当中带有对应的确认序列号,告诉发送方,接收到了哪些数据,下一次的数据从哪里发。序列号的作用不仅仅是应答的作用,序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据。这也是TCP传输可靠性的保证之一。
连接管理就是:三次握手、四次挥手。每次数据传输之前必须要建立TCP连接。
三次握手过程
1 客户端发送SYN报文请求建立连接(SYN=1,seq=x),客户端进去SYN_SENT状态
2 服务器收到SYN报文包验证处理之后,发送一个SYN+ACK包(ACK=1,SYN=1,seq=y,ack=x+1)到客户端,并进入SYN_RCVD状态。
3 客服端收到服务器的SYN+ACK包后,校验成功之后向服务器发送ACK确认包(ACK=1,seq=x+1,ack=y+1)连接成功,服务器和客户端进入established(连接成功)状态。
为什么要三次握手
1 TCP是全双工协议,三次握手要保证双向读写的可靠
第一次握手:客户端什么都确定不了;服务器可以确认客户端发送正常、服务器接收正常
第二次握手:客户端可以确认客户端发送正常、接收正常,服务器发送、接收正常;服务器可以确认客户端发送正常、服务器接收正常。
第三次握手:客户端可以确认客户端发送正常、接收正常,服务器发送、接收正常;服务器可以确认客户端发送、接收正常、服务器接收、发送正常。
2 通过第三次SYN+ACK以保证应用层接收到的数据不会因为网络上的传输的问题而乱序。假如不进行第三次确认,因为网络不稳定,一条老的连接迟迟没有收到ACK确认,客户端又重新发送了握手请求。然后老的连接网络恢复收到了ACK。这样会创建两个TCP连接浪费资源,
三次握手已经可能保证连接可靠了,就没必要进行第四次握手了。
TCP的四次挥手过程如下
1、客户端发送一个FIN报文给服务端(FIN=1,seq=u),客户端进入FIN_WAIT_1状态
2、服务端收到客户端的FIN结束报文,发送报文通知客户端(服务器知道了)ACK=1,ack=u+1,服务端年进入CLOSE_WAIT状态
3、客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4、服务端发送一个FIN报文到客户端(ACK=1,Seq=w,ack=u+1),用来关闭服务端到客户端的数据传送,服务端进入LAST_ACK(最后确认)状态
5、客户端收到服务器的FIN报文后进入TIME_WAIT,并发送确认包给服务器后。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6、服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
为什么建立连接是三次握手,关闭连接确是四次挥手呢?
tcp是全双工通信,服务端和客服端都能发送和接收数据。tcp在断开连接时,需要服务端和客服端都确定对方将不再发送数据。
当客户端发起FIN请求的时候,服务器可能还有文件需要传给客户端。所以把FIN请求的ack和服务器发起FIN分开来处理。其实有的常见三次挥手也是可以的。为什么还要等待2MSL的时间呢?
MSL(Maximum Segment Lifetime),进行一次挥手的最大生存时间。2MSL就允许服务器重传FIN+客户端确认
1、保证客户端发送最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
2、防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
确认应答机制对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段.
这样做有一个比较大的缺点, 就是性能较差. 尤其是数据往返时间较长的时候.。
窗口:窗口大小指的是无需等待确认应答就可以继续发送数据的最大值。假设窗口大小4000个字节每次传1000个字节
发送前四个段的时候, 不需要等待任何ACK, 直接发送。收到第一个ACK确认应答后, 窗口向后移动, 继续发送第五六七八段的数据…因为窗口一直移动所以叫滑动窗口
滑动窗口机制是TCP的一种流量控制方法,该机制允许发送方在停止并等待确认前连续发送多个分组,而不必每发送一个分组就停下来等待确认,从而增加数据传输的速率提高应用的吞吐量。
首先TCP在进行数据传输的时候都是先将数据放在数据缓冲区中的,TCP维护了两个缓冲区,发送方缓冲区和接收方缓冲区。只有ACK确认应答过的数据, 才能从缓冲区删掉.
发送方缓冲区:发送方缓冲区用于存储已经准备就绪数据和发送了但是没有被确认的数据
接收方缓冲区:接收方缓冲区用于存储已经被接收但是还没有被用户进程消费的数据。
流量控制:TCP支持根据接收端的处理能力在决定发送端的发送速度。接收端将自己可以接收的缓存区大小放到TCP首部的窗口大小字段,通过ACK发送给客户端。客户端收到的窗口大小其实就是接收端接受缓存数区的剩余大小,客户端根据这个决定发送速度。
当窗口大小为0时候,这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 让接收端把窗口大小再告诉发送端.
虽然TCP有了滑动窗口这个大杀器, 能够高效可靠地发送大量数据.但是如果在刚开始就发送大量的数据, 仍然可能引发一些问题.因为网络上有很多计算机, 可能当前的网络状态已经比较拥堵.在不清楚当前网络状态的情况下, 贸然发送大量数据, 很有可能雪上加霜.
因此TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态以后, 再决定按照多大的速度传输数据.
拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.
为了控制丢失的报文段或丢弃的报文段,也就是对报文段确认的等待时间。当TCP发送报文段时,就创建这个特定报文段的重传计时器,可能发生两种情况:若在计时器超时之前收到对报文段的确认,则撤销计时器;若在收到对特定报文段的确认之前计时器超时,则重传该报文,并把计时器复位。
时间差为1、3、6、12、24、48和多个64s
专门为对付零窗口通知而设立的。
发送端收到零窗口的确认时(即接收端可以接收的缓存区不够时),就启动坚持计时器,当坚持计时器截止期到时,发送端TCP就发送一个特殊的报文段,叫探测报文段,这个报文段只有一个字节的数据。探测报文段有序号,但序号永远不需要确认,甚至在计算对其他部分数据的确认时这个序号也被忽略。探测报文段提醒接收端TCP,确认已丢失,必须重传。
坚持计时器的截止期设置为重传时间的值,但若没有收到从接收端来的响应,则发送另一个探测报文段,并将坚持计时器的值加倍和并复位,发送端继续发送探测报文段,将坚持计时器的值加倍和复位,直到这个值增大到阈值为止(通常为60秒)。之后,发送端每隔60s就发送一个报文段,直到窗口重新打开为止;
每当服务器收到客户的信息,就将keeplive timer复位,超时通常设置2小时,若服务器超过2小时还没有收到来自客户的信息,就发送探测报文段,若发送了10个探测报文段(没75秒发送一个)还没收到响应,则终止连接。
2MSL定时器,TIME_WAIT 确保有足够的时间让对端收到了ACK,如果被动关闭的那方没有收到 ACK,就会触发被动端重发 FIN。因为最后一次确认应答 ACK 报文段很有可能丢失,因而使被动关闭方处于在LIST_ACK 状态的,此时被动关闭方会重发这个 FIN+ACK 报文段,在这等待的 2MSL 时间内主动关闭方重新收到这个被动关闭方重发的 FIN+ACK 报文段,因此,主动关闭方会重新发送确认应答信息,从而重新启动 2MSL 计时器,直到通信双方都进入 CLOSED 状态。如果主动关闭方在 TIME_WAIT 状态不等待一段时间就直接释放连接并进入 CLOSED 状态,那么主动关闭方无法收到来自被动关闭方重发的 FIN+ACK 报文段,也就不会再发送一次确认 ACK 报文段,因此被动关闭方就无法正常进入CLOSED 状态。