如果是第一次学可能会看懵掉,没关系,把下面内容看完再来看报文格式你就会很通透
当男生看到女生的回复后,就说明男生发的信息已经成功到达了,如果没有收到回复就可以认为上次发的数据丢失了。这就是确认应答机制,女生回复的“好的好的”就是应答报文ack。
普通报文,ack为0;应答报文,ack为1
确认应答机制是TCP保证可靠性的最核心机制
当发送多条信息时可能出现下面情况:
此时女生后发的信息,男生先收到,让男生误以为女生答应了做他女朋友的请求,这是由于网络情况很复杂,很有可能“后发先至”。
解决方案就是对请求和返回的信息进行编号。
TCP是字节流协议,编号的时候,也是以字节为单位
当数据在传输过程中可能会发生丢包,因为网络环境很复杂,中间要经过很多路由器/交换机,某个交换机不但要传输我的数据还要传输别人的数据报,有很多很多数据报都要经过这个交换机,而交换机转发的能力是有上限的,很多数据报都经过该交换机,就会达到交换机的转发上限,就有可能导致有一部分数据报超时了。(类似于堵车)
当数据丢包的时候就要进行“超时重传”,丢包有两种情况一种是数据丢包,一种是应答报文丢了
当男生等了一段时间没收到女生的回应后,男生又在发了一次。这就是超时重传
第一次发生丢包时,发送方就会在达到超时时间的阈值之后,进行重传。如果重传的数据仍然没响应,就会继续重传,但是第二次超时的时间比第二次长,超时时间是逐渐变大的(假设一次丢包的概率10%,那两次都丢包的概率就是1%,说明网络情况比较差,后序在重传大概率也无法成功,不如降低传输频率,节省点主机开销)。如果重传几次后,仍然无法传输,就会尝试重置TCP连接(断开重连),如果还是连不上,就会释放连接。
站在发送者的角度看,只知道没收到ACK,无法区分是发的数据丢了还是ACK丢了,因此发送者都会重传。如果是ACK丢包,发送者又重传就会导致接收方收到两份相同的数据,所以TCP会对相同的信息进行去重(根据序号进行去重)
TCP的连接是逻辑上虚拟连接,主机A和主机B建立连接,主机A的系统内核里有一个数据结构,包含了和他连接的是谁(IP,端口,使用的协议);主机B的内核的一个数据结构也记录了和他连接的谁(IP,端口,使用的协议)
A发送syn尝试和B建立连接,主机B的内核立即返回ack接收对方的连接,内核同时也发送syn尝试和主机A建立连接,主机A返回ack接收对方的连接。建立连接的过程有四次数据交互过程,只不过主机B返回的ack和syn都是内核同时立即发送的,所以看起来就是三次的。
三次握手的意义:
例如TCP的序号并不是从1开始的,通常都是建立连接的时候协商一个数字,目的是保证两次连接的序号有差别,如果连接断开又快速重连,接收方就可以区分当前收到的数据是当前连接的还是上一个连接的。
两个重要的TCP状态
四次挥手在某些情况下也会变成三次:TCP的捎带应答机制,会让ACK晚点发送,有可能和FIN(结束报文端)一起发送
在没有引入滑动窗口之前是一问一答,主机A大量的时间都消耗在等待ACK上。因此提高效率的方法就是不等ACK,直接发送下一条数据。当然这里说的不等也不是完全不等而是每次批量发送一波数据,然后再等一波ACK,再发一波数据。这里不需要等待直接发送的数据的量,就称为“窗口大小”
批量发送4条数据,批量等待4条ACK,此时窗口大小就是4000字节。注意:主机A收到一条ACK就继续发一条数据,而不是等所有的ACK都到了,才统一发下一组。
发生一整波数据如果出现丢包/乱序时怎么办?
这里的1001表示小于1001的数据都收到了,2001表示小于2001的数据都收到了,所以即使返回1001这个ACK丢了,下一次返回的2001就说明1-2000的数据已经收到了,包括了第一次的数据。这就类似于别人问你现在是高几呀?你说我已经大二了。
因此ACK丢包不需要任何处理
在上面的过程中只是把丢的包进行了重传,没丢的包,没有重传,效率比较高,因此被称为“快速重传”(搭配滑动窗口机制的超时重传)
有了快速重传,超时重传还有意义吗?
在传输数据量很多,批量传输时自然是遵守快速重传的方式,如果传输的数据很少,此时仍然是按照超时重传的方式进行。在滑动窗口中如果是最后一个包丢了就是按照超时重传的方式。
窗口越大发送的速度越快,那窗口可以无限大吗?
发送的速度快了但是接收方的处理速度跟不上就会导致接收方丢弃一部分数据,而TCP需要保证可靠性的,TCP就要重传这些数据,这就导致了恶性循环。
在滑动窗口的基础上对发送速率做出限制的机制,根据接收方的接收能力,来反向影响发送方的发送速率。
接收方的接收速率如何衡量?
接收方使用接收缓冲区的剩余空间大小,来作为发送方发送速率(窗口大小)的参考数值
主机B收到的数据,就会先放到系统内核的接收缓冲区中,所谓缓冲区就是一个内存空间(字节数组)等待B这边的应用程序,通过Socket把数据从接收缓冲区中读取走。这就类似于生产者消费者模型A是生产者,往B的接收缓冲区生产数据,B的应用程序,从缓冲区中消费数据。
接收方B收到A的数据后,就会在ACK应答报文中,把当前接收缓冲区的剩余空间大小的值,反馈给发送方。
流量控制是通过接收方的接收速率,接收缓冲区剩余空间大小来衡量发送方的速率。在网络中不仅要考虑接收方还要考虑中间转发节点的情况。
如何衡量中间节点的情况?
把中间设备视为一个整体,通过实验来验证发送速度多少合适。开始的时候,发的慢一点,如果网络通畅就提高速度,提高到一定程度,发生丢包了,就再降低速度;速度降低后,发现又通畅了,就再提高速度。
基于流量控制,引入的提高效率的机制,尽量让返回的接收缓冲区空间大一些,这样滑动窗口也就大一些,效率提高了。
基于延时应答的基础上引入的
在4次挥手中说过,在某些情况下由于捎带应答机制可以变成3次
TCP中只要把数据传输过去,对方收到之后,就会立即由内核返回一个ack报文,响应数据则是在应用程序中负责传输。由于这两操作是不同时机传输的,因此不能合并在一起,但是延时应答机制让这成为了可能。ACK原本是要立即返回的,但是由于延迟应答,稍等一会才返回,在某些情况下业务也正好在这个时候返回响应,此时就可以把这两个报文,合二为一了。
要想解决粘包问题就要在应用层协议进行区分,只要定义应用层数据协议的时候,明确包和包之间的边界即可
1.通过分隔符,比如约定使用 ; 作为包结束的标记
2.指定包的长度,比如在数据包的开头位置声明长度
自定义应用层协议的几个典型实现,像xml,json,http这些都解决了粘包问题
进程异常退出,操作系统会回收进程资源,包括释放文件描述符表,这样的释放操作就相当于调用了对应的socket的close方法,执行close就会触发FIN结束报文,进一步进行四次挥手。
关机的时候,系统会强制结束所有用户进程,和上述的进程崩溃类似。系统内核会进行文件描述符表的释放操作,进一步进行四次挥手
此时发送方不知道对方挂了,继续发送数据,没有收到ACK,发送方就会触发超时重传,重传几次还没有应答就会尝试重置连接,失败后就会断开连接。
接收方等了一段时间后,就会发送一个“心跳包”(心跳包是周期性触发的,只是一个简单的不携带任何业务数据的包)存在的意义就是确认一下对方是否存在,如果对方不返回心跳包就说明对方挂了,此时就放弃连接了。
情况和主机掉电一样,只不过通信双方主机是正常的,通信双方各自按照上述的两种情况进行
13. 常见面试题
如何将UDP实现为可靠传输?
基于UDP的应用层实现确认应答机制,引入序列号,超时重传机制。