有朋友问我:Linux网络到底该怎么学?是不是把常用命令背熟就够了?我说,命令只是手,协议栈才是骨架。今天这篇就把Linux网络基础中最容易绕晕的部分——协议与网络传输基本原理——摊开讲一遍,从数据包怎么封装、路由怎么转发,到TCP三次握手四次挥手、tcpdump和ss怎么用,一次说透。适合刚从Windows切到Linux的运维,也适合准备Linux面试的开发者。
1. 先说清楚:Linux网络基础到底学什么
1.1 协议栈是内核里的一套“翻译规则”
很多人一开始就把“协议”想复杂了。协议这东西,说白了就是双方约定好的沟通格式。就像两个快递公司之间要交接包裹,面单上写清楚从哪来、到哪去、多重、什么品类,双方按照同一个标准填和读,包裹就不会乱。
网络里的协议也一样。TCP/IP协议栈就是一套分层约定的规则,Linux内核里把这套规则实现成了实实在在的代码。你在Linux上跑任何网络服务,无论是Nginx还是sshd,发出的请求都会从应用层往下走,经过内核的TCP/UDP模块、IP模块、ARP模块,最后从网卡发出去。内核会帮你把数据切成合适的大小、加上对应的头部,对端收到后按同样的规则剥掉头部,把数据交给对端的应用。
所以Linux网络基础学的不是某个工具,而是“内核如何按照协议规则收发数据”。理解了这条线,后面看防火墙、看路由、看抓包、看性能问题,都是一通百通。
1.2 四层模型和七层模型,Linux更看中哪套
教科书上最爱讲OSI七层模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。这个模型适合教学,把每一层的职责分得非常细,但Linux实际跑起来并不是严格按七层实现的。内核里更贴合的是TCP/IP四层模型:链路层、网络层、传输层、应用层。
为什么这么说?因为代码里根本不存在独立的“会话层”和“表示层”。你写socket程序的时候,会话管理是内核和业务逻辑一起干的,数据加密、压缩这些“表示层”的事,往往直接放在应用层里,比如HTTPS就是在HTTP外面套了TLS,本质上还是应用层自己处理。
学习的时候我建议以四层模型为主线,先把每一层的核心职责记牢:链路层管MAC地址和帧,网络层管IP地址和路由,传输层管端口和可靠传输,应用层管具体业务格式。你抓包时看到的TCP、UDP、IP、ARP、ICMP,全部是在下面几层。热搜词里的“7层协议”可以当作补充知识,但别让七层的分层把你绕晕,真正排障时你盯着四层模型去分析就够了。
2. 数据在网络上是怎么“走”出去的:封装与传输原理
2.1 从应用层到网卡的封装过程
很多人能背出“封装”两个字,但没亲眼看过一个数据包长什么样。我帮你拆一遍。
假设你在Linux上用curl请求一个HTTP页面,比如curl http://example.com。应用层先把HTTP请求报文交给内核,内核看到目标端口是80,就把数据交给TCP模块。TCP模块干的第一件事是给这堆数据编上序号,算出校验和,加上TCP头部(源端口、目的端口、序号、确认号、标志位等),形成一个TCP段。
接着这个段交给IP模块。IP模块查路由表,确定下一跳该往哪走,然后加上IP头部:源IP、目的IP、协议号(TCP是6,UDP是17)、TTL等等,形成一个IP包。如果数据包大于链路层的MTU(通常以太网是1500字节),IP模块还可能把包分片。
再往下,链路层要把它封装成以太网帧,加上目标MAC地址、源MAC地址、类型字段,然后交给网卡驱动程序,由网卡转成电信号发出去。这一层层套起来的结构,跟俄罗斯套娃一模一样:
应用数据 + TCP头部 + IP头部 + 以太网帧头部 + 数据 + 帧尾所以在抓包工具里看到的每一个包,都是带着好几种“标签”的完整帧。这也是为什么tcpdump抓包输出里会同时出现MAC地址、IP地址、端口号——它们都在同一个包的不同区域里。
2.2 解封装:收包方向走一遍
接收方向完全反过来。网卡收到一个以太网帧后,先校验帧尾的FCS(帧校验序列),通过后剥掉以太网头部,看到里面的协议号是0x0800(IPv4),于是把IP包交给IP模块。IP模块检查目的IP是不是本机地址,是的话剥掉IP头部,再看协议号是6还是17,把里面的数据交给TCP或UDP模块。TCP模块根据目的端口找到正在监听的socket,把数据放入接收缓冲区,应用调用read()/recv()就能取到内容。
这里有个容易忽略的细节:数据包在每一层都要校验一次。以太网有CRC校验,IP头有校验和,TCP段也有校验和。任何一个校验失败,包都会被直接丢弃,不向上层传递。所以如果你看到一个网络“通但慢”的现象,有可能是链路噪声导致校验失败重传,而不是带宽不够。
2.3 IP地址、端口、协议号:三要素别搞混
要定位一个网络连接,光知道IP地址不够。我用一个比喻:IP地址是“楼址”,端口是“房间号”,协议号是“用什么语言沟通”。你找到了楼,还得找到具体房间,且双方都得会同一门语言才能聊起来。
一个完整的TCP连接是由四元组唯一确定的:
- 源IP
- 源端口
- 目的IP
- 目的端口
比如你用浏览器访问https://192.168.1.10:443,源IP是你本机的192.168.1.20,源端口是随机分配的一个高位端口比如52341,目的IP是192.168.1.10,目的端口是443。这个四元组里任何一个变了,都可能变成一条完全不同的连接。
用ss -tan在Linux上查看时,输出里每一行都包含类似192.168.1.20:52341 192.168.1.10:443的地址对,左边是本地地址,右边是对端地址。看到这个格式,你就知道内核记录连接时就是靠四元组区分的。这也是为什么端口资源不够时,系统会报“Cannot assign requested address”——因为可用的四元组组合被占满了。
3. Linux网络协议栈里的关键机制
3.1 路由表:数据包出门前必须问路
数据包从IP层出发前,内核一定要先查路由表:这个包该交给哪块网卡、下一跳是谁。Linux上用ip route查看路由表,典型输出长这样:
default via 192.168.1.1 dev eth0 192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.20第一条是默认路由:所有没匹配到更精确条目的包,都发给网关192.168.1.1。第二条是直连网段路由:目的IP在192.168.1.0/24这个网段内,直接通过eth0发出去,不需要网关。
路由匹配的核心原则是“最长前缀匹配”。比如同时存在0.0.0.0/0和192.168.1.0/24,要访问192.168.1.5时,/24的掩码更长,所以优先走直连路由;要访问8.8.8.8时,只有默认路由能匹配,所以走网关。
实际排障中,我遇到最多的情况是:Linux主机上开了多块网卡,默认路由指向了错误的接口,导致跨网段访问时通时不通。这时候用ip route replace default via 目标网关 dev 对应网卡改回来就行。另外,Linux做软路由或NAT时,需要确认net.ipv4.ip_forward是否已打开:
sysctl -w net.ipv4.ip_forward=1忘了开这个,数据包到了Linux却转发不出去,表现就是“隔壁机器能ping通Linux,但ping不通Linux后面的网络”。
3.2 TCP的状态变迁:三次握手和四次挥手
TCP可以算整个Linux网络面试里出现频率最高的考点,没有之一。我建议你把状态迁移完整过一遍。
三次握手的过程是:
- 客户端发SYN,Seq设为随机数x,状态变成SYN_SENT。
- 服务端收到SYN,回复SYN+ACK,Seq设为y,Ack为x+1,状态变成SYN_RCVD。
- 客户端收到SYN+ACK,回复ACK,Seq为x+1,Ack为y+1,连接建立,状态变成ESTABLISHED。
为什么一定要三次握手?因为TCP要解决“确认双方收发能力都正常”的问题。两次握手的话,服务端没法确认客户端是否收到自己的SYN+ACK;如果丢了,服务端以为建好了,客户端却不知情,就会白白等待。
四次挥手过程则是因为TCP连接是双向的,每一方向都需要单独关闭:
- 主动方发FIN,状态变成FIN_WAIT_1。
- 被动方回ACK,进入CLOSE_WAIT。
- 被动方也发FIN,进入LAST_ACK。
- 主动方回ACK,进入TIME_WAIT,等待2MSL后再关闭。
排障时,最常被问到的就是大量TIME_WAIT。这个状态是主动关闭方在等旧连接的延迟报文彻底消失,防止新连接收到脏数据。短连接越频繁,TIME_WAIT越多。调整TCP参数可以缓解,但如果对端是NAT环境,要非常小心,别随便开tcp_tw_recycle,它会因为时间戳失效导致连接被对端丢弃,这个坑我踩过。
3.3 内核参数与网络性能的微妙关系
Linux网络性能问题,七成最后都能落到内核参数上。以下几个是高频调整项:
net.core.somaxconn:全连接队列长度,Nginx、Redis这些服务端都在用,队列满了客户端就会连接被重置。net.ipv4.tcp_syncookies:SYN攻击时挽救半连接队列的开关,建议开启。net.ipv4.tcp_fin_timeout:FIN_WAIT_2的等待时长,默认60秒,短连接多可以适当调低。net.ipv4.ip_local_port_range:本机主动发起连接时可用的临时端口范围,范围太小会导致高并发下端口不够用。net.core.rmem_max和net.core.wmem_max:socket接收/发送缓冲区上限。
调参数前一定要知道自己要解决什么。比如客户端大量出现“connect失败”,先看半连接队列和全连接队列有没有溢出:
ss -lnp | grep 端口 # 或者用 ss -lnt | grep -E 'SYN|LISTEN'如果队列溢出,内核日志或netstat -s里会看到对应计数器在涨。只有确认了瓶颈,再去调参数才是有效的,否则就是瞎调。
4. 常用Linux网络命令与排障实战
4.1 ping、telnet/nc、tcpdump的定位不一样
排障第一步要分清工具的使用范围,不同工具检测的是不同层,不能用错。
ping用的是ICMP协议,工作在IP层之上,主要看主机是否可达、链路时延是多少。但它只证明“IP层通”,证明不了“端口通”“服务正常”。telnet IP 端口或nc -vz IP 端口用来测TCP端口是否可连。能建成TCP连接,基本说明服务端在监听且防火墙放行。curl是应用层测试工具,能直接测HTTP接口的响应码、响应时间、TLS握手等情况。tcpdump是抓包工具,能看到协议栈里真实发生的交互,适合分析三次握手、重传、丢包、异常响应等复杂问题。
我见过很多人一上来就ping,结果ping不通就断言网络断了。实际上有些服务器为了安全禁止ICMP回显,ping不通但TCP 443端口完全正常。所以正确姿势是先确认目的IP是否可达,再测端口,再抓包看协议交互。
4.2 快速看懂ss、netstat的输出
老运维习惯用netstat,新一点的环境里ss更快、输出更全。两者核心信息一致,我以ss -tan为例:
State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 0.0.0.0:22 0.0.0.0:* ESTAB 0 40 192.168.1.20:45210 192.168.1.15:80LISTEN:服务端正在监听的端口,0.0.0.0:22表示所有IPv4地址都监听22端口。ESTAB:一条已建立的连接,注意本地端口是45210,说明是本机发起的连出请求。Recv-Q和Send-Q:接收和发送队列积压字节数。如果Recv-Q长期不降,说明应用没及时读数据,可能卡在业务逻辑上。
要看进程名和PID,加-p参数,但需要root权限。排查端口被谁占用时用:
ss -tlnp | grep :80输出里会直接显示占用端口的进程名和PID,比lsof -i:80在有些场景下更直观。
4.3 一个典型连接故障的排查过程
我举一个真实排障例子,帮你把命令串起来用。
某天同事反馈,应用服务器访问数据库超时,但服务器本身能ssh登录。我先在应用服务器上ping 数据库IP,通了,延迟1ms,说明IP层和链路层没问题。接着测端口:
telnet 数据库IP 3306结果卡住不返回,说明TCP连接建不起来。可能是防火墙丢SYN包,也可能是数据库没在监听。到数据库服务器上看监听:
ss -tlnp | grep 3306结果3306确实在LISTEN。于是怀疑是中间防火墙或安全组拦截。在应用服务器上抓包:
tcpdump -i eth0 tcp port 3306 -n -c 100抓包发现SYN包发出后,没有收到SYN+ACK,也没有RST。这说明包在某个环节被静默丢弃了,最典型的就是防火墙DROP策略。去检查iptables:
iptables -L -n | grep 3306果然有一条DROP规则命中。联系网络管理调整安全策略后,连接立刻恢复。这个案例里,如果一开始只ping,只会得到“网络通”的结论,根本定位不到防火墙,后面的tcpdump和iptables检查才是关键。
5. 高频面试与实操陷阱整理
5.1 那些容易答错的概念
面试Linux网络基础时,有几个经典概念最容易踩坑。
第一个是“TCP粘包”。很多人以为粘包是TCP协议的问题,其实TCP是字节流协议,它只管把字节按顺序可靠地送到对端,不替应用划分消息边界。粘包问题本质上是你应用层没定好协议格式,接收方无法判断一条消息到哪结束。解决办法是应用层自定义分隔符或固定长度头。
第二个是“MTU和MSS的区别”。MTU是IP层能承载的最大包大小,通常以太网是1500字节。MSS是TCP数据段的最大长度,通常是MTU减去IP头部和TCP头部,差不多1460字节。TCP握手时双方会协商MSS,避免IP分片。分片会导致性能下降和丢包敏感,所以TCP一般都会尽量协商一个合适的段大小。
第三个是“半连接和全连接队列”。TCP三次握手时,服务端收到SYN后,连接先进入半连接队列,等ACK后再移入全连接队列,应用accept()从全连接队列拿连接。两个队列都有长度上限,满了就会表现出连接建立缓慢、超时甚至被RST。很多你调了net.core.somaxconn却不起作用的情况,是因为应用层自己的listen backlog没同步调大。
第四个是“TCP是可靠的,但UDP不一定不可靠”。UDP虽然不保证送达,但可以基于UDP实现可靠传输,比如QUIC就是在UDP之上实现了类似TCP的可靠性、拥塞控制,只是把控制逻辑搬到了用户态。面试时如果只说“UDP不可靠”就太浅了,能说出这个层次才算真正理解。
5.2 常见问题速查表
平时排查问题,最快的方式是按“现象 -> 可能原因 -> 排查命令”对照着来。我整理了一张速查表,覆盖面比较广。
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| ping不通 | 物理链路、IP地址配置、路由缺失、对端禁ICMP | ip addr、ip route、ping -I 源IP 目标IP |
| ping通但telnet端口不通 | 服务未监听、防火墙DROP | ss -tlnp、telnet IP 端口、tcpdump |
| 连接直接拒绝 | 服务未启动、防火墙REJECT、端口被占用 | ss -tlnp、iptables -L -n |
| 连接超时 | 防火墙丢包、路由黑洞、对端负载过高 | tcpdump看SYN是否发出、是否收到SYN+ACK |
| 大量TIME_WAIT | 短连接太多 | ss -tan state time-wait,考虑连接复用 |
| 大量SYN_RECV | 半连接队列满、SYN攻击 | netstat -s、调大队列、开syncookie |
| 大量CLOSE_WAIT | 服务端应用没关socket | 检查应用代码,处理连接释放 |
| 服务能连但响应很慢 | DNS解析慢、TLS握手慢、后端处理慢 | curl -w观察各阶段耗时 |
| 丢包率很高 | 网卡队列满、链路噪声、驱动问题 | ethtool -S、ifconfig看drop计数 |
| 无法主动发起大量连接 | 本地端口范围太小 | cat /proc/sys/net/ipv4/ip_local_port_range |
这张表没法覆盖所有网络问题,但能帮你快速建立一个排查入口。遇到一时定位不了的情况,记住一个原则:从底层往上层一层层试。链路层、IP层、TCP层、应用层,哪一层先断就在哪层停下。
5.3 想深入学习Linux网络,下一步怎么看
纸上谈兵容易,真正能把网络基础吃透,我的建议是两件事。第一件,学用tcpdump和wireshark。开一个抓包窗口,然后做一次ssh登录、一次curl请求、一次TCP三次握手,看每个包的细节,比看十遍理论都有用。第二件,自己用socket写一个最小的echo服务端和客户端,在Linux上跑通,然后故意制造故障,比如把listen队列改小、把对端延迟调大,观察程序表现。这些实操经验才是你面试和排障时真正能拿出手的东西。
我个人这几年最深的体会是:网络问题不像程序bug,它不会给你一个清晰的报错栈,而是表现为“慢”“卡”“时断时续”。这时候唯一的敲门砖就是老老实实分层排查,从物理链路到DNS再到应用,一条条排除,最终总会在某一层找到那个不起眼的丢包点或配置错误。多抓几次包、多踩几个坑之后,你对Linux网络基础的理解就会从“背概念”变成“看得见、摸得着”。