目录
一、TCP协议段格式
二、TCP原理
1.确认应答
2.超时重传
3.连接管理
建立连接
断开连接
4.滑动窗口
5.流量控制
6.拥塞控制
7.延时应答
8.捎带应答
9.面向字节流
10.TCP异常情况
TCP,即Transmission Control Protocol,传输控制协议。人如其名,要对数据的传输进行一个详细的控制。
一、TCP协议段格式
TCP段 = 报头(首部)+ 载荷(数据)
TCP报头分为了 11个部分,报头的最小长度是20字节,是不包含选项部分的,包含选项部分是60字节:
- 源/目的端口号:表示数据是从哪个进程来,到哪个进程去。
- 32位序号/32位确认号:后面会详细讲。
- 4位TCP首部长度:有4位(4bit),此处的单位是4字节,4位二进制表示最大的数是15,所以4位首部长度能表示TCP头部最大长度是15*4=60。
- 6位标志位:
- URG:紧急指针是否有效。
- ACK:确认号是否有效。
- PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走。
- RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段。
- SYN:请求建立连接;我们把携带SYN标识的称为同步报文段。
- FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段。
- 窗口大小:后面会详细讲到。
- 16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的校验和不光包含TCP首部,也包含TCP数据部分。
- 16位紧急指针:标识哪部分数据是紧急数据。
- 选项:可选的,可以有也可以没有,也是头部的一部分
- 保留6位:由于UDP的长度最大为64kb,改不了,所以在TCP段里面这个保留位就留下了扩展的余地,但这个部分暂时用不到,后面如果有需要会再使用。
二、TCP原理
在学习TCP原理之前,先了解一下TCP的特点:
- 有连接
- 可靠传输
- 面向字节流
- 有接收缓冲区,也有发送缓冲区
- 大小不限
- 是全双工
其中可靠传输时TCP最核心的特性。
TCP对数据传输提供的管控机制,主要体现在两个方面:可靠和效率。
这些机制和多线程的设计原则类似:保证数据的安全可靠传输的前提下,尽可能的提高效率。
其中,保证可靠性需要进行下面操作:
- 校验和
- 序列号
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能进行下面操作:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
1.确认应答
发送方,把数据发给接收方之后,接收方收到数据就会给发送方返回一个应答报文(acknowledge,ack),如果发 送方收到这个应答报文了,就知道自己的数据是否发送成功了。
我们先了解一下序号和确认号:
- 序号:标识发送方发送的数据段的起始字节。
- 确认号:标识接收方期望接收的下一个字节的序号。
TCP是面向字节流的,TCP将每个字节的数据都进行了编号,即为序列号。如下图:
TCP段传输中确认应答的流程:
补充:区分一个数据包是普通的数据还是ACK应答数据看ACK,当ACK这一位为1,表示当前数据包是一个应答报文,此时该数据包中的“确认序号字段”就能生效,反之为0,表示当前是一个普通报文,此时数据包中的“确认序号字段”是不生效的。
2.超时重传
上面的确认应答描述的是一个比较理想的情况,如果网络传输过程中,出现丢包了,那么发送方势必就无法收到ACK了,下面是使用超时重传机制针对确认应答进行补充。
补充:
丢包是一个随机的事件,因此在上述tcp传输过程中,丢包就存在两种情况:
- 传输的数据丢了
- 返回的ack丢了
站在发送方的角度是无法区分这两种情况的,本质上是发送方收不到ACK了。
出现丢包的情况,发送方都会进行重新传输。
因此接收方会收到很多重复数据,TCP会有一个“接收缓冲区”,就是一个内存空间,会保存当前已经收到的数据以及数据的序列号,接收方如果发现当前发送方发来的数据是已经在接收缓冲区中存在的,接收方就会直接把这个后来的数据给丢掉,确保应用程序进行read的时候读到的只有一条数据。
补充:接收缓冲区不仅仅能进行去重,确保发送的顺序和应用程序读取的顺序是一致的。
发送方对于重传,那超时的时间(即等待的时间)如何确定的呢?
- 最理想的情况下,找到一个最小的时间,保证“确认应答一定能在这个时间内返回”,如果超过会下一次重传;
- 但是这个时间的长短,随着网络环境的不同,是有差异的;
- 如果超过时间设的太长,会影响整体的重送效率;
- 如果超过时间设的太短,有可能频繁发送重复的包;
TCP为了保证无论在任何环境下都能比较高性能的通信,初始的等待时间是可配置的,不同系统上都不一定一样,也可以通过修改内核参数来引起这里的时间变化;当然,等待的时间也是会动态变化的,每多经历一次超时,等待的时间都会变长,比如第一次重传,如果发送方还是没有收到,那么第二次重传后的等待时间会比第一次等待时间更长。
3.连接管理
连接管理包括了建立连接和断开连接,分别对应了我们平时所说的三次握手和四次挥手。在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接。
连接管理的整个流程:
建立连接
这里的建立连接,就是给对方传输一个简短的,没有业务数据的数据包,通过这个数据包来唤起对方的注意从而触发后续的操作。
TCP在建立连接的过程中需要通信双方一共“打三次招呼”才能够完成连接的建立。
说明:三次握手的过程中,涉及了两个报段,一个是ack应答报文,另一个是syn同步报文段两者都不携带业务数据。
三次握手流程:
补充:
- CLOSED:表示没有建立连接或连接关闭。
- SYN_SENT:客户端发送SYN包来发起连接。
- ESTABLISHED:建立连接,可以开始数据传输。
- SYN_RCYD:服务器收到客户端的SYN包。
为什么中间的那段ACK和SYN不可以分开发送然后变成四次握手呢?
两个数据合并在一起发送这样会使效率更高。
三次握手的作用:
确认当前网络是否畅通;发送方和接收方的发送和接收是否均正常;让通信双方在握手中针对一些重要参数进行协商。
断开连接
四次挥手用于在客户端和服务器之间终止TCP连接。这个过程确保双方都完成数据传输并安全关闭连接。
说明:四次挥手的过程中涉及了FIN结束报文段和ACK应答报文段这两段报文。
四次挥手流程:
补充:
- FIN_WAIT_1:客户端发送FIN包以关闭连接。
- FIN_WAIT_2:客户端等待结束报文段。
- CLOSE_WAIT:服务器准备关闭连接。
- LAST_ACK:服务器准备接收客户端发来的ACK。
- TIME_WAIT:客户端发送ACK后等待一段时间后关闭,等待一个2MSL(Max Segment Life ,报文最大生存时间)。
- CLOSED:关闭连接。
- 和三次握手不同,此处的四次挥手是否能够把中间的两次交互合二为一?
因为ACK和第二个FIN的触发时机是不同的,ACK是内核响应的,B收到FIN就会立即返回ACK,第二个FIN是应用程序代码触发的,B这边调用了close方法才会触发FIN。从服务器收到FIN再到执行close方法然后发起FIN这中间要经历多少时间经历多少代码是不确定的,所以不能合并。
但是如果利用TCP的机制延时应答(后面会涉及)拖延一下ACK应答时间就有可能和FIN合并在一起。
- 最后一个TIME_WAIT的作用是如果最后一个ACK丢了,B就会触发超时重传,重新发送FIN,那么服务端有了TIME_WAIT这个状态就能够收到重新发送的信息了。如果没有TIME_WAIT,客户端就会释放连接了,服务端发送的FIN就无法处理了,那么也无法收到ACK了。
4.滑动窗口
刚才上面讨论了确认应答策略,对每一个发送的数据段都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差。尤其是数据往返的时间较长的时候。
每次收到一个应答报文再发下一个数据,这个过程中性能是比较低的,那么我们可以考虑一次性发送多条数据,就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)。
其中批量传输也不是“无限”的传输,批量传输也是存在一定的上限的达到一定上限之后再统一等待ack,其中这个上限的最大数据量称为“窗口大小”。
- 这个窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节(四个字段)。
- 发送前四个段的时候,不需要等待任何ACK,直接发送。
- 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据,依次类推。
- 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答的过的数据才能从缓冲区删除掉。
- 窗口越大,则网络的吞吐率就越高。
如图(发送方的滑动窗口):
那么如果出现了丢包,如何进行重传呢?
情况一:数据包已经抵达,部分ACK却丢了
像这种情况下不需要任何重传的,因为TCP段的确认序号(每个字节的序号)的含义是当前之前的数据已经确认收到了,其采用的是累计确认机制,每一次接收数据包都会把下一个数据段的序号存到确认序号里面请求下一个数据段,当发送确认号为1001的ack=1丢失时,下一个确认号为2001的ack=1发送成功后就能够证明1-1000段的数据接收成功无需再次发送,这么理解就是主机B只有确认或请求下一段数据段就能够说明之前的就已经是收到的了。
情况二:数据包丢了
当1001~2000的这段数据段丢失时:虽然3001~4000,4001~5000,5001~6000,6001~7000都被接收方收到了(放到了接收缓冲区里,同时接收方会检查有没有缺少的数据段),但是接收方一直在等待和请求序号为1001开头的数据段,反复发送ACK号是1001的确认应答请求,当发送端收到几个同样的确认应答后进行重发,接收端就会发送一个新的ACK,确认1~7000字节的数据已经成功接收,ACK号是7001,表示接收方期望收到下一段从7001开始,这样子1~7000的内容就表明都收到了。
补充:
- 为什么不把未发送的ack都发送了?
ack是累计确认机制,序号最大的发送了ack就表明了前面的已经收到了,同时少发ack能够减少网络拥堵,提高网络传输效率。
- 快速重传机制:
依赖于接收方发送重复的ACK(相同的确认号),在这个机制下,发送方收到几次相同的ACK后会认为某个数据包丢失并进行重传。
- 如果通信双方传输的数据量比较小,也不频繁,就仍然是普通的确认应答和普通的超时重传;如果通信双方传输数据量大也比较频繁,就会进入到滑动窗口模式,按照快速重传的方式处理。
- 滑动窗口传输数据效率是会提升的,窗口越大,传输效率越大。
- 滑动窗口越大越好吗?
如果传输速度太快就可能使接收方处理不过来,此时,接收方也会出现丢包,发送方还得重传。
5.流量控制
由于接收端处理数据的速度是有限的。如果发送端发的太快,导致接收端的缓冲区被填满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等一系列连锁反应。
因此TCP支持根据接收端的处理能力来决定发送端的发送速度。这个机制就叫做流量控制(Flow
Control);
在网络协议中,尤其是在 TCP(传输控制协议) 中,滑动窗口机制既在 发送端 起作用,也在 接收端 起作用,它的作用是为了控制数据的流量和保证数据的可靠传输。
-
发送端: 发送方维护一个滑动窗口,表示在某一时间内可以发送的数据量。滑动窗口的大小通常取决于接收方的缓冲区大小以及网络的拥塞状况。发送方会根据接收方的反馈来调整滑动窗口的大小,以保证不会发送超过接收方能够处理的数据量。
-
接收端: 接收方也会维护一个滑动窗口,用来告诉发送方,它的接收缓冲区有多少空间。这就可以避免发送方向接收方发送过多的数据,导致接收方无法处理而发生丢包等问题。
流量控制机制:
- 接收端将自己可以接收的缓冲区大小放入TCP首部中的“16位窗口大小”字段,即表示接收端缓冲区剩余空间的大小,通过返回ACK的形式通知发送端调整发送速度;
- 窗口大小字段越大,说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值然后通知给发送端;
- 发送端接收到这个窗口大小之后,就会减慢自己的发送速度;
- 如果接收端缓冲区满了,就会将窗口设置为0,这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小通知给发送端。
流量控制流程图:
补充:
16位数字最大表示65535,那么TCP窗口最大就是65535字节吗?
实际上,TCP首部选项的40字节中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位。
6.拥塞控制
虽然TCP有了滑动窗口从而能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。因为网络上有很多计算机,有可能当前的网络状态就已经比较拥堵了,在不清楚当前网络状态下,贸然发送大量数据,是很有可能引起雪上加霜的情况的。
TCP引入了另一种控制机制:拥塞窗口,用于避免网络拥塞。它主要控制发送方在网络中发送数据的速率,以免网络过载。
拥塞控制机制:
- 拥塞控制的关键在于引入了一个拥塞窗口;
- 发送开始的时候定义拥塞窗口大小为1;
- 每次收到一个ACK应答,拥塞窗口加1;
- 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口。
如下图:
像这样的拥塞窗口增长速度,是指数级别的。“慢启动”只是指初始时慢,但是增长速度非常快。
- 为了不增长的那么快,因此不能使拥塞窗口单纯的加倍。
- 此处引入一个叫做慢启动阈值(ssthresh)。
- 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长。
如下图:
- 当TCP开始启动的时候,慢启动阈值等于窗口最大值;
- 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口重置为 1(丢包现象可能是由拥塞引起的,而少量的丢包仅仅是出发超时重传,大量的丢包我们就认为是网络拥塞)。
- 当TCP通信后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降。
补充:
滑动窗口和拥塞窗口的区别与联系:
- 区别:
滑动窗口主要是由接收方控制,用来告诉发送方它的接收缓冲区中还能接收多少数据。
拥塞窗口是由发送方控制,用来避免网络中的拥塞,它决定了发送方在网络中可以发送的最大数据量。
- 联系:
发送方实际发送的数据量是由这两个窗口的最小值来决定的:实际发送窗口 =min(滑动窗口,拥塞窗口)。
两者关注的对象不同却相互影响。
流量控制与拥塞控制:
两者都是在限制发送方的发送窗口的大小。
7.延时应答
如果接收数据的主机立刻返回ACK应答,这时候返回的16位窗口大小可能比较小。
延时应答原理理解:
假设接收端缓冲区为1M,一次收到了500k的数据,如果立刻应答,返回的窗口大小就是500K;
但实际上可能处理端处理的速度很快,10ms之内就把500k数据从接收缓冲区消费掉了;
在这种情况下,接收端处理还远没有达到自己的极限,即使窗口大小再放大一点,也能处理过来;
如果接收端稍微等一会再应答返回ACK,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M了。
一定要记得,窗口越大,网络吞吐量越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量地提高效率。
那么所有的包都可以延迟应答么?当然不是啦
- 数量限制:会每隔N个包就应答一次;
- 时间限制:超过最大延迟时间就应答一次;
如果所有包都延迟应答,那么传输的效率会大大下降的。
具体的数量和超过时间,依照操作系统不同也有差异;一般N取2,超过时间取200ms。
延时应答搭配上流量控制,控制发送端的发送速度,提高了数据传输的效率。
8.捎带应答
在TCP协议中,捎带应答是指在一个数据包的传输过程中,当接收方有数据需要发送回发送方时,它可以将确认应答ACK与数据一起捎带在同一包中,当然了这种捎带应答机制存在于发送方和接收方。这种机制优化了网络的效率,减少了不必要的网络传输。
捎带应答流程图:
9.面向字节流
面向字节流这个机制中,有一个最重要的问题,粘包问题。
此处的“包”是指应用层的数据包,如果发送方频繁发送小数据包时,接收方无法正确地分辨每个独立的数据包边界,此时就容易出现粘包问题。简单来说,粘包问题指的是多个小的数据包在网络中“粘在一起”,导致接收方无法明确分开每一个包的数据。
如图:
目前接收缓冲区中这三个应用层数据包的数据,就是以字节的形式紧紧挨在一起的,接收方的应用程序读取数据的时候,可以一次读一个字节,也可以读两个字节,也可以读N个字节,但是最终的目标是为了得到完整的应用层数据包。对于接收方来说读多少个字节才是一个完整的数据包,这时不知道的。
TCP数据段与UDP数据报相比,UDP是面向数据报的通信方式,就没有上述问题。
解决粘包问题:
思路:通过定义好应用层协议,明确应用层数据包之间的边界。
1.引入分隔符
这个时候应用程序读取数据的时候就可以一直持续读取数据,直到读到\n为止。
2.引入长度
应用程序读数据的时候就可以先读两个字节得到数据包的长度,在根据 长度继续读取对应字节的个数。
10.TCP异常情况
如果在使用tcp过程中出现意外会如何处理呢?
1)进程崩溃
进程没了,异常终止了,文件描述表也就释放了,相当于调用了socket.close()。
此时就会触发FIN,对方收到之后,自然就会返回FIN和ACK,这边再进行ACK(四次挥手流程)。
TCP的连接,可以独立于进程存在(进程没了,TCP连接不一定没)
进程断开后,tcp连接还是存在的,而且是在四次挥手的过程,等到四次挥手结束之后,连接就断开了。
2)主机关机
在进行关机的时候,就是会先触发强制终止进程操作(相当于与第一种),此时就会触发FIN,对方收到之后,自然就会返回FIN和ACK。不仅仅是整个进程没了,整个系统也可能关闭了,如果在系统关闭之前,对端返回的ACK和FIN到了,此时系统还是可以返回ACK,进行正常的四次挥手。如果系统已经关闭了,对端发来的ACK和FIN迟到了,无法进行后续ACK的响应,这种情况站在对端的角度,对端以为是自己的FIN丢包了,重传FIN,但是重传几次都没有响应,自然就会放弃连接,把持有对端的信息删了。
3)主机没电而关机(非正常)
此时这是一瞬间的事情,来不及杀进程也来不及发送FIN主机直接就停机了。
站在对端的角度,对端不一定知道这个事情,从发送方和接收方两个角度去看:
1.接收方掉电
如果对端是在发送数据,发送的数据就会一直等待ACK,触发超时重传,触发TCP连接重置功能,发起“复位报文段(RST)”,如果复位报文段发过去之后也没有效果此时就会释放连接了。
2.发送方掉电
如果对端在接收数据,对端还在等待等了半天没有消息,此时其实无法区分是对端没发消息还是对方掉线了。
TCP中提供了心跳包机制:接收方也会周期性的给发送方发起一个特殊的不携带业务数据的数据包,并且期望对方返回一个应答,如果对方没有应答,并且重复了多次之后仍然没有应答就视为对方掉线了,此时就可以单方面的释放连接了。
4)网线断开
网线断开和刚才的主机掉电这两种情况一样的。
好啦,这些是TCP协议的内容讲解,可能会有些不足,感谢阅读!!!