文章目录
- 可靠传输机制
- 1.确认应答
- 2.超时重传
- 2.连接管理
- 1.三次握手
- 2.四次挥手
- 传输效率
- 1.滑动窗口
- 2.流量控制
- 3.拥塞控制
- 4.延时应答
- 5.捎带应答
- 面向字节流
- 粘包问题
- TCP异常情况
可靠传输机制
可靠性:即发送方知道数据是发送成功了,还是失败了。
1.确认应答
确认应答是TCP实现可靠传输最为核心的机制。
主机A给主机B发送消息,B收到之后返回一个应答报文(ACK),A收到应答报文后就知道第一条消息发送成功了,于是继续发第二条消息…
-
确认应答:即应答报文,也称为ACK报文。
-
序号:类似数据(11000)等,TCP是面向字节流的,这里的11000这类数字就是1~1000个字节,每个字节都编有序号。因为字节编有序号,所以避免了发送的数据乱序的问题, 。
TCP字节序号是依次累加的,第一条数据字节序号11000,第二条数据就是10012000。由于这1000个字节都属于一个TCP报文,所以每个TCP数据报头只存当前数据的第一个序号,比如第二条数据的TCP报文里存1001,再根据 TCP报文长度,就知道这条数据是1001~2000。
-
确认序号:每个确认应答都带有数字(确认应答1001),这类数字就是确认序号,而确认序号只有应答报文拥有,ACK标志位为1,表示为应答报文,ACK标志位为0,表示不是应答报文。确认序号表示成功收到哪些数据,下一条数据从哪里开始发。确认序号返回的是收到当前数据最后一个字节序号的下一个序号,列如:收到11000个字节数据,返回1001,表示1001之前的数据都收到了,于是下一条消息是从编号为1001的位置开始发,即10012000。
2.超时重传
丢包分为两种情况:一是发送数据丢了,二是返回的ACK丢了。为了解决丢包问题,TCP引入了超时重传机制,在丢包的情况下,重新发送一份相同的数据。如何判断丢包呢?TCP引入了一个时间阈值,发送方传输一个数据,在一个规定的时间内,如果未收到ACK,不管三七二十一,都表示为丢包了,进行重传。
数据丢包:主机A发送的数据丢包了,在隔了一定的时间后,未收到主机B发来的确认应答,则会再次传输1~1000的数据
ACK丢包:可以发现主机B收到了两次1~1000这个数据,TCP会根据这种重复数据进行去重,利用TCP的接收缓冲区进行去重。这个缓冲区相当于一个阻塞队列,当主机B读到了主机A发送的数据,主机B把这个数据放入这个阻塞队列中,再根据数据的序号排序、去重,如果序号重复则丢弃后来的数据。
超时重传可能会重传几次,当重传次数达到一定的次数时,TCP会尝试重连,如果重连失败则彻底断开连接。重传的时候,第一次重传时间和第二次时间间隔不一样,重传次数越多时间间隔越大。
2.连接管理
在正常情况下,TCP要经过三次握手建立连接,通过四次挥手断开连接,所谓连接本质上是通信双方互存储了对方的信息,管理则是描述了连接如何连接和断开。
1.三次握手
举一个比较形象的列子:A先发一条消息,B收到后并响应,同时B再发送一条消息给A,A收到后并响应。
三次握手本质上是四次交互,我们形象的把每次通信比喻成一次握手。通信双方,各自要发送一个“建立连接”的请求并各自返回对方一个ACK应答报文。由于主机B的响应和发送新消息(好啊+确认哈…)这两次交互的时间非常近,于是就把这两次交互合并为一次,也就成了三次握手;并且这两次合并是必须的,因为每次通信都需要封装分用,两次封装分用肯定要比一次封装分用的成本高。
问题:四次合成了三次,那么直接合成两次可以完成吗?
答:显然是不能的。当主机A发送了一条消息:明天一起吃饭?主机B响应并发送新消息:好啊,确认哈?一起吃饭,别放鸽子。此时,主机A知道了主机B同意明天一起吃饭,但是主机B并不完全确定主机A会不会放鸽子。那么明天A和B可能也就吃不成饭了,也就意味着主机A和主机B连接建立不了!
三次握手的意义:一是建立通信双方的认同,保存对方的信息,二是为了验证通信双方各自的发送和接收是否正常;三是在每次交互会夹带一些重要信息,来协商一些重要的参数。
客户端主动给服务器发起的建立连接请求,称为“SYN”/同步报文段(synchronize),对应着TCP报文结构里的“SYN”,然后服务器进行响应返回“SYN+ACK”,说明这条交互既是同步报文段,又是确认应答报文段,最后客户端返回一个“ACK”应答报文。
TCP连接有11种状态,分别是:LISTEN、SYN_SENT、SYN_RECEIVED、ESTABLISHED、FIN_WAIT_1、FIN_WAIT_2、CLOSE_WAIT、CLOSING、LAST_ACK、TIME_WAIT和CLOSED。
建立连接时的状态:
LISTEN:表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接;
SYN_SENT:表示在发送连接请求后等待匹配的连接请求;
SYN_RECEIVED:表示在收到和发送一个连接请求后等待对连接请求的确认;
ESTABLISHED:表示已经建立了连接,可以进行数据传输;上图中客户端和服务器都有这个状态。
CLOSED表示TCP连接已经关闭 。
2.四次挥手
挥手和握手一样都是形象的比喻。四次挥手也是通信双方各自对对方发送一个断开连接的请求,再各自发送一个响应。
问题:中间两次是否能合并?(到了+你呢?)
答:显然不行,三次握手的两次交互之所以可以合并是因为这两次交互是同一时机,这一操作是纯内核操作,是我们感受不到的,当服务器收到syn后会立即返回ack;而四次挥手则不同,这两次交互是不同时机,当主机A的应用程序执行到socket的close方法时会触发FIN结束报文段,服务器接收到FIN后,内核会立即返回ACK,FIN是人为发送的,而ACK是由系统内核控制的,当主机B的应用程序执行到socket的close方法时也会触发FIN,应用程序执行到close方法前可能要经过很多道其他的程序代码,于是和ACK的时机相隔太久,也就不能合并为一次交互。
断开连接时的状态:
CLOSE_WAIT:表示被动断开连接一方等待FIN报文;
TIME_WAIT:表示主动断开连接的一方收到了对方的FIN报文,但是还没有发送ACK报文;
TIME_WAIT的意义:
TIME_WAIT状态站在客户端视角认为已经断开连接了,但是此时TIME_WAIT会让连接状态继续保留,因为最后一个ACK还没有发出去,如果最后一个ACK丢包,服务器会进行超时重传,重新传一个FIN,客户端再重新发一个ACK,因此TIME_WAIT状态保留连接状态一定时间,就是为了有能力进行重传,如果重传的还是丢了,时间也到了,那么就认定为最后ACK没丢,直接断开连接。
TIME_WAIT保留时间为2MSL;(Max Segment Life,报文最大生存时
间)2MSL表示为互联网上,两个节点之间数据传输消耗的最大时间,这个时间通常为60s。
传输效率
1.滑动窗口
活动窗口的目的是为了在可靠性传输的基础上提高传输效率。
基础的确认应答机制:主机A发一次数据,主机B只要收到一条数据就返回一个ACK,而主机A此时在没有收到ACK的时候,需要进行等待,才能发送下一条数据,每一次发送数据都要等一次,这就造成了不少的等待时间。
加入活动窗口后的确认应答机制:主机A不再需要等待第一条数据的ACK,可以一次性发送多条数据,再使用一份等待时间来等待一组数据返回的多个ACK,这就减少了多条等待ACK的时间。
下图是主机A一次性批量发送四条数据,主机B再批量返回四个ACK,收到一条数据就返回一条ACK,依次类推,只是说主机A不需要等第一个ACK,就可以连续发4条数据。
把不需要等待就可以发送的最大的数据量就称为:“窗口大小”;上图这个“窗口大小”为4000。
发送一个窗口大小的数据后,主机A并不是要等这一个窗口大小的ACK全部返回了,才继续发送下面的数据,而是主机B接收到一条数据就返回一条ACK,主机A收到一个ACK就发一条数据,这样就保证了等待的ACK永远都是4条(窗口大小,处理的数据永远都是4条,而不是处理一条就没事做了)。
举个例子:比如一个人有4台电脑一台电脑可以刷一门课,同时只能刷4门课,需要刷满10门课,此时“窗口大小”就是4。当我刷完一门课后,手上正在刷的课为3门,那么就有一台电脑是空闲状态,于是我就可以再找一门课继续刷,一直保持刷4门课的状态。相当于用1门课的时间刷了4门课,用最少的时间刷完10门。
每次刷完一门课,就立即开始下一门,窗口大小始终未变,但是课程变了。类似于下图,本来需要等10015000的ACK,但是收到了2001的ACK,说明2001之前的数据已经被确认,此时可以立即发送50016000的数据,等待的ACK范围就成了2001~6000,这种现象看起来就像是在滑窗口,于是就形象的比喻成滑动窗口
-
滑动窗口情况下,丢包了怎么解决?
-
ACK丢了:不需要做出任何处理!因为有确认序号,当返回2001的ACK,说明2001之前的数据都到达了,2001包含了1~2000的数据序号。
-
数据包丢了:下图显示,当10012000的数据丢了,主机B返回的是1001的ACK,意思是索要主机A1001开头的数据,主机A连续发送多条数据后收到的依旧是1001的ACK,这时主机A就会发送10012000的数据。当主机B成功收到10012000的数据后,由于主机A之前已经发送20017000的数据,并且主机B成功收到了,于是这次返回的是7001的ACK,说明7001之前的数据都收到了(包含10012000的数据),也就意味着主机A下次发送的数据是从7001开始。假设,40015000也丢包了,此时是返回的ACK是5001,而不是7001,即使主机A已经把数据发送到了7000。总结:丢哪个包就索要哪个包的数据。
这种重传方式称为“快速重传”,表示只重传丢失的数据。
-
2.流量控制
滑动窗口提高了传输效率,可是窗口也不能无限大,因为接收方处理能力有限。流量控制的意义是为了根据接收方的处理能力来控制发送方的传输效率。
那么如何衡量接收方的处理能力呢?
可以根据接收方的接收缓冲区剩余大小来判断,比如传输10个,接收缓冲区还剩下20个,说明下次可以传输20个,或者30个,接收方都能处理过来。
比如主机A给主机B发送了一条数据,B就算一下接收缓冲区剩余空间,再把这个结果通过ACK返回给A,A根据这个结果来决定下次发送的窗口大小是多少,是连续发10个还是20个。
TCP报文结构里有16位窗口大小,即默认为64kb,而TCP为了让窗口大小可以更大,在选项中引入了窗口大小的扩展因子,比如扩展因子里是2,表示64kb左移2位即256kb。
上图显示:第一次A传输了11000的数据,B返回1001ACK,接收缓冲区剩余大小为3000,A再次发送10014000的数据,B依次返回三个ACK 当B的接收缓冲区为0了,A机会进行等待,在等待时间,A会发送一个窗口探测报文来询问B的接收缓冲区是否有空余。
3.拥塞控制
流量控制和拥塞控制共同决定发送方的窗口大小。
流量控制表示接收方的处理能力;
拥塞控制表示传输过程中的中间节点的处理能力(中间节点:路由器,运营商等)。
中间节点的测量:通过实验来测试出一个合适的值。比如先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;
下图表示实验中间节点的窗口大小:第0轮。窗口大小为1,没有丢包就扩大窗口,第二次就为2…以指数增长;当增长速率达到阈值时,变成线性增长;当丢包了也就是遇到网路拥堵,窗口大小一下子变为一个很小的值,再重新按指数增长线性增长,此时的增长速率阈值为上次网路拥堵的值除2…依次类推,直到找到不是网络拥堵的瓶颈并且是一个最大的值 。拥塞窗口会根据当前网络环境一直动态的调整。
4.延时应答
延时应答:收到数据后,不是立刻返回ACK,而是稍等再返回。
在滑动窗口上进行改进,比如主机A第一次发送数据,B的原本接收缓冲区大小为1M,B计算后立即返回的ACK窗口大小为500kb,那么下次A传输的数据就是500kb;但是如果要是稍等再返回ACK,在这个稍等的时间内主机B把这个500kb给消费掉,那么返回的窗口大小就为1M,下次A就可以发1M的数据,从而提高传输效率。
上图显示,实际上延时应答采取的方式为在滑动窗口下,ACK不再每一条数据都返回,比如上图隔一条返回一个ACK。
5.捎带应答
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 “一发一收” 的。意味着客户端给服务器说了 “How are you”,服务器也会给客户端回一个 “Fine, thank you”;因为TCP存在延时应答,ACK等了一会,那么这个时候ACK就可以搭顺风车,和服务器回应的 “Fine,thank you” 一起回给客户端
由于内核返回的ACK存在延时应答,于是等待,这时候应用层发了一条数据刚好和延时应答后返回的ACK时机相同,于是这两次合并为一次。
面向字节流
粘包问题
接收缓冲区实际上是把收到的数据都放在一起,由于TCP的字节流的,但是应用层序read读取时,可以一次read一个字节或多个字节,读到哪里才算是一个完整的应用层数据报呢?这就导致可能read读取到的是半个数据报或是多个数据报。就好比下图,假设下图都是主机B返回的数据,主机A,read读取时,一次读8个字节,读取到了:“abcdefgh”,这种情况就叫粘包问题
解决方案:在应用层约定每个数据报之间用什么分隔符区分,比如用“\n",或者约定每个数据报的长度。
TCP异常情况
-
进程崩溃:进程终止会释放文件描述符,仍然会完成四次挥手。和正常关闭没有什么区别。
-
主机正常关机:和进程崩溃一样。
-
主机掉电/网线断开:
- 接收方掉电:发送方接收不到ACK,超时重传几次,会尝试重置连接,最后彻底放弃连接。
- 发送方掉电:接收方收取不到数据,于是会周期性的发送一个消息,确认对方是否正常工作,这种操作称为“心跳包”。通过“心跳包”来确认通信双方是否正常。
-
进程崩溃:进程终止会释放文件描述符,仍然会完成四次挥手。和正常关闭没有什么区别。
-
主机正常关机:和进程崩溃一样。
-
主机掉电/网线断开:
- 接收方掉电:发送方接收不到ACK,超时重传几次,会尝试重置连接,最后彻底放弃连接。
- 发送方掉电:接收方收取不到数据,于是会周期性的发送一个消息,确认对方是否正常工作,这种操作称为“心跳包”。通过“心跳包”来确认通信双方是否正常。