网络协议深度解析-03-运输层:可靠数据传输与拥塞控制
全文摘要
本文将带你深入理解运输层的核心机制,帮助你掌握TCP和UDP的工作原理及适用场景。你将学到TCP如何实现可靠数据传输、三次握手与四次挥手的细节、拥塞控制的算法演进、以及UDP在实时应用中的优势。通过阅读本文,你将理解网络应用如何选择运输层协议,以及TCP/IP协议栈如何在不可靠的网络之上提供可靠的服务。
全书总结
运输层是网络协议栈中负责端到端通信的关键层次,它在不可靠的网络层之上提供可靠传输服务。本文系统梳理了TCP协议的可靠数据传输机制、连接建立与释放的过程、流量控制与拥塞控制算法、UDP协议的特点及适用场景,以及TCP/IP协议栈的演进历程。适合后端开发者、网络工程师、以及对网络底层原理感兴趣的技术人员阅读。
一、运输层的作用:端到端通信
网络层(IP协议)负责把数据包从源主机送到目的主机,但这是不够的。一台主机上可能同时运行着多个网络程序——浏览器、邮件客户端、音乐下载器,每个程序都在收发数据。IP层不知道这些数据该交给哪个程序。
运输层的任务是提供进程到进程的通信服务,确保数据被送到正确的应用程序。
flowchart TB subgraph HostA[主机A] App1[浏览器<br>进程1] App2[邮件客户端<br>进程2] TransA[运输层<br>复用/分用] NetA[网络层<br>IP] end subgraph Internet[互联网] IP[IP路由] end subgraph HostB[主机B] NetB[网络层<br>IP] TransB[运输层<br>复用/分用] App3[Web服务器<br>进程3] App4[邮件服务器<br>进程4] end App1 --> TransA App2 --> TransA TransA --> NetA NetA --> IP IP --> NetB NetB --> TransB TransB --> App3 TransB --> App4 style App1 fill:#e3f2fd style App2 fill:#e3f2fd style App3 fill:#c8e6c9 style App4 fill:#c8e6c9
图表讲解:这张图展示了运输层在网络协议栈中的位置——它连接应用进程和网络层。
运输层的核心功能是复用(Multiplexing)和分用(Demultiplexing):
- 复用:发送方,多个应用层进程的数据被运输层封装,共同使用一个网络层连接
- 分用:接收方,运输层根据端口号将数据交付给正确的应用进程
这个机制通过端口号实现。端口号是16位整数,范围0-65535:
- 0-1023:知名端口(Well-known Ports),由IANA分配,如HTTP(80)、HTTPS(443)、SSH(22)
- 1024-49151:注册端口,由应用注册使用
- 49152-65535:动态端口,客户端临时使用
例如,你同时用浏览器访问两个网站,浏览器会为每个连接分配本地端口(如54321、54322),但都使用目的端口80(HTTP)。服务器收到数据后,运输层根据源IP+源端口+目的IP+目的端口这个”四元组”来区分不同的连接,把数据交给对应的处理逻辑。
二、UDP:简单但不可靠
UDP(User Datagram Protocol,用户数据报协议)是运输层最简单的协议,它只做了最低限度的工作。
UDP的特点
flowchart LR App[应用层数据] --> UDP[添加UDP头部] UDP --> IP[IP层传输] UDP -->|包含| Field1[源端口] UDP -->|包含| Field2[目的端口] UDP -->|包含| Field3[长度] UDP -->|包含| Field4[校验和] style UDP fill:#fff9c4 style IP fill:#e3f2fd
图表讲解:这张图展示了UDP头部的基本结构——简单到只有4个字段。
UDP头部只有4个字段,每个2字节:
- 源端口:发送方端口号(可选,设为0表示不使用)
- 目的端口:接收方端口号(必须)
- 长度:UDP数据报的长度(包括头部)
- 校验和:检测数据是否出错(可选)
UDP的特点:
- 无连接:发送数据前不需要建立连接
- 不可靠:不保证数据送达,不重传丢失的数据包
- 无序:不保证数据包的顺序
- 轻量:头部开销小(8字节),处理速度快
UDP的这些”缺点”在某些场景下反而是优势:
- 实时应用:视频会议、在线游戏对延迟敏感,重传过时的数据包没有意义
- 广播/多播:UDP支持一对多通信,适合视频直播、IPTV等场景
- 简单服务:DNS查询(请求和响应都很小,丢包了再查询一次即可)、SNMP网络管理
- 高性能:在可靠网络中,UDP的开销更小
三、TCP:可靠的运输层协议
TCP(Transmission Control Protocol,传输控制协议)提供了可靠、面向连接的字节流服务。它是互联网最复杂、最重要的协议之一。
TCP的可靠数据传输
网络层是不可靠的——数据包可能丢失、重复、乱序。TCP在不可靠的IP层之上构建了可靠的数据传输服务,使用了以下机制:
flowchart TB Sender[发送方] --> Seq[给数据编号<br>序列号] Seq --> ACK[等待确认<br>ACK] ACK --> Check{收到ACK?} Check -->|超时未收到| Retrans[重传数据] Check -->|正确收到| Next[发送下一个] Retrans --> ACK Retrans --> RTO[重传超时时间<br>动态调整] style Sender fill:#e3f2fd style Retrans fill:#ffcdd2 style Next fill:#c8e6c9
图表讲解:这张图展示了TCP可靠传输的核心机制——通过序号、确认和重传来保证数据可靠送达。
序号(Sequence Number)是TCP可靠传输的基础。TCP把数据看作字节流,每个字节都有编号。发送方在头部包含数据的起始序号,接收方返回确认号(ACK),表示”期望收到的下一个字节序号”。例如,收到序号1000-1999的数据,返回ACK=2000。
重传是应对丢包的手段。发送方设置重传定时器,如果超时还没收到ACK,就重传数据。超时时间(RTO,Retransmission TimeOut)需要仔细设置——太短会不必要的重传,太长会影响响应速度。现代TCP根据测量的往返时间(RTT)动态调整RTO。
冗余ACK(Duplicate ACK)是更快速的丢包检测。如果接收方收到序号2000的数据,但还没收到1000,它会重复发送ACK=1000。发送方收到3个冗余ACK后,就知道中间有包丢失,立即重传,而不用等超时。这称为快速重传。
流量控制让发送方不要发送太快,以免淹没接收方。接收方在TCP头部通告其接收缓冲区大小(窗口大小),发送方保证未确认的数据不超过这个窗口。
TCP连接建立:三次握手
sequenceDiagram participant C as 客户端 participant S as 服务器 C->>S: SYN<br/>序号=x S-->>C: SYN+ACK<br/>序号=y, 确认号=x+1 C->>S: ACK<br/>确认号=y+1 Note over C,S: 连接建立完成 Note over C,S: 双方都知道对方的初始序号
图表讲解:这张时序图展示了TCP连接建立的三次握手过程——这是网络面试最常考的问题之一。
TCP是面向连接的协议,数据传输前需要先建立连接。三次握手的过程:
第一次握手:客户端发送SYN包(SYN=1),并选择一个初始序号x。客户端进入SYN_SENT状态。
第二次握手:服务器收到SYN包,返回SYN+ACK包(SYN=1,ACK=1)。ACK确认号是x+1,表示收到了客户端的SYN。服务器也选择自己的初始序号y。服务器进入SYN_RCVD状态。
第三次握手:客户端收到SYN+ACK包,发送ACK包(ACK=1),确认号是y+1,表示收到了服务器的SYN。客户端进入ESTABLISHED状态。服务器收到这个ACK后也进入ESTABLISHED状态。
为什么需要三次握手?两次不够吗?
- 两次握手只能确认”客户端到服务器”的通道正常,无法确认”服务器到客户端”的通道
- 如果只有两次握手,客户端发送的第一个SYN包在网络中滞留,后来才到达服务器,服务器会误以为是新的连接请求,建立连接并等待数据,浪费资源
- 三次握手让双方都知道自己和对方的发送、接收能力都正常
TCP连接释放:四次挥手
sequenceDiagram participant C as 客户端 participant S as 服务器 C->>S: FIN<br/>序号=u S-->>C: ACK<br/>确认号=u+1 Note over S: 服务器可能还有数据要发送 S->>C: FIN<br/>序号=w C-->>S: ACK<br/>确认号=w+1 Note over C,S: 连接关闭
图表讲解:这张时序图展示了TCP连接释放的四次挥手过程——比三次握手更复杂,因为TCP是全双工的。
TCP连接是全双工的,双方可以同时发送数据。关闭连接时,双方各自关闭自己的发送方向:
第一次挥手:客户端发送FIN包(FIN=1),表示没有数据要发送了。客户端进入FIN_WAIT_1状态。
第二次挥手:服务器收到FIN包,返回ACK包,确认号是u+1。此时服务器可能还有数据要发送给客户端。客户端进入FIN_WAIT_2状态。
第三次挥手:服务器发送完所有数据后,发送FIN包,表示自己也没有数据要发送了。服务器进入LAST_ACK状态。
第四次挥手:客户端收到FIN包,返回ACK包。客户端进入TIME_WAIT状态,等待2MSL(Maximum Segment Lifetime,报文最大生存时间)后完全关闭。服务器收到ACK后关闭。
TIME_WAIT状态的目的是:
- 确保最后的ACK能到达服务器(如果丢了,服务器会重传FIN)
- 等待网络中所有旧的包消失,避免影响新连接
四、TCP拥塞控制
流量控制是关于”接收方处理不过来”,拥塞控制是关于”网络拥堵”。TCP的拥塞控制是网络能够稳定运行的关键。
拥塞控制的四个算法
flowchart TB Start[开始发送] --> Slow[慢启动<br>指数增长] Slow --> Threshold{达到阈值?} Threshold -->|是| Avoid[拥塞避免<br>线性增长] Threshold -->|否| Slow Avoid --> Loss{检测到丢包?} Loss -->|超时| Reset[阈值减半<br>重新慢启动] Loss -->|3个冗余ACK| Fast[快速恢复<br>阈值减半<br>从新阈值继续] Reset --> Slow Fast --> Avoid style Slow fill:#fff9c4 style Avoid fill:#e1f5fe style Fast fill:#c8e6c9 style Reset fill:#ffcdd2
图表讲解:这张图展示了TCP拥塞控制的状态转换——这是互联网稳定运行的秘诀。
TCP拥塞控制包含四个核心算法:
慢启动(Slow Start):连接刚建立时,网络状况未知,从小的拥塞窗口(cwnd)开始,每个RTT(往返时间)翻倍。虽然叫”慢”,但实际增长很快(1→2→4→8→16…),只是为了避免突然发送大量数据导致拥塞。
拥塞避免(Congestion Avoidance):当cwnd达到慢启动阈值(ssthresh)后,进入拥塞避免阶段,cwnd每个RTT增加1,从指数增长变为线性增长,更谨慎地探测网络容量。
快速重传(Fast Retransmit):收到3个冗余ACK时,认为中间有包丢失,立即重传,不用等超时。
快速恢复(Fast Recovery):快速重传后,不进入慢启动,而是将cwnd减半,继续从拥塞避免阶段开始。这是因为收到冗余ACK说明网络还能传送数据,不是严重拥塞。
如果发生超时(说明严重拥塞),则将ssthresh设为当前cwnd的一半,cwnd重置为1,重新进入慢启动。
五、TCP的演进:从TCP Tahoe到TCP BBR
TCP拥塞控制算法经历了多次演进:
flowchart LR subgraph Tahoe[TCP Tahoe 1988] T1[超时:慢启动] end subgraph Reno[TCP Reno 1990] R1[增加快速恢复] end subgraph Cubic[TCP Cubic 2006] C1[立方函数增长<br>适合高带宽] end subgraph BBR[TCP BBR 2016] B1[基于带宽和RTT<br>不依赖丢包] end Tahoe --> Reno Reno --> Cubic Cubic --> BBR style Tahoe fill:#ffcdd2 style Reno fill:#fff9c4 style Cubic fill:#e1f5fe style BBR fill:#c8e6c9
图表讲解:这张图展示了TCP拥塞控制算法的演进——反映了网络环境的变化。
TCP Tahoe是最早版本,只有慢启动、拥塞避免、快速重传。超时后进入慢启动。
TCP Reno增加了快速恢复,3个冗余ACK后不进入慢启动,提高效率。但Reno在高带宽延迟网络(BDP)中表现不佳。
TCP Cubic在Linux中广泛使用,用立方函数调整cwnd,在高带宽网络中表现更好,是目前的默认算法。
TCP BBR是Google提出的新算法,它不把丢包作为拥塞信号(现代网络中丢包不一定意味着拥塞),而是实时测量带宽和RTT,以此调整发送速率。BBR在无线网络和跨洲传输中表现优异,正在逐渐普及。
结语
运输层是应用和网络之间的桥梁,它屏蔽了底层网络的复杂性,为应用提供简单、可靠的通信服务。
TCP的可靠传输、流量控制、拥塞控制是工程设计的典范——在不完美的基础设施上构建了可靠的通信系统。UDP的简单轻量,则在实时应用中找到了用武之地。
理解运输层的工作原理,能帮助你:
- 选择合适的协议:需要可靠性的应用用TCP,对延迟敏感的应用用UDP
- 优化性能:调整TCP缓冲区、启用TCP Fast Open等
- 排查问题:连接超时、丢包、延迟高都与运输层相关
接下来的文章将深入网络层,看看IP协议如何实现跨网络的寻址和路由。
常见问题解答
Q1:为什么TCP需要三次握手,UDP不需要?
答:TCP是面向连接的可靠协议,需要先建立连接确保双方都准备好通信。
三次握手让双方确认自己和对方的发送、接收能力都正常,并协商初始序号。
UDP是无连接的,每个数据包独立发送,不需要建立连接。就像打电话需要对方接听后才能说话(TCP),而发短信直接发出去就行,不管对方是否收到(UDP)。
连接的代价是建立连接的开销(三次握手至少一个RTT),好处是可靠性、流量控制、拥塞控制。UDP没有这些开销,但也失去了这些保障。
Q2:TIME_WAIT状态有什么危害?如何处理?
答:TIME_WAIT状态的连接会占用端口和内存资源。
在高并发服务器上,如果大量连接进入TIME_WAIT状态,可能耗尽可用端口(临时端口范围有限),导致无法建立新连接。
处理方法:
(1)调整net.ipv4.ip_local_port_range增加临时端口范围。
(2)启用net.ipv4.tcp_tw_reuse允许重用TIME_WAIT状态的端口。
(3)优化应用逻辑,让客户端而不是服务器先关闭连接(因为主动关闭方才会进入TIME_WAIT)。
(4)使用连接池复用连接,减少频繁的建立和关闭。
TIME_WAIT状态是TCP协议正常工作所必需的,不能完全消除,只能优化管理。
Q3:什么是SYN Flood攻击,如何防御?
答:SYN Flood是一种DDoS攻击。
攻击者发送大量SYN包但不完成三次握手,服务器为每个SYN分配资源并等待,最终资源耗尽,无法为正常用户服务。
防御方法:
(1)SYN Cookies:服务器不立即分配资源,而是用一种编码的初始序号(包含连接信息),收到ACK时解码验证,如果合法才分配资源。
(2)增大SYN队列:net.ipv4.tcp_max_syn_backlog。
(3)启用SYN Proxy:防火墙代答SYN/ACK,完成三次握手后再与服务器建立连接。
(4)限流:限制单个IP的SYN频率。
现代操作系统和云服务都有内置的SYN Flood防护。
Q4:TCP Keep-Alive和HTTP Keep-Alive有什么区别?
答:两者名字相似但完全不同。
TCP Keep-Alive是TCP层的机制,定期发送探测包,确认连接是否还活着。如果长时间没有数据传输,TCP可能不知道对方已经断开,Keep-Alive可以检测死连接。
HTTP Keep-Alive是HTTP层的机制,允许在一个TCP连接上发送多个HTTP请求,避免为每个请求建立新连接的开销。HTTP Keep-Alive也称为持久连接(Persistent Connection),是HTTP/1.1的默认行为。
TCP Keep-Alive默认关闭(因为会额外消耗带宽),HTTP Keep-Alive默认开启(因为性能提升明显)。
Q5:为什么QUIC使用UDP而不是TCP?
答:QUIC使用UDP主要是为了突破TCP的局限。
TCP的问题是队头阻塞——当一个包丢失时,后续的包即使到达也无法交付,要等丢包重传后才能按序交付。这在高丢包率环境下严重影响性能。
QUIC在UDP之上实现了类似TCP的可靠性,但更灵活:
(1)多路复用:不同流(stream)独立,一个流的丢包不影响其他流。
(2)连接迁移:用Connection ID而不是四元组标识连接,换网络(WiFi切4G)不断线。
(3)更快的握手:0-RTT建立连接,TCP需要1-RTT。
(4)集成TLS:加密传输更安全。
QUIC是HTTP/3的基础,特别适合移动网络和不稳定网络环境。