Linux:TCP保证可靠性的方案(1)

上一篇我们介绍了TCP的报头,但是很多可靠性的策略是在报头里体现不出来的!!比如说重传、流量控制......

一、超时重传机制

情况1:主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B;

情况2:主机A未收到B发来的确认应答, 也可能是因为ACK丢失了;

所以主机A发出去的时候,在没收到应答期间,并不知道这个数据究竟是丢了没被首收到,还是收到了应答丢了! 但是我总不能一直等着吧! 所以我们规定了在没收到回应期间,我们就约定了一个特定的时间间隔,如果没有应答的话,那么我的主机A就判定这个数据已经丢失了,然后就会进行重发!!

问题1:这个重传时间应该设置为多少比较好呢??

------>**最理想的情况下, 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返回".但是这个时间的长短, 随着网络环境的不同, 是有差异的.。**当网络比较好的时候,间隔时间不能太长,因为此时信息发得很快,如果出现少量丢包的话等待会影响整体的重传效率。而当网络比较差的时候,也不能设置得太短,这样光传就要花很多时间了,非常有可能出现大量超时补发的情况!!因此我们的重传时间应该是动态的,和网络状况强相关的!!当然平时我们用户并不关心这些,具体细节是由内核来做的!

问题2:如果是因为情况2(应答丢了),主机B收到很多重复数据怎么办???

------>所以这要求TCP协议需要能够识别出哪些包时重复的,然后把重复的包给丢掉。就可以很容易做到去重的效果. 这时候我们可以利用前面提到的序列号来判断!!

问题3:TCP需要保证任何环境下较高性能的通信,他是如何动态计算这个最大超时时间的??如果一直重传都没有得到应答该怎么办??

------>

(1)Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时 时间都是500ms的整数倍.

(2)如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.

(3)如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.

(4)累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接.

二、链接管理机制

2.1 TCP三次握手和四次挥手

TCP通信时基于链接的, 所以要经过三次握手建立连接, 四次挥手断开连接

服务端状态转化:

CLOSED -\> LISTEN\] 服务器端调用listen后进入LISTEN状态, 等待客户端连接; \[LISTEN -\> SYN_RCVD\] 一旦监听到发送SYN确认报文. \[SYN_RCVD -\> ESTABLISHED\] 服务端一旦收到客户端的确认报文, 就进入ESTABLISHED状态, 可以进行 读写数据了. \[ESTABLISHED -\> CLOSE_WAIT\] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器 返回确认报文段并进入CLOSE_WAIT; \[CLOSE_WAIT -\> LAST_ACK\] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当 服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个ACK到来(这个ACK是客户端确认收到了FIN) \[LAST_ACK -\> CLOSED\] 服务器收到了对FIN的ACK, 彻底关闭连接. **客户端状态转化:** \[CLOSED -\> SYN_SENT\] 客户端调用connect, 发送同步报文段; \[SYN_SENT -\> ESTABLISHED\] connect调用成功, 则进入ESTABLISHED状态, 开始读写数据; \[ESTABLISHED -\> FIN_WAIT_1\] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入 FIN_WAIT_1; \[FIN_WAIT_1 -\> FIN_WAIT_2\] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服 务器的结束报文段; \[FIN_WAIT_2 -\> TIME_WAIT\] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK; \[TIME_WAIT -\> CLOSED\] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会 进入CLOSED状态. **下图是TCP状态转换的一个汇总:** ![](https://i-blog.csdnimg.cn/direct/1fc9ad9897b14d9fb60609b37c0f42be.png) 较粗的虚线表示服务端的状态变化情况; ; 较粗的实线表示客户端的状态变化情况 CLOSED是一个假想的起始点, 不是真实状态; 关于 "半关闭" , 男女朋友分手例子 关于CLOSING状态. 可以网上查一下. 问题1:关于connect和accept的本质 ------\>**其实connect只是发动了三次握手(注意握手的时候发的都是裸报头,没有数据,只不过标记位有区别),但其实三次握手的细节是由双方OS自动完成的,此时他只不过是在哪里阻塞等待结果!!** **而链接成功和accpet也没有关系,他只是把建立好的连接直接拿过去使用(此时双方的一些准备工作都已经做好了) 然后就可以直接进行通信了!**! 问题2:为什么是三次握手和四次挥手?? ------\>**其实按道理来说应该是四次握手,三次是因为我们的服务端在发动链接请求的时候扫带应答了,所以中间被压缩成一层了(这是必然的,因为服务器一般都是会统一的,所以本质上是在技术上压缩成了三次)** **而之所以是四次挥手,是因为断开连接双方都需要协商(因为双方都有可能给对方发数据),当我确保没有数据给你发的时候,我告诉你我要断开连接了,然后你也得确认你没有数据给我发了,然后你也要向我申请断开连接(必须可靠地得知对方不想发消息的意愿)但其实如果我们客户端断开的时候恰好服务度也想断开,那么也有可能中间两层会被压缩成一层(少数情况,得有巧合性 因为客户端不想发的时候服务端可能还想发,全双工的特性所以客户端的收端并不会关闭)** **所以他们的本质其实就是基于双方一来一回的一种可靠性,双方都至少可靠地给对方发了一次消息,并收到了一次应答,那么双方就是可靠的(****验证了全双工通路正常****)** 问题3:既然服务器基本上都会统一客户端的连接,那为什么一次握手不行呢?? ------\>**因为如果客户端一次握手默认服务器必须同意,但由于服务器管理链接是需要消耗资源的,所以如果同时大量的客户端都来建立连接请求,那么其实服务器是需要基于自身内存去考虑的,否则很容易出现服务器内存被打满的情况**! 问题4:为什么不是两次握手呢?? ------\>**(1)情况1:如果是两次握手,那么必然要求服务端必须要先于客户端一步建立连接,可是这个要求的前提是客户端必须是好的,万一客户端突然崩掉了,对ACK不做处理,那么会使得服务端的连接会被长时间的维持。因为服务器是一对多的情况,所以如果一定要出现这种情况的话,应该让客户端去做资源的让步,而不是应该所有的异常情况负担都让服务端来兜底,这样他的压力会非常大!!** **(2)情况2:其实我们会发现,我们三次握手最怕的就是丢的就是第三个,因为第三个是没有回应的,那么如果是两次握手的话,我们的第二个一旦丢了(此时服务端已经默认建立好链接了),那么客户端没收到ACK会以为自己链接失败了,然后会再重新发送请求,又要建立新链接,这样又会把异常的负担全部都压在服务器上!!** **所以奇数次握手可以确保一般情况握手失败的话链接的成本是在client身上的!**! 问题5:为什么三次握手是最好的呢?? ------\>**因为三次握手如果是第三次丢了,大不了就重传一次,此时建立连接失败的代价是在客户端身上的(谁发送的请求让谁来承担失败后果),这样可以保证了服务端的稳定性,所以我们三次握手是性价比最高的!** 问题6:总结建立连接时做的事情 ------\> **(1)可靠地验证了全双工** **(2)奇数次握手确保了握手失败的成本是在client端** **(3)双方可以在报文里通过窗口大小告知对方自己的接收能力,为后面传输数据做准备(和滑动窗口有关)** **(4)双方通过报文里的序号协商起始序号(一般以较小的为主,这是为了避免不同报文的序号冲突)** 问题7:三次握手就绝对可靠了吗??? ------\>**虽然三次握手成本是最低的,并且把握手失败嫁接到了客户端,但是你要知道如果连接成功了那么双方是会消耗同样的资源的,所以如果同一时间有多个客户端恶意发起了链接,那么也是会造成服务端崩溃的!!** 情况1:被动成为肉鸡 比如说几台电脑中了病毒,然后在他们后台开启了一个不知名的程序,这个程序会在特定的时间点接收黑客电脑发送过来的任务,然后要求在同一时间一起向某个服务器发起请求,此时TCP解决不了这个问题!!所以大公司可能会需要有一些限流的策略或者是防火墙的策略,确保服务器不会因为攻击而挂掉,所以虽然三次握手已经很好的,但是这种情况也是难以避免的!! 情况2:自愿形成肉鸡 由一群人自愿形成一个比较大的社团,然后在同一时间用一个目标网址不断发送情况如何不断刷新(攻击外网资源),消耗对方的服务器资源,造成服务器宕机、崩溃! ### 2.2 测试三次握手 验证三次握手的状态,我们的tcp代码只需要保留链接的代码就可以了 IO部分可以不需要 ![](https://i-blog.csdnimg.cn/direct/620735d920f248b5b57e8b680be89137.png) 我们把accept也去掉,证明三次握手和他没有关系 我们先把listen的第二个参数设为1 用netstat -ntp可以看到他的状态 ![](https://i-blog.csdnimg.cn/direct/965a507df27b4ec08130716f3ee1d201.png) 如果我们建立了两个链接,客户端机器随机分配的端口号会被区分出来! ![](https://i-blog.csdnimg.cn/direct/d2e37d7e750c4a13ad482201f61c7184.png) 当我们建立第三个链接的时候,我们会发现客户端认为第三次连接成功了,但是服务端认为第三次连接没有建立成功,并且往后建立的链接都会失败 ![](https://i-blog.csdnimg.cn/direct/5f3247a62659462f9c2218e888188146.png) 问题1:为什么会这样呢?? ------\>**客户端状态正常, 但是服务器端出现了 SYN_RECV 状态, 而不是 ESTABLISHED 状态 这是因为, Linux内核协议栈为一个tcp连接管理使用两个队列:** **1. 半链接队列(用来保存处于SYN_SENT和SYN_RECV状态的请求)** **2. 全连接队列(accpetd队列)(用来保存处于established状态,但是应用层没有调用accept取走的请求) 而全连接队列的长度会受到 listen 第二个参数的影响.** **全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态了.** **这个队列的长度通过上述实验可知, 是 listen 的第二个参数 + 1.** ![](https://i-blog.csdnimg.cn/direct/060e842ef9c54dfe84a94f0b0f53f428.png) 问题2:既然队列都满了,那为啥客户端是连接成功的状态?? ------\>**因为TCP设计的时候,一般都会让服务端同意客户端的请求,也就是说我允许你前两次握手成功,但是你第三次ACK回来给我的时候我会去检测我的队列是否已经满了,如果已经满了我就会把你的ACK给丢掉!**! 问题3:关于SYN_RCVD ------\>**他表现的是一种半连接状态(全连接的前置状态),当然这是暂时的,半连接并不会被长期维护(他的队列长度跟listen的第二个参数也有关系,但是是有相关算法的,是内核区决定的),所以时间太长他也会被丢弃(就是如果因为全连接队列满了导致ACK一直被丢弃)** **所以无论是因为我们发的SYN+ACK对方没收到,还是因为他收到了但是ACK被我们丢弃,我们服务端都会评判这次链接是失败的!!所以不会放到长连接队列里供accept使用**! 问题4:关于SYN洪水 ------\>**因为进入长连接必须要先经历端链接,所以如果同一时间大量SYN涌入就会导致半连接被快速打满(因为全连接的数量有限),所以也会大量消耗服务端的资源!**! 问题5:你的网络为何时好时坏呢?? ------\>**因为在网络连接拥堵的时候,有时候可能你的连接恰好从半连接拿到了长连接队列里,但是也有可能因为长时间无法进入长连接队列而被服务端给丢弃!**! 问题6:全连接队列为什么不能太长呢?? ------\>**因为没有必要,如果我的上层很忙,需要很多资源,我明明没有很多时间来处理这些连接,那么你的这些大量链接还需要被长时间维护起来,此时因为太忙,所以这些资源其实是没有价值的!!还可能影响上层的处理速度!**! 问题7:那为什么不能没有全连接队列呢?? ------\>讲个故事,比如说海底捞因为客人太多,所以他会在门口摆一些桌子还有一些小零食,告诉那些客人我们这里暂时没有位置了,可不可以等一等,此时用户可以选择等,那么如果正好有一桌此时吃完了就离开,那么就可以立马有人可以补进来,不至于餐桌闲置(因为如果我满了的时候就告诉后面的客人我们满了,那么一旦有人吃完离开了就可能没有人立马进来补位,而桌子被闲置就会导致我们的钱赚的少了!!) 所以**如果我们没有全连接队列,你来我就处理你不来我就不处理,那万一我突然从很忙变得不忙的时候,我有能力处理很多链接凑巧又没有什么链接,那么就会造成资源的闲置,我无法将服务器的资源进行充分利用!所以我们有了listen提供的这个全连接队列,就是为了能够在服务端不忙的时候可以快速提供连接,让服务端去做新的服务!**! ### 2.3 TIME_WAIT **要验证断开连接的状态,代码里就不要有close 然后要把accept解开(因为如果你上层没有用accpet把链接拿过去用,那么也就不存在挥手的过程)** 现在做一个测试,首先启动server,然后启动client,然后用Ctrl-C使server终止,这时马上再运行server, 结果是: ![](https://i-blog.csdnimg.cn/direct/46bca3d66428459385e13106c991acf3.png) 这是因为,虽然**server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监 听同样的server端口**. 我们用netstat命令查看一下: ![](https://i-blog.csdnimg.cn/direct/504f9741717d44e99c2a20391786c17a.png) 此时我们会发现一个问题就是:主动断开连接的一方,在四次挥手完成之后会进入time_wait的状态,等待若干时长之后再自己自动释放![](https://i-blog.csdnimg.cn/direct/01c908db76bb4613bf308cf7ab63dcd9.png) 问题1:为什么会这样呢?? ------\>**TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime)****的时间后才能回到CLOSED状态.** **我们使用Ctrl-C终止了server, 所以server是主动关闭连接的一方, 在TIME_WAIT期间仍然不能再次监听同样的server端口; MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s;** **可以通过 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看msl的值;** ![](https://i-blog.csdnimg.cn/direct/2d88406e7fa0460986ae90693b6b32c2.png) **规定TIME_WAIT的时间请参考UNP 2.7节;** 问题2:TIME_WAIT导致TCP没有完成断开是server无法立刻监听合理吗?? ------\>其实很多情况下并不合理 **服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户端来请求 一般是HTTP1.0版本的短连接).** **这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务器端主动清理掉), 就会产生大量TIME_WAIT连接.** **由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占用一个通信五元组(源ip,端口, 协议). (TCP端口数量上限是65535)而且还未被系统回收,就会出现无法向服务端创建新的socket连接的情况,此时系统几乎停转,任何链接都不能建立:`address already in use : connect`异常** 问题2:怎么解决server因为断开后的TIME_WAIT状态而不能再次监听端口的问题呢?? ------\>**使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符**(就是如果你之前这个端口号因为TIME_WAIT不让我用了,那么现在请立刻让我使用!!) ![](https://i-blog.csdnimg.cn/direct/7f1e4e28face4c49a5d906a6771a3d84.png) 问题3:为什么断开的一般都是客户端,但是客户端一般不会出现这样的问题呢? ------\>**因为客户端使用的是随机端口,不受这个特点的影响,而服务器是必须绑定的同一个端口!** 问题4:为什么要等??为什么是TIME_WAIT的时间是2MSL? ------\>**MSL是TCP报文的最大生存时间, 因此TIME_WAIT持续存在2MSL的话,就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(消失其实就是为了丢掉他),否则服务器立刻重启, 可能会收到来自上一个进程的迟到的数据, 但是这种数据很可能是错误的。** **同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN. 这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发LAST_ACK);最大程度确保四次握手能够完成。** 问题5:如果历史数据消散得不彻底怎么办?? ------\>**其实因为我们在握手的时候会去协商起始序号,加上一个随机值,一方面是为了预防黑客去才我们的序号发送恶意报文,另一方面也是为了最大限度规避历史报文对我们的通信造成的影响!** 问题6:可以我们一般发送消息不也是ms级的吗,为什么MSL会这么长呢?? ------\>**因为ms级是正常报文,此时我们考虑的是最大传送时长,但是在网络异常的状态下可能出现拥堵,所以MSL表明的是报文的最大存活时长!!具体是多少不同OS实现不同(一般是60-120) 当然如果我们去配置文件修改的话也可以改!**! ### 2.4 CLOSE_WAIT ```cpp #pragma once #include #include "tcp_socket.hpp" typedef std::function Handler; class TcpServer { public: TcpServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port) { } bool Start(Handler handler) { // 1. 创建 socket; CHECK_RET(listen_sock_.Socket()); // 2. 绑定端口号 CHECK_RET(listen_sock_.Bind(ip_, port_)); // 3. 进行监听 CHECK_RET(listen_sock_.Listen(5)); // 4. 进入事件循环 for (;;) { // 5. 进行 accept TcpSocket new_sock; std::string ip; uint16_t port = 0; if (!listen_sock_.Accept(&new_sock, &ip, &port)) { continue; } printf("[client %s:%d] connect!\n", ip.c_str(), port); // 6. 进行循环读写 for (;;) { std::string req; // 7. 读取请求. 读取失败则结束循环 bool ret = new_sock.Recv(&req); if (!ret) { printf("[client %s:%d] disconnect!\n", ip.c_str(), port); // [注意!] 将此处的关闭 socket 去掉 // new_sock.Close(); break; } // 8. 计算响应 std::string resp; handler(req, &resp); // 9. 写回响应 new_sock.Send(resp); printf("[%s:%d] req: %s, resp: %s\n", ip.c_str(), port, req.c_str(), resp.c_str()); } } return true; } private: TcpSocket listen_sock_; std::string ip_; uint64_t port_; }; ``` 我们编译运行服务器. 启动客户端链接, 查看 TCP 状态, 客户端服务器都为 ESTABLELISHED 状态, 没有问题. 然后我们关闭客户端程序, 观察 TCP 状态 此时进入了CLOSE_WAIT状态,结合我们四次挥手的流程图,可以认为四次挥手没有成功!! **对于服务器上出现大量的 CLOSE_WAIT 状态, 原因就是服务器没有正确的关闭 socket, 导致四次挥手没有正确 完成. 这是一个 BUG. 只需要加上对应的 close 即可解决问题.** ## 三、流量控制 **超时重传其实是一种效率低的表现,所以并不能滥用,而流量控制就可以尽可能地避免由于接收方能力不足而造成的丢包问题!**! 接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应. 因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control); 问题1:流量控制具体是怎么做的呢?? ------\> **(1)接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段, 通过ACK端通知发送端;** **(2)窗口大小字段越大, 说明网络的吞吐量越高;** **(3)接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;** **发送端接受到这个窗口之后, 就会减慢自己的发送速度;** **(4)如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测(窗口探测不敢带数据,因为他不知道对方缓冲区刷新了没有)数据段, 使接收端把窗口大小告诉发送端(除此之外B主机如果刷新了也会直接给A主机发送窗口更新的通知,这是双重保障,为了提高容错性,当值其中一方网络信道有问题而不能给另一方发送消息的情况).** **(5) 互相问其实有一个潜台词,就是当窗口更新通知过去的时候,主机A可以顺便发数据捎带应答,而窗口探测也会有ACK回复,所以双方都会收到应答,但如果双方没都有收到应答,那么就是网络不互通,互相探测一段时间后确认双方无法通信的情况下,会直接将连接给关闭** ![](https://i-blog.csdnimg.cn/direct/dbcc5a8fb1494e1fb96e2be06d6ff83c.png) **流量控制是以可靠性为主(可以减少正常丢包的情况) 效率为辅(减少对正常报文的丢包,也就减少了重传,所以整体效率都会有提升) 所以我们发现可靠性和效率在有些情况下是并不冲突的,****TCP在设计的时候不仅仅考虑到了可靠性的问题,也考虑了很多效率问题!**! 其实我们平时在浏览器下载的东西,大部分都是TCP协议,浏览器的网页信息其实就是被下载到本地了,只不过下载的信息没有被你看到,并且没有下载到本地而是下载到内存中,然后把信息做解释呈现给你,当你关闭浏览器的时候网页信息也就在内存中释放了!! 问题2:第一次发送的时候怎么保证数据是合理的?? ---------\>**别忘了三次握手双方交换了报文,所以在建立连接的时候就已经协商了双方的接收能力! 所以其实第三次握手的时候就可以携带数据了!**! ## 四、滑动窗口 刚才我们讨论了确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答. 收到ACK后再发送下一个数据段.这样做有一个比较大的缺点, 就是性能较差. 尤其是数据往返的时间较长的时候.(串行效率低) ![](https://i-blog.csdnimg.cn/direct/6aa69782335c4ae28a8163e274332ec0.png) 既然这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时 间重叠在一起了). ![](https://i-blog.csdnimg.cn/direct/81362ba4b188498fa4d5bd1fadc98917.png) 因为有重传的策略,所以已经发送出的但是暂时还没有收到应答的报文,需要暂时被TCP管理起来!!因为已经发送出去,但是暂时没有收到应答,所以在发送方可能需要存在多个这个的数据 问题1:所以已经发送但是暂时没有收到应答的多个报文会被保存在哪里呢?? ------\>**虽然按道理来说是应该单独搞个空间来存,但是TCP其实也把这部分数据在发送的时候直接保留在发送缓冲区了,然后只要通过某种方式进行区域划分即可,所以引入了滑动窗口(本质上就是双指针)** ![](https://i-blog.csdnimg.cn/direct/07dbd372b60442f2866fcbd9325d36c8.png) 问题2:如何理解滑动窗口呢?? --------\>**(1)滑动窗口是发送缓冲区的一部分!!(2)滑动窗口的大小是取决于对方接受窗口!(3)区域划分其实就是通过指针/下标来进行区分的(避免了很多拷贝) (4)正是因为有了滑动窗口,我们才可以一次发送大量的TCP报文** ![](https://i-blog.csdnimg.cn/direct/9c3be6e01834488d845a22698cbdbfb2.png) 问题3:为什么不直接把整个数据块发过去而要进行分段呢??不是数据块越大发送效率越高吗? --------\>**这其实是跟硬件有关系,本身网卡就不能一下子发送太大块的数据块,所以只能一块块发** **那么如果出现了丢包, 如何进行重传? 这里分两种情况讨论.** **情况一: 数据包已经抵达, ACK被丢了.** ![](https://i-blog.csdnimg.cn/direct/3fa52bb4fd5e4cc1806f088eb4914c88.png) 这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认; 问题4:如果丢包了我要怎么理解滑动窗口呢?? ------\>**首先我们要理解确认序号的定义:确认序号之前的报文我们全部收到了,下次请从确认序号开始发。 所以其实TCP是允许少数ACK丢失的!这样保证了滑动窗口线性地连续向后更新的时候不会出现跳跃的情况!** ![](https://i-blog.csdnimg.cn/direct/f03baa4883db4621b393f7f14470276d.png) 问题5:指针向左移动?向右移动?移动的时候大小会变化么?会为0吗? ------\> **(1)不会向左移动!!** **(2)会向右移动!!** **情况1:右不变,左移动(当区域内报文收到应答的时候)** **情况2:左右都移动,范围扩大(发现对方接受能力很强)** **情况3:左右都移动,范围缩小(发现对方接受能力很弱)** **(3)大小是动态变化的(变大、变小、不变)** **(4)会为0,当对方接受缓冲区满的时候!!当然此时也会进行问询,如果问到了会刷新窗口** 问题6:流量控制是怎么实现的?? ------\>**通过滑动窗口实现的(基于确认序号的定义从而精准实现向右移动)!!千万不要觉得流量控制只是单纯减缓数据的发送速度,还要想到可能是因为服务端接收能力很强从而可以通过滑动窗口让传送速度更快,控制的本质是速度可以发生变化**! ![](https://i-blog.csdnimg.cn/direct/bbee86eea6014f8a8384513197b98b79.png) **其中start是确认序号,end是确认序号+min(对方缓冲区大小、有效数据、拥塞窗口)** **情况二: 数据包就直接丢了.** ![](https://i-blog.csdnimg.cn/direct/3b925ac36db3483eaf0940149288a55c.png) 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是1001"一样; 如果发送端主机连续三次收到了同样一个 "1001" 这样的应答, 就会将对应的数据 1001 - 2000 重新发送; 这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中; **这种机制被称为 "高速重发控制"(也叫 "快重传").** 问题8:有了快重传,为什么又要有超时重传呢?? ------\>**快重传是有条件的!!如果数据是在尾部发送的那么就没有办法使用!!所以快重传是用来提高效率的,而超时重传是用来兜底可靠性的!**! 问题9:滑动窗口会在发送缓冲区中越界吗? ![](https://i-blog.csdnimg.cn/direct/7665a4df6f544ea2b83ba4ec02595aaa.png) --------\>**在将TIME_WAIT的时候,我们知道保留这段时间的目的就是为了在断开连接前使得历史数据消散,其中有一种特殊情况就是比方说我链接之后发送了一些数据,其中一些数据被滞留在了网络当中,那么当我断开之后又没有及时消散,接着我又用原来的端口继续脸上了,此时就可能会出现新连接处理老数据的情况,如果此时恰好老数据的喜好和新数据的序号匹配上了,那么就会造成新数据读取有误的问题,所以我们为了尽量避免序号冲突的情况,其实在建立连接的三次握手时双方会在报文里的序号部分协商随机序号,然后以其中较小的那个序号来做起始序号,这样未来我们的缓冲区下标就需要加上这个起始序号,这样可以进一步确保老数据不会和新数据发生序号一致的问题**。 ![](https://i-blog.csdnimg.cn/direct/85f4db3899654eb8aa3768552ea4e452.png) ## ![](https://i-blog.csdnimg.cn/direct/9258a50f56ca4d1f85c48721f44a378e.jpeg)

相关推荐
Hum8le1 小时前
小科普《DNS服务器》
运维·服务器
故事与他6453 小时前
Thinkphp(TP)框架漏洞攻略
android·服务器·网络·中间件·tomcat
IYU_3 小时前
VulnHub-Web-Machine-N7通关攻略
服务器·安全·web安全·网络安全
郑州吴彦祖7724 小时前
【Java】UDP网络编程:无连接通信到Socket实战
java·网络·udp
kfepiza5 小时前
netplan是如何操控systemd-networkd的? 笔记250324
linux·网络·笔记·ubuntu
m0_490240676 小时前
qt实现一个简单http服务器和客户端
服务器·qt·http
九转苍翎6 小时前
Java EE(12)——初始网络
网络·java-ee
不是编程家7 小时前
Linux第九讲:动静态库
linux·运维·服务器
西柚小萌新7 小时前
【机器学习】--二分类
服务器·前端·机器学习
藏在歌词里7 小时前
ruoyi-vue部署4
linux·运维·服务器·vue.js·windows