目录
一、TCP
1、特性
TCP协议是有连接、可靠传输、面向字节流和全双工的传输层通信协议
TCP可靠性的基石:
- 传输顺利时,使用确认应答保证可靠性。
- 出现丢包时,使用超时重传作为补充。
2、确认应答
(1)、定义
发送方在发送完数据之后,为了确认接收方是否收到数据,接收方会返回一个应答报文,表示自己已经收到数据。
注:确认应答是实现可靠性的核心机制。
例:
A向B发送一个申请,B回复一个应答报文 。
回复可能会因为一些原因导致顺序发送改变。为了解决这个问题,就需要针对消息进行编号,给发送的消息分配一个序号,同时应答报文,给出确认序号。
(2)、原理
确认序号的规则,不是说发送方的序号是什么,确认序号就是什么,而是取得发送方送过来的所有数据的最后一个字节的下一个字节的序号 。
TCP将每个字节的数据都进行了编号,即为序列号
发送方的确认序号是一个无意义的数据,接收方的序号和发送方的序号无关
确认序号1001的含义:
- 小于1001的数据表示已经收到
- 接下来要向发送方索要从1001开始的数据
接收方可以通过ack的确认序号,告诉发送方哪些数据已经收到了
(3)、接收缓冲区
对于TCP来说,自身也承担整个队列的任务,TCP会有一个接收缓冲区(一块内核中的内存空间),每个socket都有一份自己的缓冲区。TCP可以按照序号针对收到的消息进行整队。
应用程序读数据时,读到的一定是有序的和发送顺序一样。
3、超时重传
(1)、丢包
丢包是网络上非常典型的情况,由于网络环境复杂,数据传输过程中难免遇到意外,每一次数据传输都有可能丢失。
每个设备都承担很多的转发任务,中间任何一个节点出了问题都可能导致丢包。每个设备转发能力都是有上限的,某一时刻某个设备上面的流量达到峰值就可能引起部分数据被丢包
(2)、定义
每次数据传输如果成功,都应收到ack。如果包丢了接收方就收不到,就不会返回ack。
发送方拿不到应答报文,等待一段时间后还是没有收到应答报文,发送方就视刚才的数据丢包了,这就会触发超时重传机制,重新发送一次数据。
(3)、分类
- 数据直接丢了,接收方没有收到,所以不会发送ack
- 接收方收到数据,但返回的ack丢了
发送方是区分不了这两种情况,只能重传。
注:
TCP针对多个包丢失,处理思路是继续超时重传。但是连续触发超时重传会让等待时间增加,重传的频率降低。超过一定次数后还没收到ack后就会断开连接,TCP会尝试重置连接,如果重置连接也失效,TCP就会关闭连接,放弃传输数据。(能重传就重传,传不了就关闭,尽最大可能完成传输)
二、连接管理
1、特殊比特位
这6个特殊的比特位默认是0,如果为1表示特定含义:
- 第二位是ack,如果这一位为1,表示当前TCP数据报是一个应答报文
- 第五位是syn,如果这一位为1,表示当前TCP数据报是一个同步报文
- 第六位是fin,如果这一位为1,表示当前TCP数据报是一个结束报文
2、TCP建立连接——三次握手
(1)、定义
握手指的是通信双方,各自进行一次网络交互。相当于客户端和服务器之间,通过三次交互,建立了连接关系,双方各自记录对方的消息。
三次握手这个过程本质上是投石问路,验证了客户端和服务器各自的发送能力和接收能力是否正常。
- 首先客户端向服务器发送syn(连接请求)
- 服务器接收到syn后,向客户端返回ack,同时服务器向客户端发送syn
- 客户端收到syn后向服务器返回ack
注:上述过程由内核自动完成,应用程序干预不了。等到连接完成,服务器accept把建立好的连接从内核拿到应用程序中。
例:
- B收到了A的问话,此时B知道:A麦克风正常,自己的耳机正常
- A收到了B的回答和问话,A知道:自己的耳机和麦克风正常;B的耳机和麦克风正常。
- B收到A的回答,B知道:自己的麦克风正常;A的耳机正常。
确认了客户端和服务器各自的发送能力和接收能力都正常,这就是后续可靠传输的基础
注:上述流程中间的syn和ack拆开分别发送同样能够达成目的。但是没有必要,分层两次发送效率不如合并成一次(封装和分用)。
3、TCP断开连接——四次挥手
(1)、定义
通信双方,各自给对方发送一个fin(结束报文),再各自给对方返回ack。
- 客户端向服务器发送一个fin(结束报文)
- 服务器接收到fin后,向客户端返回一个ack
- 服务器同时向客户端发送一个fin
- 客户端接收到fin后向服务器返回一个ac
注:ack和fin有一定概率合并成一个的,但是通常情况下不能合并。
- 三次握手:ack和syn是同一个时机触发的(都是内核来完成的)
- 四次挥手:ack和fin则是不同实际触发的,ack是内核完成的,会在收到fin时的第一时间返回;fin则是应用程序代码控制的,在调用到socket的close方法时才会触发fin
三、效率机制和安全机制
1、滑动窗口——效率机制
(1)、定义
对每一个发送的数据段,都要给一个ack确认应答。收到ack后再发送下一个数据段,这样做有一个比较大的缺点就是性能较差,尤其是数据往返的时间较长的时候。
TCP要保证的不仅仅是可靠性还有效率,提升可靠性往往意味着损失效率。因此为了弥补效率我们引入了滑动窗口这一机制:
相比发送一条数据, 收到ACK后发送下一条, 滑动窗口可以一次性发送 N 条数据报,收到 M 条 ACK 的应答后, 窗口向右移动M个位置,并继续发送窗口中没有发送的数据。这样就可以做到将多个 ACK 的等待时间重叠在一起,使用一份时间等待多个ack,总的等待时间缩短了,整体的效率就提升了。
批量发送数据:一次发送多条数据,一次等多个ack,称为滑动窗口
注意:UDP更快,TCP再怎么提高效率都不可能比UDP快,TCP的效率机制本质上是让性能折损少一点。
(2)、原理
批量发送不是无限的,是发送到一定程度就等待ack。不等待直接发送的数据量是有上限的,而且回来一个ack就立即发下一条,相当于总的要批量等待的数据是一致的,把批量等待数据的数量就称为“窗口大小” 。
批量发了四条数据就等待四个ack,红色的区域就相当于等待窗口
当收到2001这个ack意味着1001-2000这个数据得到了确认,此时就会立即发下一个5001-6000这个数据。此时看到的效果就好像窗口还是这么大,但是往后挪了一个格子。如果收到的ack非常快,此时这个窗口就在快速的往后滑动。
(3)、批量发送过程中出现丢包
【1】、ack丢了
例:当收到2001这个ack时,此时发送方就知道了,2001之前的数据收到了。1-1000这个数据也收到了,1001这个ack丢了无所谓。如果是最后一个丢了,就照常超时重传。滑动窗口是锦上添花,不是说前面的超时重传就没了。如果批量发送,滑动窗口说了算。
注:这种情况没事,即使丢了这么多ack,对于可靠性没有任何影响。确认序号的含义,表示该序号之前的数据都已经收到了。后一个ack能够涵盖前一个ack的意思。
【2】、数据丢了
上述重传过程没有任何冗余的的操作,丢了数据才会重传,不丢的数据不必重传。整体数据是比较快的,这个重传过程也称为快速重返。
当1001这个数据重传过来之后,此时缺失的拼图就补全了,接下来就要从7001开始索要
如果是4001也没有,收到1001-2000之后,接下来返回ack就是索要4001,也是同理,反复索要多次发送发就会重传4001。
注:滑动窗口,快速重传,是在批量传输大量数据时,会采取的操作。如果你就只传输一条两条,少量的,低频的操作。就不会按滑动窗口这么做,仍然是前面朴素的确认应答和超时重传。
2、流量控制——安全机制
(1)、定义
滑动窗口,批量发送。窗口越大,批量发送的数据越多,整体的速度就越快。但是窗口不能是无限大,如果发送太快瞬间把接受方接收缓冲区给打满了,接下来继续发送,此时数据就会丢包。
TCP支持根据接收端的处理能力来决定发送端的发送速度,这个机制就叫做流量控制。
因此我们可以通过流量控制来解决问题,本质上就是让接收方来限制一下发送方的速度。
(2)、原理
在网络通信中, 发送方的数据会通过网卡发送到接收缓冲区中, 接收缓冲区在操作系统内核中,可以视为一个阻塞队列,而接收方的应用程序则是会不断从接收缓冲区中处理数据。
- 接收端将自己可以接收的缓冲区大小放入TCP首部中的窗口大小字段,通过ack端通知发送端。窗口大小字段越大,说明网络的吞吐量越高。
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端。
- 发送端接受到这个窗口之后,就会减慢发送速度。
- 如果接收端缓冲区满了,就会将窗口置为0。发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。
接收方计算窗口大小:简答粗暴,直接拿接收缓冲区,剩余空间作为窗口大小。
如果接收缓冲区满了,这时窗口为0,也就是发送方暂时发不了数据了,只能等待缓冲区位置空出来,这期间会向接收方发送探测报文,这个数据报的作用就是让接收方返回带有滑动窗口大小数据的 ACK 数据报, 以重新调整窗口大小。
3、拥塞机制——安全机制
(1)、定义
虽然TCP有滑动窗口能够高效可靠的发送大量的数据,但滑动窗口的大小取决于流量控制和拥塞控制。流量控制是衡量接收方的处理能力,拥塞控制则是衡量中间节点的传输能力。
在不清楚当前网络状态下,冒然发送大量数据会出现问题,可能当前的网络状态就已经比较拥堵。
因此TCP引入拥塞机制即慢启动机制,先发少量的数据,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。
(2)、原理
- 引入一个概念为拥塞窗口
- 发送开始时,定义拥塞窗口大小为1
- 每次收到一个ack应答,拥塞窗口加1
- 每次发送数据包时,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口
例:
- 如上图,刚开始传输会给一个非常小的窗口(比较小的初始速度),慢开始
- 之后每次翻倍指数增长,可以让窗口大小在短时间内达到一个比较大的值,快速接近当前网络传输路径的能力瓶颈
- 当指数增长达到一定阈值就变成线性增长,使传输速度逐渐接近传输上限,避免一下突然超过上限很多
- 直到增长到一定程度出现丢包,认为当前的窗口大小已经达到当前路径的传输上限
- 此时又立即把窗口大小回归到一个较小的初始值,重复上述过程
4、延迟应答——效率机制
(1)、定义
TCP中决定传输效率的关键元素是窗口大小,而决定窗口大小的关键则是流量控制中接收方的接收缓冲区剩余空间大小。因此接收方在收到数据传输后,先处理缓冲区中的数据,暂缓ack的发送,以增大窗口提高效率。
例:
假设接收端缓冲区为1M,一次收到了500K的数据,如果立刻应答返回的窗口就是500K。但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了。在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来。如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M。
注:窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率
(2)、包的延迟应答限制
- 数量限制:每隔N个包就应答一次;
- 时间限制:超过最大延迟时间就应答一次;
5、 捎带应答——效率机制
(1)、定义
在延迟应答的基础上,客户端服务器在应用层也是 "一发一收" 的。这意味客户端传给服务器一个request,服务器也会传给客户端回一个response和ack。ack由内核负责是立即返回的,response是通过代码执行到才返回的。但是通过延时应答机制,让ack稍等一会再发送,response就可以和ack合并成一个数据报发送给客户端,增加了效率。
注:捎带应答可以用于四次挥手,使其变为三次挥手
四、面向字节流
1、粘包问题
当A给B连续发送多个应用层数据报之后,这些数据积累到B的接收缓冲区中,紧紧挨在一起。此时B的应用程序在读数据时,就难以区分从哪个数据到哪个数据是一个完整的应用层数据报,很容易产生少读或多读的问题。
2、解决方法
- 定义分隔符
- 约定长度(例:约定前4个字节表示整个数据报的长度)
注:以上两种方法都是自定义应用层协议 。类似的xml和json本质上都是通过分隔符来实现的,HTTP协议既会使用分隔符也会使用约定长度
五、异常情况
1、进程关闭/进程崩溃
进程销毁后socket文件也随之被关闭,但连接还在仍然可以继续四次挥手。
2、主机关机
先杀死所有的用户进程,也会触发四次挥手。
如果四次握手没有完成,例:
对方发的fin过来后,此电脑还没来得及发送ack就关机。此时对端就会重传fin,多次重传后还是没有ack就会尝试重置连接,没有效果就直接释放连接。
3、主机断电
(1)、对端是发送方
与主机关机执行机制顺序相同:接收不到ack->超时重传->重置连接->释放连接
(2)、对端是接收方
对端无法立即知道此电脑是未发送新的数据还是直接关机。因此TCP内置了心跳包(保活机制),对端会定期给此电脑发送一个心跳包,此电脑会返回一个回应。如果每个心跳包都有及时的回应,就说明当前的状态良好。如果心跳包发送过去后没有回应,就说明此电脑关闭。
注:一般心跳判定不会很严格,只有连续多次没有回应才会视为连接异常
4、网线断开
执行顺序同上述主机断电。