
1. 运输层到底在解决什么问题很多人学计算机网络学到运输层就开始犯迷糊。前面物理层、数据链路层、网络层好歹还能对应到网线、网卡、IP地址这些看得见摸得着的东西一到运输层好像突然就飘了——端口、复用、分用、可靠传输、滑动窗口、拥塞控制一堆名词砸过来每个字都认识连在一起就不知道在说什么。我刚开始接触这块的时候也是这样。后来在实际项目里抓包、调优、排查连接问题被现实反复教育了几轮之后才慢慢把运输层这套东西串起来。这篇文章就把我踩过的坑、总结的方法、以及那些“教材上不会明说但实际工作中天天遇到”的细节一次性讲清楚。运输层最核心的使命用一句话概括让运行在不同主机上的进程之间能够像面对面聊天一样交换数据。网络层负责把数据送到正确的主机但它不关心这台主机上到底是哪个程序在收发数据。运输层就是在这个基础上加了一层“进程到进程”的精确投递机制。你可以这样理解网络层是快递公司把包裹送到小区门口运输层是快递员根据门牌号把包裹送到具体的住户手里。没有运输层数据到了主机也不知道该交给谁。运输层主要提供两种服务模式。一种是面向连接的可靠传输代表协议是TCP另一种是无连接的尽力而为传输代表协议是UDP。这两种模式没有谁好谁坏只有适不适合当前场景。选错了协议后面要么性能上不去要么可靠性出问题返工成本非常高。这篇文章会从端口和复用分用讲起然后深入TCP的可靠传输机制、流量控制、拥塞控制再对比UDP的适用场景最后给出实际工作中排查运输层问题的思路和方法。不管你是正在准备考试的学生还是刚入行做网络编程的开发者或者是在运维岗位上被连接问题折磨的工程师应该都能从中找到对自己有用的东西。2. 端口、复用与分用运输层的第一道门2.1 端口号到底是什么端口号是运输层最基础的概念但很多人对它的理解停留在“就是那个数字”的层面。实际上端口号是运输层实现进程到进程通信的关键标识。一台主机上同时运行着浏览器、聊天软件、邮件客户端、后台服务等一大堆程序每个程序都可能需要收发网络数据。当数据到达主机时操作系统怎么知道该把这份数据交给哪个程序答案就是靠端口号。端口号是一个16位的数字范围从0到65535。这个范围看起来很大但在实际使用中端口号的分配是有严格规则的0到1023系统端口也叫知名端口。这些端口号被分配给最常用的服务比如HTTP用80HTTPS用443DNS用53SSH用22。在类Unix系统上普通用户程序不能随便绑定这些端口需要管理员权限。1024到49151用户注册端口。这些端口号由一些应用程序注册使用比如MySQL默认用3306Redis默认用6379。你自己写程序的时候一般从这个范围里选。49152到65535动态端口也叫临时端口。客户端程序发起连接时操作系统会从这个范围里自动分配一个端口号给客户端使用。注意端口号只在本机范围内有意义。同一台机器上不能有两个程序同时监听同一个端口除非用了SO_REUSEPORT这类特殊选项但不同机器上的相同端口号互不冲突。2.2 复用和分用一对容易搞混的概念复用和分用是运输层实现进程间通信的核心机制但这两个词经常被搞混。复用发生在发送方。一台主机上多个应用进程都有数据要发送运输层把它们的数据收集起来加上运输层头部包含源端口和目标端口然后交给网络层统一发送。这个过程叫复用意思是“多个进程共用同一个网络层服务”。分用发生在接收方。网络层把数据交给运输层后运输层根据头部的目标端口号把数据分发给对应的应用进程。这个过程叫分用意思是“把共用通道里的数据分给不同的进程”。用一个生活场景来类比一栋写字楼里有很多公司快递员把整栋楼的包裹送到前台网络层把数据送到主机前台根据包裹上的房间号把包裹分给各个公司运输层根据端口号分用给各个进程。反过来各个公司把要寄的包裹交给前台统一发走复用。2.3 套接字IP加端口的组合在实际编程中我们很少单独谈端口号更多是谈套接字。一个套接字由IP地址和端口号组成格式通常是IP:端口。比如192.168.1.100:8080就是一个套接字。但这里有个细节需要注意TCP连接是由四元组唯一标识的——源IP、源端口、目标IP、目标端口。这意味着即使两台主机之间只建立了一条物理链路也可以同时存在多条TCP连接只要四元组中至少有一个元素不同。我见过有同学问“为什么我的服务器监听80端口同时还能处理成千上万的连接”原因就在这里。服务器监听的是本机IP:80这个套接字但每条已建立的连接都有不同的四元组客户端的IP和端口不同所以可以同时存在大量连接。2.4 端口使用的实操建议在实际开发中关于端口号有几个经验性的建议服务端程序尽量使用1024以上的端口避免和系统服务冲突。如果确实需要用80或443通过反向代理转发而不是让应用直接绑定。客户端程序不需要手动指定端口让操作系统自动分配即可。手动指定客户端端口容易导致端口冲突。如果程序需要频繁建立短连接注意检查系统的临时端口范围是否够用。在高并发场景下临时端口耗尽是一个常见问题。排查端口占用问题时Linux下用ss -tulnp或netstat -tulnpWindows下用netstat -ano可以快速定位是哪个进程占用了端口。3. TCP可靠传输不只是“确认重传”四个字3.1 停止等待协议为什么效率低理解TCP可靠传输最好从最简单的停止等待协议开始。停止等待协议的逻辑很直白发送方发一个数据包然后停下来等确认接收方收到后回一个确认发送方收到确认后再发下一个数据包。如果发送方等了一段时间没收到确认就重传刚才那个包。这个协议的正确性没问题但效率极低。假设发送方发送一个数据包需要1毫秒确认回来需要1毫秒那么发送一个包的总周期是2毫秒。如果数据包大小是1000字节那么每秒只能发送500个包吞吐量大约是4Mbps。而实际网络带宽可能是100Mbps甚至更高利用率不到5%。问题出在“停下来等”这三个字上。发送方在等待确认的时间里明明可以继续发送后续的数据包却白白浪费了。3.2 滑动窗口让管道填满滑动窗口机制就是为了解决停止等待协议效率低的问题。核心思想是发送方不需要每发一个包就停下来等确认而是可以连续发送多个数据包只要这些包都在“窗口”范围内。窗口的大小决定了发送方可以连续发送多少个未被确认的数据包。窗口越大管道里能容纳的数据越多吞吐量越高。但窗口不能无限大。接收方的缓冲区有限如果发送方发得太快接收方处理不过来数据就会丢失。所以窗口大小需要根据接收方的处理能力动态调整这就是流量控制。3.3 超时重传时间到底怎么定超时重传是TCP可靠传输的兜底机制。发送方发出数据后启动一个定时器如果在定时器超时之前没有收到确认就重传数据。这里的关键问题是超时时间设多长合适设得太短会导致不必要的重传。网络只是稍微慢了一点确认还没回来发送方就急着重传结果接收方收到两份重复数据浪费带宽。设得太长会导致重传不及时。数据包真的丢了发送方要等很久才重传这段时间连接实际上处于停滞状态吞吐量下降。TCP的做法是自适应地计算超时时间。它维护一个往返时间RTT的加权平均值再根据RTT的波动情况加上一个安全余量。具体来说先测量每个数据包的往返时间样本计算平滑往返时间SRTT用指数加权移动平均计算往返时间偏差RTTVAR衡量RTT的波动程度超时时间 SRTT 4 × RTTVAR这个公式的好处是当网络稳定时RTT波动小超时时间接近SRTT当网络抖动大时RTTVAR增大超时时间自动放宽避免误重传。实操心得在排查网络问题时如果发现大量重传先看RTT是否稳定。如果RTT波动很大说明网络路径可能存在问题比如路由抖动或链路拥塞。如果RTT稳定但重传率高可能是超时时间设置不合理或者确实有丢包。3.4 快速重传不等超时也能重传超时重传的问题是等待时间太长。如果发送方连续收到三个对同一个数据包的重复确认就可以推断这个数据包丢了不需要等超时定时器到期直接重传。这就是快速重传。为什么是三个重复确认因为网络传输中偶尔会出现乱序接收方收到后面的包时会重复确认前面的包。如果只是偶尔一两个重复确认可能是乱序导致的不一定是丢包。但连续三个重复确认丢包的概率就非常大了。快速重传大大缩短了丢包恢复的时间。在高速网络中超时时间可能是几百毫秒而快速重传可以在几十毫秒内完成恢复。3.5 选择确认只重传丢失的包早期的TCP在重传时会把丢失包之后的所有包都重传一遍。比如发送了1到10号包其中5号丢了接收方收到了1到4号和6到10号但只能确认到4号。发送方重传时会把5到10号全部重传。这显然很浪费。选择确认SACK机制允许接收方告诉发送方“我收到了1到4号和6到10号只缺5号。”发送方就可以只重传5号包。SACK在现代网络中非常重要尤其是在有丢包但带宽充足的环境下能显著提升传输效率。4. 流量控制与拥塞控制别把接收方和网络压垮4.1 流量控制照顾接收方的处理能力流量控制解决的是发送方和接收方速度不匹配的问题。接收方的缓冲区有限如果发送方发得太快缓冲区满了新来的数据就只能丢弃。TCP通过接收窗口来实现流量控制。接收方在每个确认报文中告诉发送方“我还能接收多少字节的数据。”发送方根据这个窗口值调整发送速度确保不会超出接收方的处理能力。这里有个细节接收窗口的大小是动态变化的。当接收方应用程序读取了缓冲区中的数据缓冲区腾出空间接收窗口就会增大。如果应用程序读取速度慢缓冲区逐渐填满接收窗口就会减小甚至变成0。当接收窗口变成0时发送方停止发送数据但会定期发送窗口探测报文询问接收方窗口是否恢复。这个探测不能太频繁否则浪费带宽也不能太稀疏否则窗口恢复后发送方不知道白白浪费时间。4.2 拥塞控制照顾网络的承载能力流量控制照顾的是接收方拥塞控制照顾的是网络。如果网络中的路由器缓冲区满了数据包就会被丢弃。发送方如果继续高速发送只会导致更多丢包网络状况进一步恶化。TCP的拥塞控制包含四个核心算法慢启动连接刚建立时发送方不知道网络能承受多大的发送速率所以从一个很小的拥塞窗口开始每收到一个确认就把窗口加倍。这样窗口呈指数增长快速探测网络的承载能力。拥塞避免当拥塞窗口增长到慢启动阈值后改为线性增长每经过一个往返时间窗口只增加一个报文段的大小。这样增长速度放缓避免过快逼近网络极限。快重传收到三个重复确认时立即重传丢失的报文段不等超时。快恢复配合快重传使用。当发生快速重传时把慢启动阈值减半拥塞窗口设为新的阈值然后进入拥塞避免阶段。这样避免了重新从慢启动开始传输速率不会下降得太厉害。4.3 拥塞控制算法的演进经典的TCP拥塞控制算法Tahoe、Reno在现代高速网络中暴露出一些问题。比如在长肥管道高带宽、高延迟中慢启动需要很长时间才能把窗口涨到足够大链路利用率上不去。后来出现了一系列改进算法NewReno改进了快恢复阶段的重传逻辑一次可以恢复多个丢包。CUBIC使用三次函数控制窗口增长在高速网络中收敛更快对丢包不那么敏感。Linux默认使用这个算法。BBR不依赖丢包作为拥塞信号而是测量带宽和往返时间主动控制发送速率。在有一定丢包但带宽充足的链路上BBR表现明显优于基于丢包的算法。实操心得如果你的服务部署在跨国或跨运营商链路上发现TCP吞吐量上不去可以尝试调整拥塞控制算法。Linux下通过sysctl net.ipv4.tcp_congestion_control查看当前算法通过修改/etc/sysctl.conf切换。BBR在很多场景下效果显著但也不是万能的需要根据实际链路测试。4.4 流量控制和拥塞控制的配合发送方实际能发送的数据量取接收窗口和拥塞窗口中的较小值。接收窗口是接收方的限制拥塞窗口是网络的限制两个都不能突破。这个设计很合理如果只考虑接收方网络可能被压垮如果只考虑网络接收方可能处理不过来。两个窗口共同决定发送速率既保护了接收方也保护了网络。5. UDP被低估的“简单”协议5.1 UDP到底简单在哪里UDP的头部只有8个字节包含源端口、目标端口、长度和校验和。没有连接建立没有确认重传没有流量控制没有拥塞控制。发送方把数据交给UDPUDP加上头部就交给网络层完事。这种简单性带来两个直接结果一是开销小传输效率高二是不可靠数据可能丢失、重复、乱序。很多人因此看不起UDP觉得它“不靠谱”。但在很多场景下UDP反而是更合适的选择。5.2 什么时候该用UDP实时音视频视频通话、直播、在线游戏这些场景对延迟极其敏感但对少量丢包有一定的容忍度。用TCP的话丢一个包会导致后续数据都被阻塞等待重传延迟飙升体验反而更差。UDP丢了就丢了画面可能花一下但不会卡顿。DNS查询DNS查询通常很小一问一答。用TCP的话建立连接就需要三次握手开销比查询本身还大。UDP一发一收简单高效。如果响应太大超过UDP限制DNS会切换到TCP但这是少数情况。广播和多播TCP只支持一对一通信UDP支持一对多。需要向多个接收方发送相同数据时UDP是唯一选择。简单请求响应如果应用层自己能处理可靠性比如请求超时后重试那么底层用UDP可以避免TCP的额外开销。5.3 UDP的可靠性怎么补UDP本身不提供可靠性但应用层可以自己实现。常见的做法包括应用层确认和重传接收方收到数据后回一个确认发送方超时未收到确认就重传。序列号给每个数据包编号接收方可以检测丢包和乱序。前向纠错发送方额外发送一些冗余数据接收方在少量丢包时可以通过冗余数据恢复不需要重传。这些机制在实时通信协议中很常见。比如WebRTC就有一套完整的机制来在UDP之上实现可靠传输同时兼顾实时性。注意在UDP之上实现可靠性需要仔细设计拥塞控制。如果应用层不管不顾地重传可能导致网络拥塞加剧。RFC 8085给出了UDP使用指南值得参考。6. 运输层问题排查实战6.1 连接建立失败的常见原因TCP连接建立失败通常表现为连接超时或连接被拒绝。排查思路如下现象可能原因排查方法连接超时目标端口未监听、防火墙拦截、网络不通检查目标端口是否监听检查防火墙规则用ping和traceroute确认网络可达性连接被拒绝目标端口未监听、监听队列满检查目标端口监听状态检查backlog设置连接建立后立即断开应用层主动关闭、协议不匹配检查应用日志确认双方协议版本6.2 传输速度慢的排查思路传输速度慢可能涉及多个层面运输层相关的因素包括窗口太小接收窗口或拥塞窗口限制了发送速率。可以通过抓包查看窗口大小。重传率高丢包导致频繁重传有效吞吐量下降。抓包统计重传次数。拥塞控制算法不合适在高延迟链路上基于丢包的算法可能过于保守。尝试切换算法。Nagle算法与延迟确认相互作用小包被合并发送导致延迟增加。对延迟敏感的应用可以关闭Nagle算法。6.3 抓包分析运输层问题抓包是排查运输层问题最直接的手段。Wireshark是最常用的工具但命令行下tcpdump更轻量。几个常用的抓包技巧# 抓取指定端口的TCP流量 tcpdump -i eth0 -nn tcp port 8080 -w capture.pcap # 抓取指定主机的UDP流量 tcpdump -i eth0 -nn udp and host 192.168.1.100 -w capture.pcap # 实时查看TCP标志位 tcpdump -i eth0 -nn tcp[tcpflags] (tcp-syn|tcp-fin|tcp-rst) ! 0在Wireshark中重点关注以下几个指标Expert InformationWireshark自动标记的异常如重传、重复确认、零窗口。TCP Stream Graphs时序图可以直观看到窗口变化、重传位置。Statistics TCP Stream Graphs Window Scaling查看窗口缩放情况。6.4 常见问题速查表问题可能原因解决方向大量重复确认丢包、乱序检查链路质量确认是否有设备乱序零窗口频繁出现接收方应用读取慢优化接收方处理逻辑增大缓冲区重传率高网络丢包、超时时间过短检查链路调整超时参数连接建立慢三次握手延迟高、SYN队列满检查网络延迟调整backlog吞吐量上不去窗口限制、拥塞控制保守调整窗口大小切换拥塞控制算法7. 一些容易被忽略的细节7.1 TIME_WAIT状态的意义主动关闭连接的一方会进入TIME_WAIT状态等待2MSL报文最大生存时间的两倍后才完全关闭。这个设计有两个目的一是确保最后一个确认能到达对方如果确认丢了对方会重传FIN处于TIME_WAIT的一方还能响应二是让本次连接的残留报文在网络中消失避免影响后续使用相同四元组的新连接。在高并发短连接场景下服务器上可能出现大量TIME_WAIT占用端口资源。可以通过调整内核参数来缓解比如开启tcp_tw_reuse允许重用TIME_WAIT状态的端口。但要注意这些参数不是随便调的需要理解其影响。7.2 SYN Flood与SYN CookieSYN Flood是一种常见的资源耗尽型问题攻击者发送大量SYN报文但不完成三次握手服务器为每个半连接分配资源最终资源耗尽无法处理正常请求。SYN Cookie是一种防御机制服务器不立即为SYN分配资源而是根据连接信息计算一个Cookie作为序列号放在SYN-ACK中。只有收到客户端的ACK并验证Cookie有效后才分配资源。这样半连接不占用资源有效抵御SYN Flood。7.3 运输层安全传统的TCP和UDP不提供加密和认证数据在网络上明文传输。TLS在运输层之上提供加密和认证保护数据机密性和完整性。理解运输层是理解TLS的基础因为TLS的运行依赖于TCP提供的可靠传输。我在实际工作中发现很多运输层的问题最终都归结为对基础概念理解不透彻。比如把流量控制和拥塞控制搞混导致调优方向错误或者不理解TIME_WAIT的机制盲目调整内核参数引发新问题。把基础打牢遇到问题才能快速定位到正确的方向。运输层的内容远不止这篇文章覆盖的这些比如TCP的选项字段、窗口缩放、时间戳、SACK的具体格式UDP-Lite、DCCP、SCTP等传输层协议都值得深入了解。但掌握了上面这些核心概念和排查方法大部分实际工作中遇到的问题都能有清晰的解决思路。