搞网络的人,不管是做开发、搞运维还是蹲在实验室调嵌入式板子,迟早都会撞上“TCP、UDP、端口”这三个词。它不是某个框架或某个语言里的概念,而是整个互联网通信的地基。你写的每一行socket代码、你配的每一个端口映射、你排查的每一次“连不上”和“一直掉线”,本质上都绕不开这几件事。这篇我就不按教科书顺序念了,直接把平时工作里最常用、最容易踩坑、也最常被问起的东西串起来讲一遍,从协议差异到端口机制,再到各种热门应用场景和排障实操,尽量一篇文章把这块讲透。
1. TCP和UDP:两个传输层老大哥的真实面目
1.1 先看TCP:可靠是有代价的
TCP在传输层干了三件大事:面向连接、可靠传输、拥塞控制。这三个词听起来高大上,翻译成人话就是:TCP会先和对方“握个手”,确认对面活着再开始传数据;数据传出去之后盯着对方,没收到就重发;如果网络堵了,它就主动放慢速度,不给网络添堵。
三次握手这个过程你应该不陌生:客户端发SYN,服务器回SYN+ACK,客户端再回ACK,连接建立。为什么要三步?因为TCP要同时确认双方的收发能力都正常。如果只有两步,服务端没法确认自己的报文客户端能不能收到。这里有个很典型的坑:服务器上如果发现大量SYN_RECV状态的连接,多半是被SYN洪泛攻击了,或者是后端accept队列满了,客户端一直在发SYN却等不到ACK,表现就是“连接超时”。
四次挥手大家也经常听到,客户端发FIN,服务端回ACK,服务端再发FIN,客户端回ACK,连接关闭。这里最有名的就是TIME_WAIT状态。主动关闭连接的一方,会在TIME_WAIT状态里待上2MSL(一般是60秒左右),这个状态存在的意义就是让网络上残留的旧报文自然消亡,避免干扰下一个使用相同端口的新连接。很多做高并发短连接服务的人,都见过TIME_WAIT堆积成山的问题。如果你用netstat看到上百个TIME_WAIT,不用慌,这通常是正常现象,真正要警惕的是ESTABLISHED连接数爆满、CLOSE_WAIT大量堆积——后者多半是你的程序没正确调用close。
拥塞控制这块,TCP提供了慢启动、拥塞避免、快重传和快恢复。简单说就是:连接刚开始不知道网络深浅,先小步试探,成倍往上提速,碰到丢包或网络拥塞就立刻降速。这也是为什么TCP在丢包率高的链路上性能会直线下降——它宁可慢,也要保证数据的可靠性。TCP头部最小20字节,加上可选项可能到60字节,每一项都是为了可靠性和控制服务的,这是它“重”的根源。
1.2 UDP:无连接、轻量、能忍就忍
UDP的哲学跟TCP完全相反。它不建立连接,不确认对端是否存活,不管数据有没有到,也不管顺序乱不乱。UDP头部只有固定的8字节:源端口、目的端口、长度、校验和。正因为这么轻,UDP才能做到低延迟、低开销,适合那些对延迟敏感、能容忍少量丢包的场景。
典型的使用者是DNS查询、DHCP获取IP、RTP音视频流、SNMP网管、NTP时间同步。你去访问一个域名,第一件事就是发UDP的DNS查询,53端口,如果没响应就重试——这是UDP最典型的“尽力而为”。视频通话里偶尔花一下屏可以接受,但你要是等TCP把丢掉的帧重传回来,通话早就卡爆了。所以实时音视频领域,UDP是绝对的主流。
但UDP的“不可靠”不代表它没规则。UDP的数据报是有边界的,一次sendto对应一次recvfrom,不像TCP是字节流,要自己处理粘包拆包。这一点恰恰是很多新手在UDP通信里栽跟头的地方:你以为对方一次发送的数据你能分两次读出来,实际上UDP一次收的就是完整的一个数据报。
1.3 那“TCP比UDP更省流”这个说法到底对不对
这个热搜问题我隔一段时间就能看到一次,一句话回答:纯看流量,TCP一点都不省,反而更费。从单包开销看,TCP最少20字节头部,UDP只有8字节;从交互流程看,TCP每个数据包都可能触发ACK,还要维护窗口通告、快速重传,这些都是额外流量;从重传看,丢一个包TCP就要把整个包再发一遍。所以同样的应用数据量,TCP产生的总流量通常比UDP大不少。
但为什么很多人还是觉得“TCP更省”?因为在弱网环境下,UDP大量丢包导致接收端数据残缺,重发策略如果做不好,实际效果反而比TCP更差、更费流量。比如某些基于UDP自研协议的应用,收到完整报文的比例低,导致上层反复请求补包,流量反而爆炸。所以“谁更省流”要看场景,看网络质量,看应用是否自带了可靠性机制。在干净的内网、优质的宽带下,两者流量差没想象中的大;在公网弱网下,TCP因为有拥塞控制,反而更“懂节制”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 端口:从“找到机器”到“找到进程”
2.1 端口号的分层与分配机制
IP地址负责把数据包送到一台主机,端口号负责把这台主机上收到的数据,投递给对应的进程。你可以把IP想象成小区地址,端口就是单元门牌号。没有端口,数据进了小区也不知道找谁。
端口号是16位整数,范围0到65535。习惯上分三段:0-1023是知名端口,一般需要root权限才能绑定,比如HTTP的80、HTTPS的443、SSH的22、MySQL的3306;1024-49151是注册端口,软件厂商可以申请正式使用,比如PostgreSQL默认5432、Redis默认6379;49152-65535是动态端口,通常作为客户端临时端口池,内核从这个范围里给主动发起的连接分配源端口。
这里有个很经典的疑问:为什么TCP的80端口和UDP的80端口可以同时被不同的程序占用,不冲突?因为一个网络连接在传输层是靠“五元组”唯一定位的:源IP、源端口、目的IP、目的端口、协议类型。TCP和UDP是两个不同的协议,即使端口号一样,也属于两个独立的空间。所以一台服务器同时跑着TCP 80的Web服务和UDP 80的某种服务,完全不冲突。相反,同一个IP上两个程序都想绑定TCP 8080,那绝对会撞车,报“Address already in use”。
2.2 常用端口清单:做开发运维必须烂熟于心
这里我把工作里最常见的端口整理成一张表,照着查就行,不用背,但得有印象。特别是做中间件运维和微服务的人,端口冲突是最常见的故障源。
| 端口号 | 协议 | 常见服务 |
|---|---|---|
| 22 | TCP | SSH远程管理 |
| 53 | TCP/UDP | DNS域名解析 |
| 67/68 | UDP | DHCP |
| 80 | TCP | HTTP |
| 443 | TCP | HTTPS |
| 3306 | TCP | MySQL |
| 5432 | TCP | PostgreSQL |
| 6379 | TCP | Redis |
| 8080 | TCP | 常见HTTP备用端口/部分Java应用 |
| 9090 | TCP | Prometheus生态/部分管理面板 |
| 16000 | TCP | HBase Master RPC |
| 16020 | TCP | HBase RegionServer RPC |
| 16030 | TCP | HBase RegionServer Web UI |
| 2181 | TCP | ZooKeeper |
| 5000 | TCP | MLflow默认UI端口/部分开发服务器 |
| 1883 | TCP | MQTT默认端口 |
| 502 | TCP | Modbus TCP |
| 1935 | TCP | RTMP直播流 |
看到这里你应该明白,端口冲突是“多个不同领域技术栈同时上机器”时最常见的矛盾。比如又装Prometheus、又装某些Java管理端、又跑Docker容器的时候,9090和8080最容易打架。
2.3 Linux的9090端口:到底是哪个进程在用
很多人一查服务器发现9090端口被占用,就发懵。9090在多个生态里都有出场:最典型的是Prometheus Pushgateway,默认就监听9090;另外一些监控面板、Cloudera Manager的管理端口也是9090,部分Java中间件的调试口或管理API也爱用9090。所以它不是一个“某个软件的专属端口”,更像是一个“通用替补端口”。
判断它到底被谁占了,一条命令就够:
bash复制ss -tlnp | grep 9090
如果进程名还不明确,可以用lsof -i :9090看更详细的进程PID和启动用户。搞清楚占用方之后再决定是保留还是杀掉。千万不要一看到端口被占就kill -9,我以前见过有人把Prometheus监控进程直接杀了,结果整个集群的指标采集全停了好几天才被发现。
3. 端口冲突与配置:实操排障全记录
3.1 端口被占用:Linux和Windows的排查套路
Linux下最顺手的组合就是ss和lsof:
bash复制# 查看某个端口谁在监听
ss -tlnp | grep 8080
lsof -i :8080
# 查看所有监听端口
ss -tln
# 找到PID后看进程详情
ps -fp 进程PID
那个PID如果是你的程序残留,直接kill -9;如果是正当服务,就别硬碰硬,改你自己程序的监听端口更稳妥。Windows上用一条组合拳:
cmd复制netstat -ano | findstr 8080
tasklist | findstr 进程号
taskkill /F /PID 进程号
Windows这里有个容易踩的坑:netstat默认看不到PID是谁的,得配合tasklist再查一次,而且有些系统进程的端口被占,杀起来会提示权限不够,这时候要用管理员身份打开CMD。另外Windows从Win10开始有个奇怪的“端口排除范围”,明明端口没被监听,绑定却报错,我遇到过好几次,那是Hyper-V或WSL保留的动态端口范围,用netsh interface ipv4 show excludedportrange protocol=tcp查一下就能发现。
3.2 Docker端口冲突:见到“ports are not available”别慌
Docker启动容器的时候,最常见的一个报错是:
text复制Error response from daemon: Ports are not available: exposing port TCP 0.0.0.0:8080 -> 0.0.0.0:0: listen tcp 0.0.0.0:8080: bind: address already in use
这个报错的中文意思很直白:宿主机上8080端口已经被别的进程占了,Docker没办法把宿主机的8080映射给容器。解决办法分三步:
第一,确认是谁占了8080:
bash复制ss -tlnp | grep 8080
第二,要么杀掉占用进程,要么改容器映射端口。改映射最直接,比如把容器的8080映射到宿主机的18080:
bash复制docker run -p 18080:8080 你的镜像名
第三,检查是不是Docker自己之前的容器还在占用端口。有些容器停止后端口未必立即释放,可以docker ps -a看一下残留容器,或者重启Docker守护进程清理:
bash复制systemctl restart docker
这里有个经验:Docker的-p端口映射要谨慎使用0.0.0.0监听,尤其在云服务器上,等于把端口暴露到所有网卡,非常容易被扫到。能用127.0.0.1:8080:8080就尽量绑定到本机回环地址,减少外部扫描面。
3.3 SSH端口转发:把“不能直连”变成“能连”
SSH端口转发是排查网络问题时的一把瑞士军刀。典型场景是:生产环境数据库只允许跳板机访问,你在本地没法直连。这时候用本地转发:
bash复制ssh -L 3306:数据库内网IP:3306 user@跳板机
执行完上面命令之后,你在本地连127.0.0.1:3306,就等于连到了数据库的内网IP的3306。原理是SSH把本地端口收到的流量,加密通过跳板机转发到目标端口,对方看到的来源IP就是跳板机的IP,安全性和便利性兼顾。
反向转发的场景也常见:你有一台内网机器,外网访问不到,但它可以主动SSH连到一台公网服务器。这时在公网服务器上可以用远程转发,把公网服务器的某个端口流量转发到内网机器的端口:
bash复制ssh -R 9090:127.0.0.1:80 user@公网服务器
这样公网服务器上访问127.0.0.1:9090,流量就会被隧道送到内网机器上。这个技巧在临时应急排查、远程调试时特别实用,不用改防火墙、不用申请公网IP,非常方便。注意一点:SSH隧道默认是加密的,但不要把临时隧道当成长期生产方案,性能和稳定性都比不上正经的方案。
3.4 华为交换机端口镜像:抓包在设备侧怎么配
“端口”这个词在交换设备上又有一层含义:交换机的物理接口。端口镜像的意思是,把某一个接口的流量复制一份到另一个接口,方便接到抓包设备上分析。排查网络故障时经常要用到。华为设备的配置思路是:先定义观察口,再把业务口镜像过去。
text复制observe-port 1 interface GigabitEthernet0/0/1
interface GigabitEthernet0/0/2
port-mirroring to observe-port 1 both
上面这段的意思是:GE0/0/1作为观察口(也就是说,抓包设备接在GE0/0/1上),GE0/0/2作为被镜像口,方向both表示收发双向都复制。如果需要镜像多个接口,可以继续在别的接口下加同样的命令。抓完后记得把镜像配置去掉,不然大量复制流量会一直消耗交换机CPU和带宽。
3.5 宝塔端口20772报TLS重协商攻击告警怎么处理
有人用宝塔面板建站后,用安全扫描工具扫到20772端口(有时候是8443、443等自定义HTTPS端口),报“服务器支持TLS client-initiated重协商攻击(CVE-2011-1473)”。这个漏洞本质是TLS握手中的重协商机制可以被滥用,造成CPU资源耗尽。年代久远,但扫出来就是扫出来了。
处理思路不是去改端口号,因为漏洞在TLS实现层面,不在端口号。正确的做法是:升级OpenSSL到修复版本,或者让Nginx/Apache关闭客户端发起的重协商。以Nginx为例,可以在配置里确保SSL库版本足够新,并加入:
text复制ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
同时检查系统openssl版本,太低就升级。如果你用的是宝塔,直接在软件管理里升级对应版本的Nginx和OpenSSL插件,刷新面板版本一般也能修复。扫出来是好事,别慌,这种历史漏洞修复起来很简单,真正该警惕的是那种毫无征兆的高危端口在被扫描器记录。
4. 从热点协议看TCP/UDP在现实里怎么选型
4.1 Modbus RTU和Modbus TCP:工控领域绕不开的两种形态
Modbus是工业控制里资历最老的通信协议之一,它的两种常见形态经常被人搞混。Modbus RTU跑在串口上,基于RS-232/RS-485,报文格式是:从站地址+功能码+数据+CRC16校验。Modbus TCP跑在以太网上,传输层走TCP 502端口,报文里把RTU的地址和CRC都去掉了,换成7字节的MBAP头(事务处理标识、协议标识、长度、单元标识)。
两者最大的区别不是“数据格式差一点”,而是传输机制完全不同。RTU是串口半双工通信,一条总线上挂多个从站靠地址区分,必须排队问、排队答;TCP是点对点全双工通信,每个连接独立,不用考虑总线争用。做上位机开发时,如果现场设备走RTU,程序里就要自己处理超时重试和轮询队列;如果走Modbus TCP,一个连接可以像HTTP一样随时发起读写请求,省心很多。但要注意,Modbus TCP的“省心”是有前提的:TCP的握手时延在跨公网环境可能很高,而且PLC的TCP连接资源往往有限,并发连接一多就会出问题。
4.2 MQTT和RTMP:为什么“忠实可靠”都选了TCP
MQTT基于TCP 1883端口,RTMP基于TCP 1935端口,它们不约而同选择TCP,是因为这两个场景的核心诉求都是“尽量不丢消息”。MQTT在物联网里传遥测数据和指令,一条设备上下线消息丢了,可能就是一次联动失败;RTMP在直播推流里把视频帧推给CDN,TCP的重传机制反而保证了关键数据的完整性。很多人唱衰RTMP,说延迟高、该用WebRTC的UDP方案了,但实际在公网推流场景里,RTMP基于TCP的稳定性依然让它活得很久,尤其在弱网下,UDP推流丢一帧关键帧,花屏比卡顿更让观众抓狂。
4.3 ROS通信协议:到底是不是UDP
搜“机器人ROS分发协议是udp吗”的人特别多,答案要分两代看。ROS1的默认通信走的是TCPROS,节点之间的话题和服务通信默认是TCP。ROS2完全重构了通信栈,底层用DDS,而DDS的传输层可以配置多种:共享内存、TCP、UDP都可以。DDS的服务质量策略QoS里有一个Reliability参数,配成RELIABLE时倾向于TCP,配成BEST_EFFORT时更常走UDP——高频传感器数据(比如激光雷达点云)经常牺牲可靠性换实时性,就会选BEST_EFFORT。所以“ROS是不是UDP”这个问题,得先搞清楚你用的是ROS1还是ROS2,再看话题的QoS配置,不能一刀切。
4.4 那些“XX协议”和TCP/UDP到底是什么关系
热搜词里有SPI、I2C、CAN、MIPI、ARINC818、AXI、Ymodem,很多新手一看到一堆协议就头大,觉得“怎么这么多通信协议,TCP/UDP排不上号了”。其实它们是不同层级、不同场景的东西,跟“TCP/UDP”并不是竞争关系:
| 协议 | 所在层级 | 典型场景 |
|---|---|---|
| TCP/UDP | 传输层 | 跨主机网络通信 |
| HTTP/HTTPS、MQTT、RTMP、Modbus | 应用层 | Web、物联网、直播、工业控制 |
| SPI、I2C | 板级总线 | MCU与传感器/Flash等板内器件通信 |
| CAN | 现场总线 | 汽车、工业设备控制器局域网 |
| MIPI | 接口协议 | 手机摄像头、显示屏的连接 |
| ARINC818 | 航电数据 | 航空电子视频传输 |
| AXI | 片上总线 | FPGA/SoC内部模块互联 |
| Ymodem | 应用层协议 | 串口文件传输 |
SPI、I2C不是“网络协议”,它们解决的是同一块电路板上芯片之间的数据传输问题,跑在GPIO和时钟线上;CAN虽然也是总线,但主要覆盖一个局部控制网络内多节点通信,不是IP网络;AXI是芯片内部总线,跟TCP/UDP彻底不搭边;Ymodem则是典型的上位机与单片机之间通过串口传固件的协议。把这些分清楚之后,你再看“TCP/UDP协议与端口”这个话题,就不容易跟SPI、
