计算机网络是指互联的计算机系统之间通过通信设备和通信线路进行数据交换的系统。计算机网络可以分为局域网、城域网、广域网和因特网等不同的类型。计算机网络使用各种协议来实现不同的功能和服务,例如 TCP/IP 协议用于互联网通信,HTTP 协议用于网页浏览,SMTP 协议用于电子邮件传输等等

网络协议是计算机之间进行数据交换的一类规则协议、标准集合,不同计算机之间的通信必须建立在相同的标准上,如:HTTP协议、TCP协议等等,在这些协议中TCP/IP协议影响力最大,是必须学习的协议,本模块会列出常见的协议并进一步分析,加深印象和理解

扫码关注公众号,查看更多优质内容

网络层次划分

为什么要进行网络层次划分?互联网的通信都必须建立在同一标准上才能够正常进行,划分层次可以进一步促进不同层次标准化,使得不同的供应商和组织可以遵循相同的标准进行设计和实现;进行模块划分、简化整体设计更有利于不同标准的实现、维护和扩展,提高网络的灵活性

网络协议层次划分的两个经典代表:OSI模型TCP/IP模型

OSI模型

OSI七层模型是国际标准化组织(ISO)在20世纪70年代提出的一个计算机网络通信模型。当时,由于不同的厂商和组织都在开发自己的网络协议,这导致了网络之间的互操作性和标准化问题。为此,ISO组织提出了OSI七层模型,以便解决这些问题。总之,<u>OSI七层模型推动了网络协议的标准化和互操作性,为网络设计和实现提供了重要的参考模型,对于推动计算机网络的发展和应用具有重要的作用</u>

以上是OSI七层模型,层层向上每一层为上一层提供服务

  • 应用层(Application Layer):负责提供各种应用程序所需的服务,如文件传输、电子邮件、网页浏览等
  • 表示层(Presentation Layer):负责数据格式的转换,提供数据加密和压缩等功能
  • 会话层(Session Layer):负责建立和管理会话连接,提供数据交换和同步功能
  • 传输层(Transport Layer):负责建立端到端的可靠传输连接,提供数据流的分段和重组功能,包括协议TCP和UDP等
  • 网络层(Network Layer):负责在数据链路层上建立逻辑连接,选择最佳传输路径,进行数据包的传输和路由选择
  • 数据链路层(Data Link Layer):负责在物理层上建立数据链路,进行数据帧的传输和接收,并进行错误检测和纠正
  • 物理层(Physical Layer):主要负责传输比特流,包括建立物理连接、信号编码、调制解调制等

TCP/IP模型

TCP/IP协议的诞生背景是20世纪60年代末和70年代初,当时美国国防部的ARPA(高级研究计划局)在研究如何建立一种去中心化、可靠的通信网络,以应对核战争带来的威胁。ARPA的研究成果最终演变成了TCP/IP协议

可以说是TCP/IP协议是OSI的具体实现,其中典型的的代表是TCP和IP协议。TCP协议负责数据的可靠传输,它将数据分成小的数据包并确保它们以正确的顺序被发送到目的地。IP协议则负责数据包的路由和寻址,它将数据包从一个网络设备传输到另一个网络设备,使得不同类型的计算机和网络设备能够相互通信,并且能够在全球范围内进行数据传输,它是互联网的基础

  • 应用层:应用层是TCP/IP协议的最高层,它负责处理应用程序之间的通信。这个层次包括许多协议,如HTTP、FTP、SMTP等,这些协议决定了应用程序如何在网络上进行通信
  • 传输层:传输层负责处理数据的传输,如TCP协议提供了可靠的连接服务,能够确保数据的完整性和顺序性;UDP协议则提供了无连接服务,适合于需要快速传输的数据
  • 网络层:网络层负责数据包的路由和寻址,它使用IP协议来实现数据包的传输。IP协议能够将数据包从一个网络设备传输到另一个网络设备,同时还能够进行网络地址转换(NAT)
  • 数据链路层:数据链路层负责将数据包封装成帧并发送到物理层,也负责接收物理层传输的帧并解封装成数据包。这个层次包括了以太网、Wi-Fi、蓝牙等协议,它们决定了数据在物理层如何传输

传输过程


了解了网络模型后那数据到底是怎么传输的呢?假如以上计算机A、B要进行通讯,A在浏览器上提交网页登录信息点击发送,这里我们认定他使用HTTP协议,接着将数据发送到传输层当然这里会使用TCP协议作为可靠传输。TCP协议中会添加自己的相关信息作为数据的首部,如:源端口和目标端口、窗口大小、相关标识等等,形成数据段,再提交给网络层。网络层ARP协议会探测源IP地址的mac地址,IP会添加源IP和目标IP等信息作为首部形成数据包,再提交给数据链路层。链路层将添加以源mac地址和目标mac地址等信息为首部,交给物理层。物理层以比特流的形式开始转发,假如遇到路由器时路由器识别目标ip并根据路由表进行转发,期间会将源mac地址修改为路由器另一出口网卡的mac地址,然后再经过其他的路由器或交换机,这样层层转发直到目标主机。目标主机链路层去掉链路首部给到网络层,网络层去掉IP首部给到传输层,传输层去掉TCP首部给到应用层,应用层拿到数据后根据不同情况进行响应或拒绝

以上简单的概述了两端数据转发的过程

mac地址每经过一个路由器时都会将源mac地址修改会其某一网络接口的mac地址,而IP地址不会,因此IP地址决定了数据的起点和终点,mac地址决定了下一跳地址,二者缺一不可

TCP/IP协议

TCP/IP协议是互联网最重要的协议,它是万物互联的基础。<u>TCP/IP协议不是一种协议而是一类协议,是个协议簇包含了很多不同层次协议</u>,因为TCP、IP两种协议比较经典而作为重要代表。以下列出TCP/IP协议中各层所包含的协议:

  • 应用层:HTTP、FTP、Telnet、SMTP、DNS、POP3等等
  • 传输层:TCP、UDP、TLS、QUIC等等
  • 网络层:IP、ARP、RARP、ICMP等等
  • 数据链路层:PPP、HDLC、CSMA等等

TCP/IP协议中包含很多重要的协议,如:HTTP协议对于web开发者了解其数据报文信息很有用、TCP协议连接过程(面试考点)、IP划分(子网掩码、网段等等)、ARP寻址、ICMP网络诊断等在实际开发或网络排错中都很有用

总之搞懂这些协议是非常有用的,了解网络传输本质对于网络的学习也很有帮助,接下来以TCP/IP为范畴详细讲解部分协议

为什么要懂计算机网络?

可以说计算机网络是每个工程师的基础,了解它会让我们明白其背后的原理是怎么样的,比如:前后端的数据传输,使用的什么协议,换成什么协议可以加快数据传输等等。总之学习计算机网络是很重要的,可以拓宽自己视野发展,总之关于网络中的盲区最好还是填补一下

TCP(Transmission Control Protocol,传输控制协议)是一种在计算机网络中常用的协议,它是一种<u>面向连接的、可靠的、基于字节流的传输层协议。TCP协议主要负责对数据进行分段、组装、传输和确认,以保证数据的可靠传输</u>。

特点

  • 可靠性:TCP协议可以保证数据的可靠传输,通过数据分段、校验和、确认机制等手段来确保数据不会丢失、损坏或重复
  • 面向连接:TCP协议在传输数据之前需要先建立连接,传输完成后再释放连接。这种连接方式可以保证数据的有序传输,避免数据混乱
  • 流式传输:TCP协议是基于字节流的传输层协议,将数据按照字节流方式分段传输,不需要考虑数据的长度和格式
  • 拥塞控制:TCP协议可以根据网络状态来控制数据的发送速度,避免网络拥塞导致数据丢失和延迟

TCP首部

  • 源端口:发送方端口,长度为2个字节16位,因此最大值为2的16次方65536 - 1,故TCP的端口范围为0 ~ 65535
  • 目标端口:另一方的端口,范围同上
  • 序列号:代表着数据的位置,最大序号为2的32次方-1,每发一次数据就要累加一次该数据的长度,当序号超过最大值又会从0开始。序列号不一定从0或1开始,而是从建立连接的随机数作为其初始值,在建立连接和断开连接时发送的SYN包和FIN包虽然并不携带数据,但是也会作为1个字节增加对应的序列号。
  • 确认号:指下一次应该收到的数据的序列号
  • 数据偏移:字段长4位,单位为4字节,代表着数据包开始的位置,也可以理解成TCP首部总长度。从图中可以知道首部的固定长度为20字节,在没有可选数据时其最小值为5(二进制表示0101),最大值2的4次方减1(二进制1111)15再乘4为60字节,由此可知可选部分的最大长度为40字节
  • 保留位:4位长度,以后扩展使用,到目前为止还没有任何用处
  • 控制位:8位长度,由左到右分别是CWR(Congestion Window Reduced)ECE(Explicit Congestion Notification)URG(Urgent Flag)ACK(Acknovledgement Flag)PSH(Push Flag)RST(Reset Flag)SYN(Synchronize Flag)FIN(Fin Flag),当他们的值为1时都表示一定的含义

    • CWR:减小拥塞窗口,发送方降低发送速率
    • ECE:ECN回显,发送方接受到了一个更早的拥塞通知(ECN是一种拥塞控制机制,用于在发生拥塞时通知TCP发送方降低发送速率,从而避免网络拥塞)
    • URG:表示当前数据需要紧急处理
    • ACK:表示确认需要有效,也就是ACK为1时上图中的确认号才会有效
    • PSH:表示数据需要立刻传给上层协议
    • RST:表示连接出现非常严重的错误必须重新连接
    • SYN:表示希望建立连接,并将当前的序号作为初始值
    • FIN:表示希望断开连接不会有数据发送了
  • 窗口:字段长为16位,用于通知从确认号开始能够接受的数据大小,如果窗口的值为0时表示可以发送窗口侦测,通常情况下TCP会根据窗口大小进行分段传输,每段最大传输数据大小(MSS)为1460,实际情况的最大长度为1448(减去12字节的时间戳选项)
  • 校验和:端到端校验机制确保数据的正确性,由发送方通过头部、数据计算得到的校验和,接收方方会重新校验进行对比
  • 紧急指针:在URG控制位为1时有效,该字段的数值表示数据中的紧急数据,也就是说从数据的开始到紧急指针的位置为紧急数据,因此紧急指针也代表了紧急数据的末尾
  • 选项:选项类型(无操作、最大段大小MSS、窗口缩放因子、时间戳等等),每个选项的第一个字节为种类指明选项的类型,种类为0和1的选项仅占1字节,其他种类选项根据种类来确定字节数,种类1允许发送者用多个4字节组填充字段

:::warning 注意
在老版本的TCP协议中可能只会存在6为控制位,而CWR、ECE控制位可能不存在,新版本都会有8位控制位
:::

以上的字段明细都可以使用wireshark抓包工具捕获到,如果你对此工具还不熟悉可以参考我的「wireshark网络抓包」一文

连接与终止

TCP是可靠传输协议,其数据传输前通信双方必须建立一条连接,总体来说TCP通信是个复杂的过程(超时重传、流量控制、分包组装等等),整体通信大致过程示意图如下:

三次握手

关于TCP的三次握手和四次挥手是常问的考点,搞清楚它非常简单,这里总结下二者。TCP的连接需要三个步骤:发起端发送SYN、接收端发送SYN+ACK、发送端发送ACK。为什么需要三次呢?一次行不行?答案是否定的,三次正好符合可靠传输的特点。这就好比2人打电话,甲方打给乙方,甲方问你是乙方吗,乙方回答我是那你是谁呢,甲方再次回乙方说我是谁,这样下来双方都知道对方的身份并且知道电话已经打通了,可以收发数据。而TCP连接也是这样的,下方是三次握手连接的示意图:

  1. 刚开始Client处于close状态,Server处于Listen状态
  2. Client主动打开发送SYN=1、Seq=x请求表示主动连接(注意:<u>seq在初始化时是个随机数,它会根据时间戳变化,超时重传时使用相同的seq,新的连接会生成新的序列号,来避免历史或其他连接的错误问题</u>),此时Client进入SYN_SENT状态
  3. Server接受到客户端的SYN请求后,需要应答Client并发送SYN=1、ACK=1、Seq=y请求,其中确认号为x+1且有效期望下一次客户端的发送序列号应为x+1,而SYN表示也想连接Client且序号为y,此时Server进入SYN_RCVD状态
  4. Client收到Server的ACK响应后进入ESTABLISHED状态表示已连接(半连接状态),然后发送Seq=x+1、ACK=1的请求,序列号为y+1且有效,表示确认了Server的连接请求,并期望Server下一次发送的序号为y+1
  5. Server收到客户端的ACK响应后也进入ESTABLISHED状态,双方进入全连接状态,三次握手完毕可以进行数据传输了

抓包结果:

TCP握手期间虽然没有真实的数据进行传输,但在这期间会进行相关重要数据的约定,如:窗口大小、MSS等有用的信息

重置连接

三次握手是TCP连接的正常过程,但还有其他非正常的连接出现严重的连接错误,此时就会通过标记RST来重置连接,比如主机A使用telnet连接主机B的某个未开放端口,就会被主机B拒绝:

# 使用主机A192.168.10.8登录 主机B192.168.10.9:8080
➜ telnet 192.168.10.9 8080
Trying 192.168.10.9...
telnet: connect to address 192.168.10.9: Connection refused

当主机B接受到TCP连接请求时被认为成错误的连接,此时主机B会主动发送一个RST的响应,表示连接错误,需要重新连接,可以使用抓包工具查看:

连接队列

上面的三次握手只是简单的概述了主要的连接过程,在真实环境中存在请求队列的概念,如同时并发多个TCP请求,就会将其排列成队列进行处理

在TCP连接中存在两个重要的队列SYN队列(半连接队列)Accept队列(全连接队列),它们分别用于<u>处理连接请求和已经建立连接的数据传输</u>

  • SYN队列:用于存储SYN(同步)请求的队列。当一个客户端请求与服务器建立TCP连接时,它会向服务器发送一个SYN包。服务器在收到SYN包后,将在SYN队列中排队等待确认,并向客户端发送一个SYN-ACK包作为确认
  • Accept队列:用于存储已经建立连接的队列(未被上层应用程序使用)。当服务器收到客户端第三次握手的ACK请求后,客户端和服务器之间的连接就建立了,此时连接会被添加到accept队列中,等待应用程序使用

大致处理过程示意图如下:

首先Client主动发送SYN连接请求,Server收到后创建半连接对象将其放入SYN队列,Server发送ACK+SYN给Client后,直到收到Client的ACK响应后,创建全连接对象将其连接放入Accept队列,最后由应用程序接受处理

SYN队列的的最大限制通常是1000,Accept队列的最大限制通常是128,可以通过以下方式查看:

# 查看syn队列最大值
➜ cat /proc/sys/net/core/netdev_max_backlog
1000
# accept队列最大值
➜ cat /proc/sys/net/ipv4/tcp_max_syn_backlog
128

总的来说两个队列并不是很大,但实际情况中由于CPU、内存等相关因素的影响,我们的应用程序可能达不到很高的并发处理请求,因此TCP的请求就会被延时,或当SYN、Accept队列溢出时,Server也会忽略掉Client的SYN请求包,根据重传机制Client等待一段时间重新发送SYN,此时Server表现出繁忙的状态

SYN泛洪

SYN泛洪也称SYN攻击,是TCP常见的网络攻击手段,其利用TCP连接三次握手的第一阶段,当Client向Server疯狂发送SYN请求,却不响应Server的ACK+SYN请求时,Server的SYN队列会很快被打满,从而主动丢弃后面的SYN请求,也就无法进行正常的TCP连接了,大量的SYN请求也会消耗Server的资源,产生不正常的消耗

如何避免SYN攻击呢,可以从防火墙SYN Cookies减小SYN队列SYN ACK重传次数等着手:

  1. 开启SYN Cookies:

    什么是SYN Cookies?当SYN队列溢出时,如果再收到SYN请求不会为其分配任何存储资源,而只有当SYN+ACK报文段被确认时才分配到Accept队列里。SYN Cookies是由Server通过一定的算法加随机值计算而来,再作为序列号发送给Client,Server收到Client的ACK报文后会校验合法性,如果合法将会放入Accept队列,否则直接丢弃,这样就绕过了SYN队列资源。

    可以通过以下方式设置syncookies:

    # 0 表示关闭
    # 1 SYN队列溢出时开启
    # 2 永久开启
    ➜ cat /proc/sys/net/ipv4/tcp_syncookies
    1
    服务器通常用下面的方法设置初始序列号:首5位是t模32的结果,其中t是一个32位的计数器,每隔 64秒增1;接着3位是对服务器最大段大小(8种可能之一)的编码值;剩余的24位保存了 4元组与t值的散列值,该数值是根据服务器选定的散列加密算法计算得到的。Server根据其中的t值可以计算出与加密 的散列值相同的结果,那么服务器才会为该SYN重新构建队列
  2. 减小SYN ACK重传次数

    减小重传次数让其达到最大重传次数时,释放掉SYN资源断开连接,SYN ACK重传次数由linux内核决定的,可以通过以下方式进行查看和设置:

    ➜ cat /proc/sys/net/ipv4/tcp_synack_retries
    5

四次挥手

TCP断开需要4步完成

  1. 假如Client想要释放连接将会发送FIN请求给Server端,接着进入FIN_WAIT_1状态
  2. Server收到Client的FIN请求后,给Client回了一个ACK响应,进入CLOSED_WAIT状态
  3. Client收到Server的ACK响应后,进入FIN_WAIT_2状态,等待Server发送FIN请求
  4. Server处理完数据发送FIN请求给Client,进入LAST_ACK状态
  5. Client终于等到了Server的FIN请求,赶紧给Server回了一个ACK响应,紧接着进入TIME_WAIT状态,在等待2MSL时间后也会进入CLOSE状态
  6. Server收到Client的ACK响应后进入CLOSE状态,此时Server已经完全关闭

抓包结果:

为什么看到的是3次挥手?

不是说TCP断开需要4次挥手吗?为什么实际抓包的结果却是3次就完成了?这里就需要了解TCP的延时确认机制

TCP的延迟确认(Delayed ACK)是一种优化TCP传输性能的机制。TCP协议默认采用延迟确认机制,即接收方不会立即发送确认消息,而是等待一定时间(通常是200ms),看是否需要返回数据,如果无需数据就会等待一会看后面的请求是否需要返回数据,便一起返回发送一个确认消息,以减少确认消息的数量和网络负载,否则需要数据就会立刻返回ACK

而三次回收的情况通常是接收端不需要再发送数据了,根据延时确认机制便会将2、3次挥手(ACK&FIN)合并成一次数据返回,这就是为什么实际网络中看到的是3次挥手的原因。可以通过修改TCP的延迟发送值来禁用此功能,这里不再展开

什么是MSL?为啥需要2MSL

MSL(Maximum Segment Lifetime)是报文最大生存时间,也就是说网络中最大的存活时间,否则将会丢弃。<u>MSL的时间通常大于TTL的跳数时间,IP的传输存活时间是基于TTL的跳数,每过一个路由器TTL都会减去1,直到为0时将会丢弃报文,同时发送ICMP报文通知源主机目标不可达</u>

MSL默认值通常为30,TTL的默认跳数为64,系统认为30s内IP可以被转发64次。为什么是2MSL呢?2MSL是从Client接收到Server的FIN报文后发送ACK报文开始计算的,假如ACK报文由于网络原因在MSL时间内没有到达Server,Server会触发超时重传机制再次发送FIN报文给Client,所以2MSL是最保险的时间,当Client等待2MSL内没有收到任何Server的包就会被关闭掉

通过以下方式查看系统默认的TTL跳数和MSL时间

# TTL 默认跳数
➜ cat /proc/sys/net/ipv4/ip_default_ttl
64
# FIN默认超时时间(2MSL时间)
➜ cat /proc/sys/net/ipv4/tcp_fin_timeout
60

有趣的是处于TIME_WAIT(2MSL)状态下的端口还不能重新被使用!虽然Client已经收到Server的FIN报文并发送了ACK报文确定要断开连接了,但在TIME_WAIT期间其端口还是不能被使用(尽管Server可能已经被关闭了),这个原因其实和为啥要等待2MSL道理一样,都是为了避免2MSL期间Server重传FIN报文。我们来验证下端口不可用:

# 访问本地nginx
➜ curl localhost

# 查看TCP的连接状态,可以很明显本地的 `33196` 端口关联了 80端口,即curl随机选用了 33196 端口访问nginx
➜ netstat -ant | grep 80
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:33196         127.0.0.1:80            TIME_WAIT

# 使用nodejs监听33196端口,抛出异常 端口被占用
➜ node index.js
events.js:377
      throw er; // Unhandled 'error' event
      ^

Error: listen EADDRINUSE: address already in use :::33196

上述等再次查看33196已经被完全断开时就可以正常启动了

查看TCP状态

可以在主机上通过netstat命令查看tcp的连接状态,netstat是一个用于显示网络状态和统计信息的命令行工具,可以用来查看系统的网络连接、路由表、接口状态等

➜ netstat -antp | more
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN      872/rpcbind
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      1229/nginx: master
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      1211/sshd
tcp        0      0 127.0.0.1:25            0.0.0.0:*               LISTEN      1454/master
tcp        0      0 0.0.0.0:10010           0.0.0.0:*               LISTEN      1229/nginx: master
tcp        0    196 192.168.10.9:22         192.168.10.1:65308      ESTABLISHED 1691/sshd: root@pts
tcp        0      0 127.0.0.1:33196         127.0.0.1:80            TIME_WAIT   -

可以从上看出tcp监听的端口号、进程、连接状态等等。如:nginx监听了80端口、ssh监听了22端口,22端口和65308端口保持连接(这里我在主机用terminal连接了虚拟机)处于ESTABLISHED建立状态,33196端口和80端口已经处于断开但还在TIME_WAIT状态

半连接、半断开、半打开

要了解这几种状态含义需要明白TCP是个全双工连接协议,它提供了全双工的数据传输能力。全双工连接指的是通信双方可以同时发送和接收数据,而不受对方数据传输的影响。这意味着,在一个TCP连接中,数据可以沿着两个方向同时传输,而不需要等待对方的回复

半连接:在TCP三次握手时,Client如果不回复Server端ACK报文时,Server端就处于SYN_RCVD状态,消耗Server端的资源,常见的SYN攻击就是基于半连接漏洞产生的

半断开:在TCP四次挥手期间,Client发送了FIN报文后,却一直等不到Server的FIN报文,此时Client只能收到报文而不能发送报文

半打开:正常的TCP连接双方都可以收发数据报文,假如在某时间段两端不进行任何数据传输,而一端由于某种原因断开了且不发送任何FIN报文,另一端也不会知道是不是断开了,这时就处于半打开状态。而系统内核一般对TCP有一个保活机制(KeepAlive)来心跳检测是否还处于连接状态,当TCP在指定时间不发送任何数据,系统则认为已断开连接

:::warning 注意
HTTP也有一个KeepAlive的概念,但与TCP的KeepAlive完全不同;HTTP的KeepAlive是开启长连接,在HTTP/1.1默认会开启,主要是提供HTTP请求可以在同一个TCP连接上完成,减小网络开支;而TCP的KeepAlive是个保活机制,在长时间没有进行数据传输时用来探测是否还处于连接状态
:::

数据传输

我们知道TCP是个可靠传输协议,那么它是如何实现数据传输的可靠性的呢?通常情况下TCP会对传输的数据进行分段并进行编号,当接受方收到数据后重新进行组装。若在传输的过程中出现了丢包,发送端便会重传丢失的包,其具体实现看下一小节

分段传输

TCP协议的每段最大数据长度通常被称为MSS(Maximum Segment Size)1460,这个值是根据IP层MTU(Maximum Transmission Unit)最大传输单元计算的,MTU值一般为1500,如果过大会造成网络中路由器的缓冲压力,当IP层所传输的数据大小大于MTU时就会进行IP分片

如果IP分片传输中有一个IP片丢失了就会重传所有的IP片段,这无疑会浪费网络资源。所以TCP为了不让数据在IP分片,通常设置为每片IP最大数据承载大小也称为MSS。<u>MTU的值为1500字节,IP头占用20字节,TCP头占用20字节,因此MSS的值通常为1500-20-20=1460字节</u>

可以通过设备上的某个网卡查看MTU大小:

➜ cat /sys/class/net/ens160/mtu
1500

在实际中MSS的值通常都是1448比1460少了12个字节,这通常是TCP的头部选项部分会多12字节的时间戳等信息,其用来服务超时重传等功能

滑动窗口

TCP的数据可靠传输类似于一问一答的形式,每发送一个数据包会通过另一方的确认来判断是否丢包,但如果每次只能发送一个数据包将大大提高时延,因此TCP中引入了窗口的概念

什么是TCP窗口(Window)?<u>窗口通常用来限制数据包的发送速率,是一个流量控制机制,通过动态改变窗口的大小进行控制发送速率</u>。窗口的大小通常等于接收方的窗口大小,会根据接收方的确认消息动态调整,在三次握手期间虽然没有进行实质的数据传输,但通常都会协商一些有用的信息,其中就包含窗口的大小

窗口其实是个缓存空间,每一方都会在缓存空间中维护着一些有用的信息,如:已发送的数据、未发送的数据等等。假如发送方的窗口大小为3,那么就会连续发送3个数据包,而不需要等待一个一个确认,同时缓冲区会记录这些已发送和未发送的信息,从而改变窗口大小和位置

滑动窗口的概念则是调整发送数据的起始位置,TCP在传输数据时会将数据分成多段MSS大小的数据段并进行编号,然后便会以窗口大小为基本单位按顺序发送数据,因此窗口范围内的数据发送期必早于窗口后的数据。而窗口内通常会包含多个数据片段,每段数据被实际接收的时间会因为网络原因进行波动,所以发送方收到不同数据片段的ACK时间也会不同,当窗口的前部分被确认后,就会进行窗口滑动,这样后面未在窗口内的数据段就会被放入窗口内等待发送

大概的滑动窗口示意图如下:

窗口的大小会根据接收方的确认信息、发送时延、拥塞控制动态改变的,然后来控制发送速率,达到数据传输的可靠、高效,接下来了解下TCP如何完成数据传输的可靠性

重传机制

大家知道TCP是个可靠的传输协议,所以会有特殊的机制来保证传输数据的可靠性,而其中一个重要的特性就是超时重传。当发送方发送一个报文后在指定的时间内没有收到另一方的响应后,就会认为数据包已经丢失,便会根据重传机制重新发送数据包

由于网络波动等种种原因,超时重传是避不开的问题,重传有两种机制:<u>基于时间和基于冗余ACK,通常后者比前者更高效</u>

RTT、RTO

了解超时重传前需要知道RTTRTO两个时间概念

RTT(Round-Trip Time)是指TCP数据包从发送方发送到收到接收到另一方的ACK所需的时间

RTO(Retransmission Timeout)是指TCP发送方在未收到确认的情况下等待重传的时间

超时重传是根据RTO的时间进行判断的,RTO是根据RTT进行动态计算的,如何确定RTO的时间是非常关键的,通常略大于RTT的时间,过小造成网络资源浪费,过大网络延时偏大,这里不具体展开其实现算法

超时重传

当TCP发送一个数据包时会启用一个定时器进行倒计时,如果在RTO的时间范围内收到了对方的ACK包则重置定时器不会重传报文,反之没有收到另一方的ACK,则会重新发送数据包,并重启定时器,当超时重传时会把定时器的时间设置为上一次的2倍,也称二进制指数避退(binary exponential backof),当然不会无线重传下去,会有重传的阈值,当超过了这个值就会断开TCP连接,默认操作系统的重传次数可以通过以下方式进行查看:

➜ cat /proc/sys/net/ipv4/tcp_syn_retries
6

快速重传

快速重传不是基于定时器,而是基于数据进行重传的,通常发生在没有延时的情况下。若TCP累积确认无法返回新的ACK,或者当ACK包含选择确认信息(SACK)表明出现失序报文段时,快速重传会推断出现丢包

通常来说,当发送端认为接收端可能出现数据丢失时,需要决定发送新(真正丢包的) 数据还是重传所有的问题,处理不好就会造成网络资源的浪费

TCP提供了SACK(Selective Acknowledgment)方法来提高重传的效率。该机制允许接收方向发送方发送选择性确认(SACK),即确认接收到的连续数据块,同时告知发送方已经接收到丢失数据块的位置,发送方一般都会维护一个缓冲区用来标识已发送的和超时的,从而让发送方只重传丢失的数据块

拥塞控制

拥塞控制是一种网络流量控制机制,用于避免在网络中发生拥塞而导致网络性能下降或崩溃。该机制通过动态调整发送数据的速率来控制网络中的拥塞程度,并确保网络能够承载传输的数据量

TCP的拥塞控制算法通常基于网络拥塞的反馈信息,例如丢包、延迟等。当TCP发送方收到这些反馈信息时,它会采取一系列措施来减少发送数据的速率,以避免过多的数据流入网络中而导致拥塞。<u>通过改变发送窗口进行速率的控制,发送窗口一般等于接收窗口,而有了拥塞控制后,发送窗口会取拥塞窗口和接收窗口的最小值</u>

慢启动

在TCP连接建立时,发送方会将拥塞窗口的大小设置为一个较小的值,通常为2个最大分段大小(MSS)。发送方将拥塞窗口的大小逐渐增加,每发送一个数据段就将窗口大小加1,这样可以使得发送方逐渐探测网络的可用带宽。但不可能一直这样增长下去,当拥塞窗口大小达到一个阈值时,发送方将进入拥塞避免阶段,此时拥塞窗口的增加速率将变慢

假如拥塞窗口刚开始为1,当接受到1个ACK后,窗口大小加1可以同时发送2个SYN,然后接受到2个ACK后变成窗口大小变为4;接着发送4个SYN收到后大小变成8;就这样反复增加直到达到最大阈值,慢启动算法就是以指数的形式增加,增加速度比较快

拥塞避免

当达到慢启动的阈值时就会进入拥塞避免算法,以防止指数级的发送造成网络堵塞问题,拥塞避免算法当收到1个ACK时,拥塞窗口增加1/n个大小,这样窗口的增长速率会越来越小

除了慢启动、拥塞避免算法外,还有快速恢复算法快速来恢复发送速率,这里不再展开了

总结

本文从TCP的首部、建立断开、传输过程、重传机制、拥塞控制等多方面讲述了TCP协议的工作方式和细节,而TCP远不止这么简单内容,作为非网络工程师对于更深的概念了解下即可,掌握这些通常足够了

参考文献


ihengshuai
49 声望5 粉丝

人生是一场修行