文章目录
- 前言
- 一、拥塞控制
- 二、延时应答
- 三、捎带应答
- 四、面向字节流
- 五、异常处理
前言
随着时代的发展,越来越需要计算机之间互相通信,共享软件和数据,即以多个计算机协同⼯作来完成业务,就有了⽹络互连。
一、拥塞控制
虽然TCP有了滑动窗⼝这个⼤杀器, 能够⾼效可靠的发送⼤量的数据。 但是如果在刚开始阶段就发送⼤量的数据, 仍然可能引发问题。因为⽹络上有很多的计算机, 可能当前的⽹络状态就已经⽐较拥堵. 在不清楚当前⽹络状态下, 贸然发送⼤量的数据, 是很有可能引起雪上加霜的。
TCP引⼊ 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的⽹络拥堵状态, 再决定按照多⼤的速度传输数据。
总的原则是流量控制和拥塞控制,谁产生的窗口大小更小,谁说了算,这个拥塞控制具体是怎么把这个窗口大小试出来的?
- 慢启动。刚开始传输的数据速率比较小,采用的窗口大小也比较小,此时,网络的拥堵情况未知,贸然发送⼤量的数据, 是很有可能引起雪上加霜的。
- 如果上述传输的数据,没有出现丢包,说明网络还是畅通的,就要增大窗口大小,此时增大方式是按照指数来增长。
- 指数增长。并不会一直持续保持,可能会增长太快,一下子就导致网络拥堵,这里引入了一个“阈值”,当拥塞窗口达到阈值之后,此时,指数增长就成了线性增长。
- 线性增长。积累了一段时间之后,传输的速度可能太快,此时还是会引起丢包。一旦出现丢包,就会把拥塞窗口重新设置成较小的值,回到最初的慢启动过程(又要重新指数增长),并且这里也会根据刚才丢包时的窗口大小,重新设置指数增长到线性增长的阈值。
拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。
二、延时应答
同样是基于滑动窗口,要尽可能的再提高一点效率,结合滑动窗口以及流量控制,能够通过延时应答ack的方式,把反馈的窗口大小变大一些。
接收发收到数据之后,不会立即返回ack,而是稍微等待一下再返回ack,等待的时间就相当于给接收方的应用程序留出更多的时间来消费这里的数据。因此接收缓冲区的剩余空间就更大了。
⼀定要记得, 窗⼝越⼤, ⽹络吞吐量就越⼤, 传输效率就越⾼. 我们的⽬标是在保证⽹络不拥塞的情况下尽量提⾼传输效率。
那么所有的包都可以延迟应答么?
肯定也不是;
• 数量限制: 每隔N个包就应答⼀次;
• 时间限制: 超过最⼤延迟时间就应答⼀次;
具体的数量和超时时间, 依操作系统不同也有差异; ⼀般N取2, 超时时间取200ms。
三、捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客⼾端服务器在应⽤层也是 “⼀发⼀收” 的. 意味着客户端给服务器说了 “How are you”, 服务器也会给客户端回⼀个 “Fine, thank you”;那么这个时候ack就可以搭顺风车, 和服务器回应的 “Fine, thank you” ⼀起回给客户端,从而提高传输效率。
四、面向字节流
创建⼀个TCP的socket, 同时在内核中创建⼀个 发送缓冲区 和⼀个 接收缓冲区; • 调⽤write时, 数据会先写⼊发送缓冲区中;
• 如果发送的字节数太⻓, 会被拆分成多个TCP的数据包发出;
• 如果发送的字节数太短, 就会先在缓冲区⾥等待, 等到缓冲区⻓度差不多了,或者其他合适的时机发送出去;
• 接收数据的时候, 数据也是从⽹卡驱动程序到达内核的接收缓冲区;
•然后应⽤程序可以调⽤read从接收缓冲区拿数据;
• 另⼀⽅⾯, TCP的⼀个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这⼀个连接, 既可以读数据,也可以写数据,这个概念叫做 全双⼯。
由于缓冲区的存在, TCP程序的读和写不需要⼀⼀匹配, 例如:
• 写100个字节数据时, 可以调⽤⼀次write写100个字节, 也可以调⽤100次write, 每次写⼀个字节;
• 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以⼀次read 100个字节, 也可以⼀次read⼀个字节, 重复100次;
粘包问题
此处的“包”是指“TCP的载荷中的应用数据包”。
tcp传输的数据到了接收方之后,接收方要根据socket api来read出来,read出来的结果就是应用层数据包,由于整个read过程非常灵活,可能会使代码中无法区分出当前的数据从哪到哪是一个完整的应用数据包。
粘包问题不是TCP独有的问题,只要是面向字节流的都有同样的问题。
那么如何避免粘包问题呢?
归根结底就是⼀句话, 明确两个包之间的边界。
• 对于定⻓的包, 保证每次都按固定⼤⼩读取即可;例如上⾯的Request结构, 是固定⼤⼩的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
• 对于变⻓的包,可以在包头的位置, 约定⼀个包总⻓度的字段, 从⽽就知道了包的结束位置;
• 对于变⻓的包,还可以在包和包之间使⽤明确的分隔符(应⽤层协议, 是程序猿⾃⼰来定的, 只要保证分隔符不和正⽂冲突即可);
思考: 对于UDP协议来说, 是否也存在 “粘包问题” 呢? • 对于UDP, 如果还没有上层交付数据, UDP的报⽂⻓度仍然在.。同时,
UDP是⼀个⼀个把数据交付给应⽤层,就有很明确的数据边界。 • 站在应⽤层的站在应⽤层的⻆度, 使⽤UDP的时候,
要么收到完整的UDP报⽂, 要么不收。 不会出现"半个"的情况。
五、异常处理
考虑丢包更严重的情况,甚至是网络直接出现故障的情况,如何处理?
1)进程终⽌: 进程终止会释放⽂件描述符, 仍然可以发送FIN,和正常关闭没有什么区别。
2)机器重启: 和进程终止的情况相同。
3)机器掉电/网线断开: 接收端认为连接还在, ⼀旦接收端有写⼊操作, 接收端发现连接已经不在了, 就会进⾏reset。即使没有写⼊操作, TCP⾃⼰也内置了⼀个保活定时器, 会定期询问对方是否还在。如果对方不在, 也会把连接释放。
另外, 应⽤层的某些协议, 也有⼀些这样的检测机制. 例如HTTP⻓连接中, 也会定期检测对⽅的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接。
最后,码字不易,如果觉得对你有帮助的话请点个赞吧,关注我,一起学习,一起进步!