TCP通信十大核心机制解析)
前言hello hello这里是洋不写bug~欢迎大家点赞关注收藏网络原理一和网络原理二中一共解析了TCP通信中的三个重要机制确认应答、超时重传、连接管理三次握手、四次挥手这篇博客就会解析其他的7个常用机制这篇博客中也会把TCP报文格式的剩余部分给解析完个人主页洋不写bug的博客所属专栏JavaEE学习铁汁们对于JavaEE的各种常用核心语法都可以在上面的前端专栏学习专栏正在持续更新中有问题可以写在评论区或者私信我哦~1滑动窗口有一类算法题解决方法也叫滑动窗口双指针两个指针都往一个方向移动围成的区域的变化就像是滑动窗口但滑动窗口这个概念最早来自计算机网络TCP的在前面的确认应答和超时重传机制下铁汁们想下发送方发送完成一个数据是否需要等待返回ACK后再发送下个数据如果等待返回ACK后才发送下个数据这样虽然能保证可靠性但是效率就比较低因为接收方接收信息回复ACKACK传回来都是需要时间的如果不等这个数据的ACK回来就直接发送下个数据这样就可能会发送的特别快把接受方的缓冲区冲爆滑动窗口机制就是用来解决这个问题的如下图批量发送4组数据等待一组的时间能够同时等待四组等待时间减少了效率就高了那铁汁们思考个问题上面这个机制是等4组的ACK都到达了再继续往后发4组还是收到一组的ACK就立刻继续往后发一组明显后者的效率是更高的第1组数据因为先发送第1组的ACK大概率是会最先到达的这时候直接发送第5组数据在第二种方案下第5组数据ACK到达的时间是一定早于第一种方案的同理第6组和第7组数据ACK到达的时间也是早于第一种方案这些组的ACK到达后又继续发下一组故第二种方案的效率是更高的如下图第1组收到了ACK就立刻发送第5组的数据可以看作有一个窗口窗口中一直有4组数据这个不是固定的随着收到ACK发送新的一组数组这个窗口是一直向右滑动的因此这个机制就称为“滑动窗口”滑动窗口提高了数据传输速度但是还是有等待时间的速度是不会超过UDP通信的那使用滑动窗口时出现丢包的情况应该如何处理呢如果主机A数据发送给主机B了主机B发送的ACK丢失是无所谓的因为滑动窗口ack确认序号的设定规则是返回的ack的确认序号表示的是该序号前的所有数据都已经接收到了如下图虽然确认序号为1001的ack丢失了但后面确认序号为2001的ack是正常返回了就说明前面1 - 1000数据都已经传输过来了返回确认序号为6001的ack就说明1 - 6000的数据主机B都已经收到了最后一个数据的ack丢包属于特殊情况后面会提到如果主机A发送的数据包丢失了如下图主机A发送完10001 - 2000数据后紧接着发送后面的数据主机B一边接收后面的数据一边一直向主机A发送确认序号为1001的ack也就是反复向A索要开头为1001的数据当A感知到B多次索要开头为1001的数据时就会重传1001 - 2000数据A重传后B后面返回的ack的确认序号就正常了当A收到3个同样的确认序号的ack时就认为B在索要该数据了不同系统中这个次数是不同的主要还是要搞懂这里的逻辑滑动窗口的重传机制就称为“快速重传”也就是多次接收到同一个确认序号的ack后触发重传前面还提到了超时重传超过一定时间没有收到ack后就进行重传操作当TCP传输的数据量较大时就会触发滑动窗口采取快速重传机制当TCP传输的数据量较小时就是按照确认应答和超时重传机制前面提到的最后一段数据ACK丢包的情况这时候就使用超时重传机制一段时间后没有收到ACK就把最后一段数据再重发一遍2流量控制TCP传输较大的数据时就用滑动窗口机制来提升效率前面是一个窗口中放四组数据窗口越大放的数据越多传输速度就越快但是窗口也不能无限大如果发送数据的速度过快把接收方的缓冲区塞满后面再发送的一些数据就会被直接丢弃就会影响可靠性那如何衡量窗口的大小在保证可靠性的同时让数据传输的速度尽量快一点这就要用到流量控制机制如下图发送方发送的数据会先到接收缓冲区接收方调用read之类的操作从接收缓冲区读取数据接收缓冲区就类似于阻塞队列如果里面没有数据接收方调用read就会阻塞接收方的处理能力就是接收方应用程序调用read的速度以及每次read读取多少数据这些指标是应用程序自己规定的不太好衡量因此就引入了另一个指标来衡量接收方的处理能力直接看接收缓冲区剩余空间的大小如下图可以把缓冲接收区看作是一个水桶水桶上有两个孔一个孔进水一个孔出水通过看桶中剩余空间的大小来判断进水速度和出水速度的差异如果桶中剩余空间大就说明出水接收方read的速度更大如果桶中剩余空间小就说明进水发送方发送数据的速度更大接收方返回ACK报文时就在TCP报头中把接收缓冲区剩余空间大小的数值放到ACK的报头中等发送方知道ACK后就能根据这个数值判断出接收方的处理速度这个数值就是存到了TCP报头中的16位窗口大小部分窗口大小为16位无符号整数转为十进制也就是65535TCP传输是字节流大小单位就是字节那是不是接收缓冲区最大就是64KB(网络协议中K就是1024)并不是这样TCP报文部分还有个选项方便后续的扩展可以在选项中设置窗口扩展因子通过位运算把二进制位进行左移窗口扩展因子位每左移一位就相当于乘以2这个是呈指数级增长的这个数值就会变得很大想复习下位运算的铁汁可以看博主的运算符解析博客链接放在下面了Java运算符详解流程如下图主机A先发送了一组数据主机B返回的ACK中显示窗口大小为3000这里A再连续发送三组数据主机B返回的ACK中显示窗口大小为0这时候主机A就不再主动发送数据了图中A发送1 - 4000数据时B是没有处理数据的当主机A收到显示窗口为0的ACK时就会停止发送数据那后面A如何知道B的缓冲区剩余空间是否为0判断能不能继续发送数据当主机B的缓冲区剩余空间不为0时就会主动向主机A发送窗口更新ACK只靠1的话这个窗口更新ACK是可能会出现丢包的一旦丢包那主机A就卡死了第2条机制就是用来兜底的主机A每隔一段时间就会向主机B发送窗口探测包不包含业务数据让主机B返回ACK3拥塞控制拥塞控制就是限制滑动窗口的发送速率设定滑动窗口的大小不能只看接收方处理数据的能力因为两个主机通信时是有很多中间节点路由器、交换机作为数据中转站的还要综合考虑这些中间节点处理数据的速度那就既不能让接收方的数据缓冲区溢出也不能让中间节点过载流量控制是看接收方应用的数据处理速率拥塞控制是看中间链路节点的承载能力发送窗口的大小取决于传输速度更慢的那个这个就类似于木桶效应中间节点的承载能力理论上是不好计算的拥塞控制的核心就是通过实验的方式来确定滑动窗口的大小先按照比较小的速率发送数据看下是否丢包如果丢包说明中间链路有节点顶不住了就减小窗口大小降低速度如果不丢包就说明中间链路的节点还能再加数据就增大窗口大小提升速度关于拥塞控制实验的具体流程如下图这个横坐标就是数据实验的轮次纵坐标的单位是“份”一份有多个字节这个具体看传输时的设定刚开始网络的畅通情况是未知的初始窗口是非常小的这个就称为“慢启动”慢启动之后如果数据不丢包窗口大小就会按照指数方式快速增长当指数增长到一定程度时达到阈值指数增长就会变成线性增长如果一直指数增长的话就很可能某次翻倍后一下超出上限很多直接把中间路由器冲爆了线性增长到一定程度网络承载能力就达到上限了出现丢包收到三个重复的ACK达到上限时有两种处理方法①Tahoe版本出现丢包后回到最初的慢启动窗口大小接下来重复指数增长/线性增长的过程阈值会减小②Reno版本出现丢包后重新计算阈值丢包的窗口大小/2从阈值开始作为新的拥塞窗口继续线性增长相比Tahoe版本省略了指数增长的过程现在Tachoe版本因为传输速率不稳定大起大落已经废弃了4延时应答TCP 数据传输是基于滑动窗口的可靠性由确认 重传保证重传机制主要包括超时重传和快速重传在滑动窗口中返回确认序号为2001就意味着2001序号前的数据都接收到了滑动窗口过程如下图每次接收方收到数据后立刻返回ack延时应答就是让接收方收到数据后不立刻返回ack先延时一段时间例如收到1 - 1000数据时不发送ack后面等收到1001 - 2000的数据时再返回确认序号为2001的ack就说明2001之前的数据都收到了确认序号为1001的ack也就不用发了少发送一次ack开销就降低了如下图有的铁汁会说每次都延时应答那发送2001这里不是也应该延时等到3001一起发送吗为什么2001这里发送了呢这是因为在大多数情况下延时应答规定最多攒 2 个报文收到 2 个有序段就立刻输出 ACK不会无限攒下去那延时应答并不会触发超时重传因为延时应答的时间是要远远小于超时重传的等待时间的RFC互联网规范文档 1122 规定延时应答的延迟不能超过 0.5 秒。RFC 6298 建议RTORetransmission Timeout超时重传时间 最小值通常为 1 秒。日常开发中延时应答通常是 40ms200ms 左右远小于 RTO5捎带应答网络通信中经常是一问一答的模型客户端发起request服务器返回response如下图服务器在收到客户端的请求后立刻返回ack接下来服务器需要经过一定的时间来计算响应再返回如果服务器计算响应的时间比较短的话就可以把返回ack和返回响应两步合并在一起也就是先不返回ack响应报文的ack值为132位确认序号和16位窗口大小中设置相应的值把两个报文合并为一个TCP报文减轻了中间节点的压力也节约了主机系统的开销捎带应答经常和和延时应答搭配起来使用延时应答会等待一段时间才发送ack这样就算服务器计算响应需要一段时间也能将返回ack和返回响应报文合并在一起延时应答的等待时间是有限的如果服务器处理响应的时间比等待时间长那仍然是合并不了两个报文的6面向字节流TCP是面向字节流的在读取/写入数据时操作是非常灵活的例如读取100个字节一次读10个字节10次完成一次读取20个字节5次完成一次读取50个字节2次完成…TCP字节流的特征收到多个TCP数据报的时候把所有的载荷混到一起放到接收缓冲区中这时候就会出现“粘包问题”如下图发送方给接收方传输一些数据接收方分用的时候会去掉报头把载荷内容放到接收缓冲区中接收方的应用程序read的时候就会有多种可能性a a a b b b c c caa ab bb cc caaa bbb cccaaab bbcc c粘包问题粘的是应用层的数据包包的边界比较模糊好像黏上了一样解决粘包问题需要从应用层入手合理的设计应用层协议让包之间的边界比较清晰这样才能确保读到一个完整的应用层数据包具体就有两种方案通过特殊的分隔符来区分包的边界在应用层数据包开头的地方通过固定长度约定整个应用层数据包的长度第1种方案选用分隔符时要确保包的数据部分不包含分隔符ASCII 0‑31 属于控制字符不可打印、不可见就有一部分专门设计用来做数据分隔如下图前面网络编程三博客中解析的TCP回显服务器的代码使用的分隔符就是\n客户端构建请求发送给服务器用的是writer.println(request)发送请求的时候就会在末尾加个/n服务器读取请求用的是request scanner.next()就是读到空白字符空格、换行、回车、制表符、分页符…就结束了第二种方案应用程序在read的时候就能根据长度确定每次读多少个字节读到的是完整的应用层数据包在文件操作中使用文件存储多个结构化数据也是会涉及到粘包问题的例如用文件来存储多个学生的信息就可以约定每个学生信息占一行使用\n作为结束标记作为分隔符对于UDP来说就不存在粘包问题UDP是面向数据包传输的TCP是面向字节流传输的UDP的应用层每次从接收缓冲区中读取到的都是一个完整的数据包10异常情况在进行网络通信的时候会出现下面这些异常情况进程崩溃主机关机正常流程主机掉电直接拔电源网线断开进程崩溃是正常的流程正常调用close干掉进程关闭对应的文件描述符就属于是进程崩溃只要是进程退出都会释放文件描述符触发FIN开始四次挥手TCP的连接还会保留一会等四次挥手完成再删除对于主机关机就会杀死所有的线程也会触发FIN开始四次挥手如果关机的速度比较慢那四次挥手就可能会执行完如果关机的速度比较快刚发FIN机器就关了对于关机比较快的这种情况关机方发完FIN就关机了对端可以正常的返回ack和FIN只是发送的FIN不会收到ack尝试重传几次FIN还是没有收到ack对端就直接放弃连接删除存储的连接信息主机掉电台式机直接拔电源就是直接啥都没了是来不及发送FIN的这时候分为两种情况掉电的一方是接收方对方是发送方对方继续发送数据但是收不到ack就会触发超时重传超时重传后还是收不到ack就向掉电方发送一个复位报文RST表示要断开当前连接RST发完后就断开TCP连接而且RST是单方面发送的不关心对方有没有收到因为发送RST时本身通信就已经有异常了掉电的一方是发送方那接收方的感觉就是对方突然不发送数据了接收方就会阻塞等待那接收方如何判断只是这会发送方没有数据发送还是说发送方已经挂了呢这就要靠“心跳包”接收方会周期性的和发送方交换“心跳包”接收方给发送方主动发送个无业务的数据的报文发送方返回个ack这个就称为“心跳包”也就是判断对方有没有挂掉如果对方有应答就认为对方是正常工作的如果心跳包也没有应答就认为对方已经挂掉了就可以单方面的释放连接了在服务器机房中也会通过心跳包来验证服务器有没有挂掉如下图客户端向服务器发送请求请求量可能非常大一台业务服务器处理不过来就需要多台业务服务器分工合作网关服务器就是把收到的请求分发给各个业务服务器例如收到了2000个请求有5个业务服务器那就每个服务器分配400个请求来处理如果有一个业务服务器挂掉了网关服务器需要及时的发现重新分配请求给剩下的服务器这样就能保证整体的服务是稳定可用的网关和业务服务器之间就是通过“心跳包”来感知存活状态的这个“心跳包”的发送频率很高进而快速做出响应维持服务的稳定性网线断开也是一瞬间的事情来不及发送FIN处理过程跟主机掉电是类似的这里就不再赘述了11TCP报文补充至此TCP报文中的大部分内容已经解析完成了标志位中的URG和PSH和16位紧急指针也没有解析标志位已经解析了四个ACK应答报文RST复位报文单方面放弃连接SYN同步报文FIN结束报文URGUrgent为1就表示紧急指针是有效的正常情况下tcp数据的传输是顺序传输的紧急指针就意味着后面有些数据要先传输插队16位紧急指针的值就表示从当前位置往后多少个字节位置的部分要进行插队PSHPush就是催促接收方尽快把缓冲区的数据交给应用程序URG和PSH都用的比较少这里铁汁们了解下即可结语在网络原理一二三三篇博客中就完成了TCP通信十种核心机制确认应答超时重传连接管理三次握手四次挥手滑动窗口快速重传流量控制拥塞控制延时应答捎带应答面向字节流粘包问题异常情况心跳包当然网络通信的机制是非常多的这里只是解析了工作中经常涉及到的10种有个经典的面试题问如何用UDP实现可靠传输其实还是在考察TCP要基于UDP实现可靠传输就需要在应用层自己写代码来实现可靠传输写代码的思路还是参考TCP的做法例如确认应答给数据包编号、超时重传、滑动窗口、流量\拥塞控制…这里只是让我们说一下是不会让当场去写代码实现的也解析完成了TCP和UDP的报文格式和通信特征TCP是有连接的可靠传输面向字节流大部分传输情况优先考虑TCPUDP是无连接的不可靠传输面向数据报经常用于机房内部的数据传输因为机房内部传输丢包概率很低而且要求数据传输效率高像竞技类网游既需要可靠性不是那么严格也需要传输效率就可以用KCP协议传输层不只是TCP和UDP协议以上就是今天的所有内容啦完结撒花