🍒3.4连接管理(三次握手,四次挥手)
🍉3.4.1三次握手
三次握手就是类似于有两个人进行打电话环节:
●A:你好,我是A,你能听到我说话吗?
●B:你好A能听到你说话;你能听到我说话吗?
●A:能听到!
其实发送SYN本质就是将SYN置为1,发送ACK的本质就是将ACK置为1,SYN与ACK同时发就是将这两位同时置为1,同理其他的也是如此
上面的状态我们了解一下即可:
●CLOSED 表示客户端或服务器处于关闭状态
●LISTEN 表示服务器就绪,等待服务器连接状态
●SYN_RCVD 表示服务器已经收到客户端的连接请求,发送ACK和SYN并进入阻塞等待客户端连接状态,一般此状态存在时间很短
●SYN_SENT 表示客户端连接请求已发送,此时客户端进入阻塞等待服务器确认应答状态,一般此状态的存在时间很短
●ESTABLISHED 表示客户端或服务器已经建立连接成功,随时可以进行通信
三次握手的作用不限于建立连接,除此之外三次握手还能检测发送能力与接收能力是否正常
当发送方发出SYN后,接收方都到发送方的SYN后,此时接收方就能够确定发送方的发送能力和接收方的接收能力是正常的,然后接收方回应ACK和SYN,当接收方收到ACK和SYN后,就知道了发送方以及接收方的发送能力和接收能力都是正常的,最后发送方回应ACK给接收方,此时接收方也确定了接收方以及发送方的接收能力和发送能力都是正常的。当然三次握手的过程中客户端与服务器还会“协商”配置一些重要的信息,这里就不展开了
🍉3.4.2四次挥手
既然有连接,那么自然会有断开连接的时候,客户端与服务器通过四次交互而断开连接的过程被称为“四次挥手”
其中断开连接的请求也被称为FIN,FIN也是属于控制位家族的一员,此外四次挥手可以是客户端发起断开连接请求,也可以是服务器首先发起断开连接请求,我们以客户端主动断开连接为例。
首先,客户端发出断开连接FIN请求,然后服务器收到请求后立即响应ACK(内核返回ACK),服务器调用socket.close()方法后才会给客户端发送FIN(应用层返回FIN),所以服务器这里ACK与FIN发送的时机并不是在一起的,不像三次握手(时机相同,能够合并)能够合并ACK与SYN。当然如果ACK与FIN相差的时间较小的话,还是可以合并发送的(延时应答机制),但是间隔时间长了不行,总体上还是不能合并的。
同理,四次挥手过程中客户端与服务器的状态也在发生改变,我们来了解一下这些状态
上图的主动方是客户端,被动方是服务器
●FIN_WAIT_1 主动方发送FIN后,进入等待被动方确认断开响应状态
●FIN_WAIT_2 收到被动方的ACK,进入等待主动方FIN状态
●CLOSE_WAIT 当被动方收到主动方发送的FIN请求后,被动方响应ACK并进入准备关闭状态
●LAST_ACK 被动方FIN发送后,进入等待最后一个ACK状态
●TIME_WAIT 收到被动方FIN,发送最后一次ACK并进入最后等待状态
🍒3.5滑动窗口
滑动窗口在保证传输的可靠性的前提下,尽量地提高传输效率
滑动窗口的本质就是发送多组数据,然后等多组数据的ACK
比如,客户端一次发送了4组数据,然后等ACK的到达,但并不是等4组数据的ACK全部到达后才继续发送数据,而是每收到一次ACK就发一组数据,比如如上图所示,客户端发出1-1000,1001-2000,2001-3000,3001-4000,四组数据后,客户端收到1001,就发送4001-5000,收到2001,就发送5001-6000以此类推
这就相当于一个大小为4的窗口滑动,原来数据的范围是1-4000,收到一个1001确认应答响应后,数据的范围就变成了1001-5000,相当于窗口向右滑动了一格。
但是上面的是正常的情况,也就是没有考虑后发先制和丢包的情况,下面我们来讨论一下这几种情况,
情况1:后发先制,当出现1001-2000比1-1000先到达这种情况时,由于确认应答机制,收到1001-2000后,服务器会认为2001之前的数据已经全部到达了,因此会返回确认应答2001,客户端收到2001后,窗口会向右移动两格,传输的数据范围为3001-7000,只要1-1000的数据没有丢,没有任何影响,所以后发先制的情况,不用处理,数据仍然能够正常传输
情况2:ACK丢了,这种情况其实与后发先制相似,比如1001丢了,当客户端收到2001时发送窗口就会右移两格,数据还是能够正常传输的,只要大部分的ACK没有丢,客户端可以通过下一次或者后面的确认应答序号来进行确认,所以ACK丢了不要紧,该情况也不用处理
情况3:数据丢了,这种情况不用想,肯定有问题,必须得处理的,比如1-3000的数据中,其中1001-2000的数据丢了,那服务器每收到一个数据,都会返回1001,表示让客户端重传1001-2000这个数据,当客户端收到若干个个相同的确认应答序号时,就明白了,数据丢了,就会对丢失的数据进行重传,直到服务器收到1001-2000的数据,就会返回最新的确认应答序号,这种机制也被称为高速重发控制
当遇到A传输数据过程中失败后,主机B一直返回上一层的序号A就明白自己在哪里传输数据的时候传输失败了,并且会在失败的位置重新输入
那这么说,只要窗口越大,那么传输速度不就快了吗?你的窗口大了,发送方的发送速度确实提高了,但是接收方能接受得过来吗?如果发送速度过快,接收方的接收缓冲区满了之后,传来的数据就放不下了,就会造成数据丢失,那数据丢了不也还需要重传嘛。所以并不是窗口大小越大,传输效率就越高。只有保证发送方与接收方发送与接收的速率最大并保持一致时,传输效率才是最高的,因此为了做到这一点,就有了流量控制的机制
🍒3.6流量控制
流量控制的关键就是得到处理方的处理速度,然后根据处理方的处理速度来动态调节发送的速度
而此处是通过接收方缓冲区的剩余容量来衡量接收方处理速度的,发送方发送数据后会放到一个缓冲区,然后接收方通过这个缓冲区来读取数据,这样的一个过程也可以理解为生产者消费者模型,即发送方是生产者,接收方缓冲区是“交易场所”,发送方是消费者,当生产者的生产速度与消费者的消费速度到达平衡时,传输的数据既快又稳定。
不妨把这个接收方的缓冲区看作成一个水池,那么发送方的工作就是注水,接收方的工作就是使用水池中的水,当水位比较低(剩余空间大)那就注水的时候就快一点,水位比较高(剩余空间小)那就注水的时候就慢一点。
当发送方的数据到达接收方的时候,接收方都会返回一个ACK,这个ACK除了确认能够确认应答,还能告知接收方缓冲区的空间还剩余多少,然后发送方根据接收方缓冲区剩余的容量来控制发送速度(窗口大小),当接送方得知接收方缓冲区空间满了的时候,就不会去发送 数据,而是会去发送一个探测窗口报文,获取接收方缓冲区剩余空间的大小。
而上面的获取接收方缓冲区剩余空间大小,是通过TCP报头中的窗口大小来进行获取,占据16位(即64k),实际上这里的可描述的窗口大小不止64k,因为TCP报头选项中还包含选项,选项里面有一个窗口扩大因子M,实际窗口大小是将窗口大小字段左移M位,也就是扩大2 M 2^M2倍
其中的窗口探测报文不包含任何数据,只是触发接收方响应ACK
🍒3.7拥堵控制
拥塞控制是滑动窗口的延伸,也就是限制滑动窗口中数据的发送速度,拥塞控制描述的是从接收方到发送方之间链路的拥堵情况。
发送方发送的多快不仅取决于接收方的处理能力,还取决于中间链路的处理能力。而发送方与接收方中间的结点的个数我们是不得而知的,因此拥塞控制采取“测试实验”的方式来逐渐调整发送的速度。
一开始的时候接收方会以较小的窗口进行发送,通过逐渐提高窗口的大小,当窗口达到一定的大小,就会出现丢包的情况,这就意味着链路就出现了“拥堵”,这时候就会减小窗口的大小,是快速的减小窗口大小,因为如果出现丢包减小窗口大小的速度不够大,可能会出现持续性的丢包,对网络通信的质量会造成很大的影响
阈值不是一直不变的,阈值会根据每次达到拥塞的时候进行适量提高达到接收缓冲区的满状态后,会重新在ssthresh中进行线性增长知道趋于平衡,每次丢包的时候就会将丢包的时候就会将拥塞窗口减少到一半
当TCP开始启动的时候,慢启动阈值等于窗口最大值;
在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1;
少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案。
TCP拥塞控制这样的过程,就好像 从开始谈恋爱到结婚到生子的过程→热恋期→吵架→和好→结婚→吵架→和好→生孩子
就想这一些列过程就会经过多次传输"吵架"到传输速率"和好"中逐渐走向平稳!
🍒3.8延时应答
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。
假设接收端缓冲区为1M。一次收到了500K的数据;如果立刻应答,返回的窗口就是500K;
但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了;
在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;
如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;
一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
那么所有的包都可以延迟应答么?肯定也不是;
数量限制:每隔N个包就应答一次;
时间限制:超过最大延迟时间就应答一次;
具体的数量和超时时间,依操作系统不同也有差异;一般N取2,超时时间取200ms
🍒3.9捎带应答
捎带应答是延迟应答的延伸,由于延时应答的存在,ACK并不是立即就发送响应的,当应用层代码需要响应时机与ACK响应时机重合时,就会将这两个数据合二为一,这就是捎带应答
捎带应答我们有时可以将操作系统内核的ACK与程序代码实现的命令进行同时打包一起发送,所以可以节省传输成本,增加传输效率
客户端与服务端通信的几种模型:
●一问一答: 客户端发出请求,服务器响应
●一问多答: 上传文件
●多问一答: 下载文件
●多问多答: 直播
🍒3.10粘包问题
粘包问题,就是应用层去取缓冲区的数据时,会出现分不清哪些数据是哪些TCP数据包里面的应用层数据,那也很可能就不知道从哪里到哪里是一个完整的应用层数据报·,就造成了粘包问题,其实不止TCP传输存在这种问题,所有面向字节流读文件都会有这种问题
那么要怎么解决呢?由于是找不到应用层的数据始末,所以去TCP上面做功夫是不可行的,问题出在哪里,就要在哪里解决,毕竟解铃还须系铃人。所以解决办法就是在应用层协议中加上包与包之间的边界,比如在应用层数据报结尾加上一个";"这个包分割条件,这样在读取的时候,就能区分出一个完整的应用层数据报了
🍒3.11 TCP异常
进行TCP协议传输时会出现以下几种情况:
进程终止:
在进程毫无准备的情况下,突然结束进程,偷袭它,其实它有闪,会"偷袭失败",我们知道TCP连接是通过socket来进行连接的,socket本质上是进程打开的一个文件,文件就存在与PCBZ中的文件描述符表之中,每次打开一个socket文件都会在文件描述表中添加一项,删除会减少一项,当你强制结束进程时,PCB没了,里面的文件描述符表也没有了,就相当于文件自动关闭了,这个过程和手动调用socket.close方法没有区别,依然会执行四次挥手过程
机器关机:
也是按照操作系统关闭PCB关闭线程操作流程,也是需要经过socket来进行四次挥手关闭,“偷袭失败”
停电/网络断开:
"偷袭成功"操作系统没有进行任何操作,没有反应过来就已经停止运行