【Linux网络】网络基础:传输层TCP协议(二)

📝个人主页🌹:Eternity._

⏩收录专栏⏪:Linux " 登神长阶 "

🌹🌹期待您的关注 🌹🌹


❀ 传输层UDP/TCP协议

前言:在当今这个信息爆炸、网络互联的时代,TCP(Transmission Control Protocol,传输控制协议)作为互联网通信的基石之一,扮演着举足轻重的角色。无论是我们日常浏览网页、观看在线视频,还是企业进行大规模数据传输、云计算服务,TCP协议都默默地在幕后工作,确保数据的可靠、有序和高效传输。

TCP协议自1974年由Vint Cerf和Bob Kahn提出以来,经历了无数次的迭代与优化,逐渐成为互联网中最广泛使用的传输层协议之一。它不仅仅是一种技术规范,更是一种智慧的结晶,体现了人类对复杂网络通信需求的深刻理解和精妙设计。

然而,TCP协议机制的复杂性和多样性,常常让初学者感到困惑和迷茫。它涵盖了从三次握手建立连接、滑动窗口流量控制、拥塞控制算法,到数据包的拆分与重组、错误检测与重传等多个方面,每一个细节都蕴含着深刻的原理和实践经验。

让我们一同踏上探索TCP协议机制的旅程,共同领略这一伟大发明的魅力与智慧。

确认应答机制


我们上一篇用寄快递的抽象例子简单介绍了一下确认应答机制:

TCP会给我们发送的每个数据进行编号,也就是序列号

每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据,下一次你从哪里开始发。

超时重传机制


  • 当我们主机A发送数据给B之后,可能因为网络拥堵等原因,数据无法到达主机B
  • 如果主机A在一个特定时间间隔内没有收到B发来的确认应答,就会进行重发

当然了除了上面这种情况外,主机A未收到B发来的确认应答,还有可能是发送回来的ACK丢了

因此主机B会收到很多重复数据,那么TCP协议需要能够识别出那些包是重复的包,并且把重复的丢弃掉,这时候我们可以利用前面提到的序列号达到去重的效果。

超时时间的设置:

  • 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返回"
  • 时间会随着网络环境的不同,会有所不同
  • 如果超时时间设的太长,会影响整体的重传效率
  • 如果超时时间设的太短,有可能会频繁发送重复的包

TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间

  • Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍
  • 如果重发一次之后,仍然得不到应答,等待 2*500ms 后再进行重传
  • 如果仍然得不到应答, 等待 4*500ms 进行重传,依次类推,以指数形式递增
  • 累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接

注意:

  • 发送方一旦把数据发出去,一段时间内已经发送的数据是会被暂时保存起来的,然后根据具体情况决定它的去留
  • 暂时保存的数据在发送缓冲区的滑动窗口中,收到应答的时候,通过窗口滑动,删除指定的报文

连接管理机制


在正常情况下, TCP要经过三次握手建立连接,四次挥手断开连接

为什么要进行三次握手:

  • 如果只进行一次或者两次握手,此时有客户端恶意挂靠链接,就会造成SYN洪水问题
    1. 最小成本验证TCP全双工
    1. 基数次握手,客户端优先将链接建立好,服务器才建立,此时客户端要承担对等的成本来建立连接,有效防止了客户端恶意挂靠链接
    1. 新的角度看待三次握手:4次握手 + 捎带应答

当我们在进行断开连接时,是需要经历许多状态的



连接断开后,会维持一段时间的TIME_WAIT状态,在此期间, 不能重新在同样的端口启动服务,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监 听同样的server端口

理解 TIME_WAIT 状态


  • 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的值
    网络中可能还会存在尚未到达的报文,TIME_WAIT存在的原因就是让历史报文从网络中消散。

为什么是TIME_WAIT的时间是2MSL:

  • MSL是TCP报文的最大生存时间,因此TIME_WAIT持续存在2MSL的话就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能会收到来自上一个进程的迟到的数据,但是这种数据很可能是错误的)
  • 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失,那么服务器会再重发一个FIN。这时虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK);

有时我们在终止一个连接,马上用同一个端口号再次连接时,就会出现问题,这就是由于TIME_WAIT状态,引起的bind失败。端口号和TIME_WAIT占用的链接重复了

  • 使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符

滑动窗口


服务器对每一个客户端发送的数据段,都要一一给予ACK确认应答,收到ACK后再发送下一个数据段。这样做效率非常地下,尤其是数据往返的时间较长的时候,因此TCP肯定有一次性发送多条数据的方法(多个段的等待时间重叠)

  • 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,上图的窗口大小就是4000个字节(四个段)
  • 发送前四个段的时候,不需要等待任何ACK, 直接发送
  • 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据,依次类推
  • 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区 来记录当前还有哪些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉
  • 窗口越大,则网络的吞吐率就越高

当我们在发送数据时,接收方在缓冲区在写入数据时,上层没有将数据取走,随着时间的推移,窗口大小会慢慢变小,左侧慢慢在滑动,右侧一直不滑动。既然可以变小,当然可以变为0,也可以变大。

滑动窗口的发送缓冲区类似于环形结构,不用考虑越界问题

流量控制


接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control)

  • 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段,通过ACK端通知发送端
  • 窗口大小字段越大,说明网络的吞吐量越高
  • 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端
  • 发送端接受到这个窗口之后,就会减慢自己的发送速度;
  • 如果接收端缓冲区满了,就会将窗口置为0,这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端

在TCP三次握手时, 收发双方就会交换各自的接受缓冲区,知道对方接受能力

拥塞控制


TCP虽然有滑动窗口,能够高效可靠的发送大量的数据,但是如果在网络状态比较拥堵时,开始阶段就发送大量的数据,仍然可能引发问题。

在不清楚当前网络状态下,贸然发送大量的数据,可能损失惨重,发生大面积丢包,发送方可能会判定网络出现问题,让大量数据进行重传,数据大量堆积进而造成网络瘫痪,TCP引入 慢启动 机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据 这就是 TCP 的拥塞控制

  • 此处引入一个概念程为拥塞窗口
  • 发送开始的时候,定义拥塞窗口大小为1
  • 每次收到一个ACK应答,拥塞窗口加1
  • 在每次发送数据包时,网络良好则考虑对方接受能力,如对方接受能力强则考虑网络问题
  • 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口

拥塞窗口增长速度, 是指数级别的( 2 n 2^n 2n)。"慢启动" 只是指初使时慢, 但是增长速度非常快

  • 为了不增长的那么快,因此不能使拥塞窗口单纯的加倍
  • 此处引入一个叫做慢启动的阈值
  • 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长
  • 当TCP开始启动的时候,慢启动阈值等于窗口最大值
  • 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1
  • 少量的丢包,我们仅仅是触发超时重传,大量的丢包,我们就认为网络拥塞
  • 当TCP通信开始后,网络吞吐量会逐渐上升,随着网络发生拥堵,吞吐量会立刻下降
  • 拥塞控制归根结底是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力

延迟应答


如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小:

  • 假设接收端缓冲区为1M。一次收到了500K的数据,如果立刻应答,返回的窗口就是500K
  • 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了
  • 接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来
  • 如果接收端稍微等一会再应答,等待200ms再应答,那么这个时候返回的窗口大小就是1M

窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率

延迟应答也许满足相应条件:

  • 数量限制:每隔N个包就应答一次
  • 时间限制:超过最大延迟时间就应答一次
  • 具体的数量和超时时间,依操作系统不同也有差异,一般N取2,超时时间取200ms,如果超过可能误判重传

捎带应答


很多情况下,客户端服务器在应用层也是 "一发一收" 的。意味着客户端给服务器说了 "How are you",服务器也会给客户端回一个 "Fine, thank you",那么这个时候ACK就可以搭顺风车,和服务器回应的 "Fine, thank you" 一起回给客户端。我们在三次握手中,就应用到了捎带应答

粘包问题


我们在做包子的时候,当它刚蒸好出锅的时候,我们用手去拿的时候,一下子可能会拿起来很多个,它们的边界是连在一起的,所以我们在包子刚出锅的时候,是先要把它们一个一个分开的。

  • 粘包问题中的 "包" ,是指的应用层的数据包
  • 在TCP的协议头中,没有如同UDP一样的 "报文长度" 这样的字段,但是有一个序号这样的字段
  • 站在传输层的角度,TCP是一个一个报文过来的,按照序号排好序放在缓冲区中
  • 站在应用层的角度,看到的只是一串连续的字节数据
  • 那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包

如何避免粘包问题,首要的就是明确两个包之间的边界:

  • 对于定长的包,保证每次都按固定大小读取即可,例如上面的Request结构,是固定大小的,那么就从缓冲区从头开始按sizeof(Request)依次读取即可
  • 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置
  • 对于变长的包,还可以在包和包之间使用明确的分隔符

对于UDP协议来说, 不会存在 "粘包问题"

总结


TCP异常情况:

  • 进程终止: 进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭没有什么区别
  • 机器重启: 和进程终止的情况相同
  • 机器掉电/网线断开: 接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset。期询问对方是否还在即使没有写入操作,如果对方不在,TCP自己也内置了一个保活定时器,也会把连接释放

TCP之所以复杂,是因为要保证可靠性, 同时又尽可能的提高性能

可靠性:

  • 校验和
  • 序列号(按序到达)
  • 确认应答
  • 超时重发
  • 连接管理
  • 流量控制
  • 拥塞控制

提高性能:

  • 滑动窗口
  • 快速重传
  • 延迟应答
  • 捎带应答

其他:

  • 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)

在探索TCP(传输控制协议)这一互联网通信基石的征途中,我们不仅揭开了其复杂而精细机制的神秘面纱,更深刻理解了为何TCP能够成为现代网络通信中不可或缺的一部分。TCP以其可靠的数据传输、流量控制、拥塞避免以及错误检测与恢复等特性,构建了一个既稳健又高效的数据传输框架,确保了数据从源头到目的地的无缝流动,即便是在面对复杂多变的网络环境时亦能游刃有余。

通过本文的梳理, 我们从TCP的三次握手建立连接,到数据传输过程中的序列号与确认应答机制,再到滑动窗口实现的流量控制,以及慢启动、拥塞避免等策略,一步步揭示了TCP协议背后的智慧与精妙。 这些机制不仅仅是技术上的创新,更是人类智慧在解决复杂通信问题上的集中体现,它们共同构成了TCP强大的错误恢复能力和网络适应能力。

让我们带着这份启示,继续在技术的海洋中遨游,为构建更加智能、高效、安全的网络世界贡献自己的力量。

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

相关推荐
Shine.Zhang7 分钟前
【树莓派4B】MindSpore lite 部署demo
linux·mindspore
QQ同步助手14 分钟前
服务器虚拟化:进阶应用与管理
运维·服务器
运维小文30 分钟前
elasticsearch设置密码访问
linux·运维·elasticsearch·全文检索·索引
NiNg_1_23435 分钟前
Linux中vi和vim的区别详解
linux·运维·vim
三天不学习38 分钟前
Linux中 vim 常用命令大全详细讲解
linux·运维·vim
千千道1 小时前
Qt 实现 UDP 广播的详细教程
开发语言·qt·网络协议·udp
Anna_Tong1 小时前
ARMS,让企业应用性能问题无处藏身
运维·服务器·云原生·性能优化
9毫米的幻想2 小时前
【Linux系统】—— 权限的概念
android·linux·服务器·c语言·c++·学习
apgk12 小时前
docker 容器相互访问
运维·docker·容器
changliangwl2 小时前
certbot 服务器证书配置
运维·服务器