
目录1. 滑动窗口重点1.1 滑动窗口的概念1.2 丢包处理 - 快重传1.2.1 情况一数据包已经抵达, ACK被丢了。1.2.2 情况二数据包就直接丢了。1.3 理解滑动窗口1.3.1 滑动窗口概念理解1.3.2 滑动窗口和缓冲区的关系1.3.3 滑动窗口“丢包检测”和数据收发是解耦的1.4 滑动窗口相关问题2. 流量控制3. 拥塞控制3.1 拥塞窗口 慢启动机制3.2 拥塞判定4. 延迟应答TCP 延迟确认 (Delayed ACK) 机制5. 捎带应答6. 面向字节流7. 粘包问题8. TCP 异常情况 保活机制9. TCP小结10. 基于TCP应用层协议11. TCP/UDP对比12. 用UDP实现可靠传输(经典面试题)1. 滑动窗口重点1.1 滑动窗口的概念刚才我们讨论了确认应答策略对每一个发送的数据段都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点就是性能较差。尤其是数据往返的时间较长的时候。既然这样一发一收的方式性能较低那么我们一次发送多条数据就可以大的提高性能其实是将多个段的等待时间重叠在一起了。窗口大小指的是无需等待确认应答而可以继续发送数据的最大值. 上图的窗口大小就是4000个字节(四个段).发送前四个段的时候不需要等待任何ACK应答直接发送收到第一个ACK后滑动窗口向后移动继续发送第五个段的数据依次类推操作系统内核为了维护这个滑动窗口需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答只有确认应答过的数据才能从缓冲区删掉窗口越大则网络的吞吐率就越高。1.2 丢包处理 - 快重传那么如果出现了丢包如何进行重传这里分两种情况讨论。1.2.1 情况一数据包已经抵达, ACK被丢了。这种情况下部分ACK丢了并不要紧因为可以通过后续的ACK进行确认1.2.2 情况二数据包就直接丢了。当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK就像是在提醒发送端 “我想要的是1001” 一样;如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了被放到了接收端操作系统内核的接收缓冲区中;这种机制被称为 “高速重发控制”(也叫 “快重传”).1.3 理解滑动窗口1.3.1 滑动窗口概念理解滑动窗口的本质维护左端start和右端end指针滑动窗口的大小end - start1.3.2 滑动窗口和缓冲区的关系滑动窗口在每个缓冲区都存在。而一个TCP协议由两态主机完成客户端和服务端主机都有发送缓冲区和接收缓冲区所以滑动窗口在一个TCP协议中有4个序号在发送的轮次中数字是一次增大的也就意味着滑动窗口未来基本是向右滑动的宏观滑动窗口移动的本质start end下标增加滑动窗口和缓冲区的关系及理解滑动窗口左边是缓冲区中已发送已确认的部分。意味着这部分空间可以再次被利用了滑动窗口内部是已发送但未确认的部分。滑动窗口右边是未发送的部分对于发送缓冲区来讲。意味着这部分空间也可以使用1.3.3 滑动窗口“丢包检测”和数据收发是解耦的结合以上两个图查看滑动窗口的“丢包检测”和数据收发是解耦的此处的图1为服务接收端的滑动窗口即在检测到有丢包时服务端接收缓冲区仍然在继续接收客户端的数据。1.4 滑动窗口相关问题可以像左滑吗不能滑动窗口可以变大吗可以可以变小吗可以可以不变吗可以可以为零吗可以如果丢包了滑动窗口会不会跳过报文进行应答答不会这是由确认序号的定义决定的确认消息一定是连续的发送时必须连续发送滑动窗口不能跳跃。三种丢包情况最左侧丢失接收端发现数据丢失滑动窗口左侧不变。等待快重传发送端再次发送数据后填补。报文对应的应答丢失滑动窗口正常工作。中间丢失接收端发现处理逻辑和 “最左侧丢失” 一模一样因为在 TCP 眼里只要当前期待的包没到那就是 “当前最左侧” 的问题。它不关心后面还有没有坑。最右侧丢失现象收到了1001、2001、3001窗口滑到 4001。发送端发了4001但是丢了。发送端停止发送了因为窗口满了或者数据发完了。实际处理窗口左侧不动卡在4001。关键点服务端没有收到任何后续包没有 5001 撞过来。结果服务端不会发送 ACK因为没收到包没法触发回复。服务端不知道丢包了它只是在傻等。谁发现丢包是发送端发现的发送端发现4001发出去很久了还没收到 ACK触发了超时重传RTO。总结丢包位置接收端是否收到后续包接收端反应谁负责重传最左侧是收到了后面的立即回复 ACK 缺口窗口不动发送端快重传中间是收到了后面的同上逻辑上等同于最左侧发送端快重传最右侧否什么都没收到沉默窗口不动傻傻等待发送端超时重传2. 流量控制接收端处理数据的速度是有限的。如果发送端发的太快导致接收端的缓冲区被打满这个时候如果发送端继续发送就会造成丢包继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力来决定发送端的发送速度。这个机制就叫做流量控制(FlowControl)接收端将自己可以接收的缓冲区剩余空间大小放入 TCP 首部中的 “16位窗口大小” 字段通过接收端的ACK通知发送端窗口大小字段越大, 说明网络的吞吐量越高;接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;发送端收到这个窗口之后, 就会减慢自己的发送速度;如果接收端缓冲区满了就会将窗口置为0这时发送方不再发送数据但是需要定期发送一个窗口探测数据段使接收端把窗口大小告诉发送端。那么问题来了16位数字最大表示65535那么TCP窗口最大就是65535字节么?实际上TCP首部40字节选项中还包含了一个窗口扩大因子M实际窗口大小是 窗口字段的值左移 M 位。3. 拥塞控制虽然TCP有了滑动窗口这个大杀器能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据仍然可能引发问题.因为网络上有很多的计算机可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下贸然发送大量的数据是很有可能引起雪上加霜的。TCP引入”慢启动“机制先发少量的数据探探路摸清当前的网络拥堵状态再决定按照多大的速度传输数据。3.1 拥塞窗口 慢启动机制1.核心概念拥塞窗口 (cwnd)TCP 发送方维护一个状态变量叫做拥塞窗口 (Congestion Window, cwnd)。它代表了发送方根据网络拥堵情况自己估算出的最大发送量。2.发送策略取最小值每次发送数据时TCP 发送方必须同时考虑两个限制接收端的限制接收方通告的窗口 (rwnd)。接收缓冲区剩余空间大小网络的限制发送方自己的拥塞窗口 (cwnd)。实际发送窗口 Min [拥塞窗口 (cwnd), 接收端通告窗口 (rwnd) ]3.慢启动阶段 (Slow Start)虽然名字叫 “慢” 启动但这只是相对于 “一上来就发送无限多数据” 而言的。实际上它的增长速度非常快。初始状态发送开始时定义拥塞窗口大小为 1或初始值如 MSS×2。增长规则微观动作每收到一个 ACK 应答拥塞窗口加 1这里的 “1” 指 1 个 MSS即最大报文段长度简单来说它是 TCP 协议在一次传输中能够发送的 “纯数据” 的最大长度不包括 TCP 头部和 IP 头部。。宏观趋势因为每一个报文段被确认都会增加发送机会这导致在一个 RTT往返时间内拥塞窗口的大小会翻倍。结论因此慢启动阶段的拥塞窗口是指数级增长的1, 2, 4, 8, 16…。为什么 “加 1” 是指数增长第 1 个 RTTcwnd1发 1 个包。收到 ACKcwnd 变为 2。第 2 个 RTTcwnd2发 2 个包。这 2 个包分别被确认cwnd 会执行两次 “加 1”变为 4。第 3 个 RTTcwnd4发 4 个包。这 4 个包分别被确认cwnd 会执行四次 “加 1”变为 8。修正后的理解正因为每收到一个 ACK 都 “1”导致发送速率越快收到的 ACK 越多窗口开得越大从而形成了指数爆炸。说明RTT是Round-Trip Time往返时间的缩写。简单来说它是指数据从发送方发出去到发送方收到接收方确认ACK所经过的时间。4.慢启动阈值 (ssthresh)为了防止拥塞窗口无限指数增长导致网络瞬间崩溃引入一个状态变量叫做慢启动阈值 (Slow Start Threshold, ssthresh)。初始状态慢启动阈值 (ssthresh)通常被设置为一个较大的值如 65535 字节这意味着在初始阶段TCP 总是处于慢启动状态直到发生丢包或达到阈值。修正规则在每次超时重发的时候慢启动阈值会变成原来的一半同时拥塞窗口置回1规则当cwnd ssthresh时处于慢启动阶段窗口指数增长。当cwnd ssthresh时进入拥塞避免阶段窗口线性增长。当cwnd ssthresh时可以是任意一种通常进入拥塞避免。5.拥塞避免阶段 (Congestion Avoidance)当拥塞窗口超过阈值后为了试探网络的极限同时保持稳定不能再指数增长了。修正后的增长规则不再是每收到一个 ACK 就加 1。而是每经过一个 RTT即每一轮拥塞窗口只增加 1。结论此时拥塞窗口按照线性方式增长1, 2, 3, 4… 或者 8, 9, 10…增长速度显著变慢。总结对比表特性慢启动阶段 (Slow Start)拥塞避免阶段 (Congestion Avoidance)条件cwnd ssthreshcwnd ssthresh增长逻辑指数增长(Doubling)线性增长(Additive)具体实现每收到 1 个 ACKcwnd cwnd 1每经过 1 个 RTTcwnd cwnd 1直观感受1, 2, 4, 8, 16… (非常快)8, 9, 10, 11… (缓慢试探)3.2 拥塞判定1.拥塞的判定与应对TCP 发送方无法直接 “看到” 网络拥塞只能通过丢包这一结果来推断。根据丢包的严重程度TCP 采取不同的应对策略轻微拥塞检测到 3 个重复 ACK现象发送方收到了 3 个相同的确认报文。这意味着后续的数据包已经到达了接收方只是中间有一个包丢了乱序到达。判定网络还在传输数据只是发生了少量丢包。动作触发快速重传 (Fast Retransmit)立即重发丢失的包并进入快速恢复 (Fast Recovery)状态。拥塞窗口减半不重置为 1。严重拥塞发生超时重传 RTO现象发送方等待了很长时间RTO依然没有收到任何 ACK。判定网络可能已经堵塞数据包大量丢失或彻底断连。动作触发超时重传。此时认为网络严重拥塞将慢启动阈值减半并将拥塞窗口重置为 1重新开始慢启动。2.吞吐量的动态变化TCP 的通信过程就是一个不断探测网络极限的过程表现为吞吐量的锯齿状波动上升期探测阶段当 TCP 通信开始后发送方通过慢启动指数增长和拥塞避免线性增长机制不断增加拥塞窗口的大小。结果网络吞吐量逐渐上升直至达到当前网络链路的带宽极限。下降期拥塞发生当吞吐量超过网络承载能力时路由器开始丢包。TCP 检测到丢包信号无论是重复 ACK 还是超时。结果发送方立即采取 “乘法减小” 策略将拥塞窗口缩小通常减半或归零。表现吞吐量会因窗口的急剧收缩而立刻下降。拥塞控制归根结底是TCP协议想尽可能快的把数据传输给对方但是又要避免给网络造成太大压力的折中方案。4. 延迟应答如果接收数据的主机立刻返回ACK应答这时候返回的窗口可能比较小。假设接收端缓冲区为1M。一次收到了500K的数据如果立刻应答返回的窗口就是500K但实际上可能处理端处理的速度很快10ms之内就把500K数据从缓冲区消费掉了在这种情况下接收端处理还远没有达到自己的极限即使窗口再放大一些也能处理过来如果接收端稍微等一会再应答比如等待200ms再应答那么这个时候返回的窗口大小就是1M一定要记得, 窗口越大网络吞吐量就越大传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率那么所有的包都可以延迟应答么? 肯定也不是数量限制每隔N个包就应答一次时间限制超过最大延迟时间就应答一次具体的数量和超时时间依操作系统不同也有差异一般N取2超时时间取200msTCP 延迟确认 (Delayed ACK) 机制核心目的为了提高网络吞吐量接收方希望通告给发送方的窗口rwnd尽可能大。但如果应用程序处理数据很快接收方如果能稍微 “等一等” 再回复 ACK就可以把缓冲区腾出的空间合并到窗口通告中从而让发送方发送更多数据。1.为什么要 “延迟” 应答权衡策略场景假设接收端缓冲区总大小1M。收到 500K 数据此时剩余空间 500K。应用程序处理极快10ms 内就把这 500K 数据取走了缓冲区又空了剩余空间变回 1M。立刻应答的弊端如果收到数据立刻回复 ACK窗口大小是500K。发送方收到后认为接收方只剩一半空间可能会降低发送速率。延迟应答的优势如果接收方等待一小会儿例如 40ms再回复 ACK。此时应用程序已经把数据取走缓冲区空了窗口大小可以通告为1M。结果发送方获得了更大的发送窗口吞吐量更高传输效率更高。2.所有的包都可以延迟应答吗风险与限制不可以。如果接收方无限期等待会导致以下问题死锁发送方可能因为没收到 ACK 而停止发送后续数据因为窗口限制。超时重传发送方等待时间过长RTO误以为包丢了触发重传浪费带宽。因此TCP 协议规定了严格的双重限制数量限制每隔 N 个包通常N 2。意思是最多积累 2 个报文段的确认。如果收到了第 2 个包必须立刻回复 ACK不能再等了。时间限制最大延迟时间【重要修正】这个时间通常不是 200ms而是200ms 以下如 Linux 内核通常默认是40ms或100ms。一旦计时器超时无论是否凑够数量必须立刻回复 ACK。3.延迟确认的另一个重要好处除了让窗口变大延迟确认还有一个经典用途捎带确认 (Piggybacking)。如果接收方比如客户端在等待 ACK 的期间正好也要向发送方比如服务端发送数据。那么接收方可以直接把 ACK 信息 “贴” 在自己要发的数据报文头上一起发回去。结果节省了一个纯 ACK 报文的网络带宽。5. 捎带应答在延迟应答的基础上我们发现很多情况下客户端服务器在应用层也是 “一发一收” 的。意味着客户端给服务器说了 “How are you”服务器也会给客户端回一个 “Fine, thank you”那么这个时候ACK就可以搭顺风车和服务器回应的 “Fine, thank you” 一起回给客户端6. 面向字节流创建一个TCP的socket同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区调用write时数据会先写入发送缓冲区中如果发送的字节数太长会被拆分成多个TCP的数据包发出如果发送的字节数太短就会先在缓冲区里等待等到缓冲区长度差不多了或者其他合适的时机发送出去接收数据的时候数据也是从网卡驱动程序到达内核的接收缓冲区然后应用程序可以调用read从接收缓冲区拿数据另一方面TCP的一个连接既有发送缓冲区也有接收缓冲区那么对于这一个连接既可以读数据也可以写数据。这个概念叫做 全双工。由于缓冲区的存在, TCP程序的读和写不需要一 匹配, 例如:写100个字节数据时可以调用一次write写100个字节也可以调用100次write每次写一个字节读100个字节数据时也完全不需要考虑写的时候是怎么写的既可以一次read 100个字节也可以一次read一个字节重复100次7. 粘包问题粘包问题是指 TCP 作为流式协议将连续发送的多个报文合并传输导致接收方无法识别数据的边界即不知道数据是哪一次发送的也不知道开头和结尾其解决方法是在应用层制定通信协议通过规定特定的边界规则如长度前缀或分隔符来区分消息的开头和结尾从而保证读取报文的完整性。关于粘包问题首先要明确粘包问题中的 “包” 是指的应用层的数据包。在TCP的协议头中没有如同UDP一样的 “报文长度” 这样的字段但是有一个序号这样的字段。站在传输层的角度TCP是一个一个报文过来的。按照序号排好序放在缓冲区中。站在应用层的角度看到的只是一串连续的字节数据。那么应用程序看到了这么一连串的字节数据就不知道从哪个部分开始到哪个部分是一个完整的应用层数据包。那么如何避免粘包问题呢归根结底就是一句话明确两个包之间的边界。对于定长的包保证每次都按固定大小读取即可例如上面的Request结构是固定大小的那么就从缓冲区从头开始按sizeof(Request)依次读取即可对于变长的包可以在包头的位置约定一个包总长度的字段从而就知道了包的结束位置对于变长的包还可以在包和包之间使用明确的分隔符(应用层协议是程序猿自己来定的只要保证分隔符不和正文冲突即可)思考对于UDP协议来说是否也存在 “粘包问题” 呢?对于UDP如果还没有上层交付数据UDP的报文长度仍然在。同时UDP是一个一个把数据交付给应用层。就有很明确的数据边界。站在应用层的站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收. 不会出现半个的情况.8. TCP 异常情况 保活机制在实际开发中TCP 连接挂掉通常有三种典型情况我们来看看它们的区别1. 进程终止或崩溃发生了什么程序突然挂了或者被kill了。TCP 行为操作系统内核很负责它会帮死掉的进程把连接关了发送 FIN 包。结果对端看来这就是一次正常的关闭相当于你说 “再见” 然后挂断read会返回 0。2. 机器重启发生了什么服务器或电脑重启了。TCP 行为重启过程中如果来得及OS 会发 FIN。重启后这是重点机器醒来后完全忘记了之前的连接。结果如果此时对端发数据过来本机发现 “我不认识这个连接啊”就会回复一个RSTReset强制断开连接。3. 机器掉电 / 网线断开最坑的情况发生了什么物理断连或者突然断电没有任何数据包发出。TCP 行为对端完全不知情以为连接还好好的。如何发现主动写数据如果对端尝试发数据发现收不到 ACK重传几次失败后就会报错断开。不写数据如果双方都不说话TCP 有个保活定时器Keep-Alive默认过 2 小时会发个探测包。如果对方没反应就判定连接断了。关于 “保活” 的补充既然 TCP 自带保活为什么 QQ、微信、HTTP 长连接还要自己搞心跳“心跳” 就是应用程序自己发的 “我还活着” 的信号。TCP Keep-Alive底层太慢默认 2 小时才检测一次对于即时通讯来说太慢了。系统级配置麻烦通常改不了。应用层心跳业务层灵活比如 QQ 每 30 秒发个空包一旦断网马上就能发现并提示 “正在重连”。穿透性有些防火墙会杀掉长时间空闲的连接应用层心跳可以保持连接活跃。9. TCP小结为什么TCP这么复杂? 因为要保证可靠性, 同时又尽可能的提高性能.可靠性:校验和序列号(按序到达)确认应答超时重发连接管理流量控制拥塞控制提高性能:滑动窗口快速重传延迟应答捎带应答其他:定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)10. 基于TCP应用层协议HTTPHTTPSSSHTelnetFTPSMTP当然, 也包括你自己写TCP程序时自定义的应用层协议;11. TCP/UDP对比我们说了TCP是可靠连接, 那么是不是TCP一定就优于UDP呢? TCP和UDP之间的优点和缺点, 不能简单,绝对的进行比较TCP用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;UDP用于对高速传输和实时性要求较高的通信领域, 例如, 早期的QQ, 视频传输等. 另外UDP可以用于广播;归根结底, TCP和UDP都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定.12. 用UDP实现可靠传输(经典面试题)参考TCP的可靠性机制, 在应用层实现类似的逻辑;例如:引入序列号, 保证数据顺序;引入确认应答, 确保对端收到了数据;引入超时重传, 如果隔一段时间没有应答, 就重发数据;… 下面了解一下即可。doff (Data Offset):这实际上是TCP头部长度以32位字为单位而不是一个标志。它指示了TCP头部有多少32位字4字节。由于TCP头部可能包含可选项因此这个字段用于告诉接收方TCP头部有多长。通常没有可选项的TCP头部长度是20字节即doff为5因为5*420字节。res1:这是保留位通常被设置为0。它们用于未来的协议扩展。cwr (Congestion Window Reduced):当发送方收到一个带有ECEExplicit Congestion Notification显式拥塞通知标志的ACK时它可能会设置CWR标志来响应。这通常用于ECNExplicit CongestionNotification显式拥塞通知机制一种改进TCP拥塞控制的机制。ece (ECN-Echo):当TCP的接收方检测到网络拥塞时它可能会发送一个带有ECE标志的ACK给发送方。ECE标志告诉发送方接收方已经检测到拥塞并且可能希望发送方减少其发送速率。…过云雨-CSDN博客主页