ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

计算机网络概述:用分层模型理解封装、寻址与排障

计算机网络概述:用分层模型理解封装、寻址与排障

有人问我,学计算机网络概述这一章到底有什么用,反正后面讲TCP、讲IP的时候还得重新学一遍。我当时没直接回答,而是让他打开浏览器随便输个网址,然后在按下回车之后的三秒钟里,把屏幕上发生的事按顺序讲一遍。他讲了DNS、讲了TCP握手、讲了HTTP,卡在"数据怎么从我的电脑跑到服务器上"这一步就说不下去了。这恰恰是概述这一章真正要解决的问题:它不教你任何一条协议的细节,它教的是一张地图,告诉你数据在这个系统里要经过哪些关卡、每个关卡由谁负责、关卡之间靠什么衔接。地图没画清楚,后面学再多协议也只是背名词。

这篇东西面向的是刚接触计算机网络、或者学过一遍但脑子里还是散装知识点的朋友。我会从一次真实的网页加载出发,把分层模型、封装解封装、IP与端口、TCP与UDP这些概述阶段的核心概念串成一条线,中间穿插我自己踩过的坑和几段可以直接抄下来用的命令。读完你应该能做到:看到一个网络故障现象,先判断它大概发生在哪一层,而不是上来就重启路由器。

1. 打开一个网页的三十毫秒里,网络概述里的概念全都上场了

1.1 从地址栏回车到首字节到达的时间线

很多教材讲概述喜欢从"计算机网络是连接多台计算机的系统"这种定义开始,讲完你依然不知道它长什么样。我习惯反过来,先看一次真实的访问过程,把里面出现的每个角色都标注出来,概念自然就落地了。

你在地址栏敲下一个域名并回车,浏览器做的第一件事不是发数据,而是查这个域名对应哪个IP。它先翻自己的缓存,再问操作系统,操作系统再看本机的hosts文件和DNS缓存,都没有才往外发DNS查询。这一步对应的是应用层的活儿,只不过DNS本身也是一个应用层协议,它跑在UDP的53端口上——注意这里已经出现"分层"的影子了,一个应用层的东西要靠传输层来送。

拿到IP之后,浏览器要和对方建立连接。如果是HTTP,通常是TCP,需要三次握手。握手这个过程里,你的机器要知道对方的IP、要知道走哪个网卡出去、如果对方不在同一个网段还要知道网关是谁。判断"在不在同一网段"靠的是子网掩码做与运算,这个动作发生在网络层。而真正把帧发到下一个设备的网卡上,靠的是MAC地址,这属于数据链路层。

连接建好,浏览器把HTTP请求报文交给操作系统,报文一层层加上头部,最后变成电信号或者光信号从网卡出去。对方的服务器收到之后一层层剥开,处理完再原路返回。整个过程里,你的机器可能还经过了家里的路由器、运营商的多台设备、机房的负载均衡器,每一跳都在做转发判断。

这条时间线上,域名解析、连接建立、数据封装、路由转发、解封装、响应返回,六个动作分别落在不同的层上。概述这一章要你记住的,就是这套分工。

1.2 为什么"概述"这门课值得慢下来啃

我见过太多人把概述当成序言,翻两页就跳去学TCP三次握手,结果学到拥塞控制就卡住了,因为他不知道TCP的报文段最终要装进IP数据报,IP数据报又要装进以太网帧,而以太网帧有1500字节的上限,这个上限反过来约束了TCP每次能发多少数据。这条因果链在概述里其实已经埋好了,只是当时没人在意。

另一个常见的问题是概念混淆。比如有人会问"IP地址和MAC地址有什么区别",如果他脑子里有分层这张图,答案几乎是自动浮现的:IP是网络层的逻辑地址,负责端到端的寻址,可以变;MAC是链路层的物理地址,负责同一段链路上的下一跳投递,出厂就定了。两者不在一个层面上,谈不上谁替代谁。

所以我的建议是,概述这一章不要追求速度,要追求建立索引。你不需要记住OSI七层每一层的完整定义,但你需要知道遇到一个现象时该往哪一层去找。这个能力在后面排查问题时价值极高——我处理过的网络问题里,一多半是有人在应用层的问题上反复折腾网络层配置。

提示:学习概述阶段,与其背"物理层传输比特流"这种定义,不如给自己提一个问题——如果网线坏了,现象是什么?如果IP配错了,现象是什么?如果端口没开,现象又是什么?把现象和层对应起来,比背定义有用得多。

2. 分层不是画着好看:OSI七层与TCP/IP四层到底在解决什么麻烦

2.1 分层的真实动机是"让改动不外溢"

教科书上讲分层的好处,通常列三条:各层独立、灵活性好、易于实现和维护。这些话没错,但太抽象,理解不了为什么非得这么设计。

换个说法。假设没有分层,你要实现一个网页访问功能,就得自己处理网卡驱动、自己处理信号编码、自己处理路由选择、自己处理丢包重传、自己处理字符编码。这些东西里,网卡型号换了你要改代码,线路从铜缆换成光纤你要改代码,中间经过的某台设备不支持某个特性你还要改代码。一个功能牵动全身,根本没法维护。

分层就是把这一大坨切成几段,每段只对上下相邻的层负责,并且约定好接口。网卡换了,只要新网卡还符合链路层的接口约定,上面的网络层完全不用动。传输介质从双绞线换成无线,物理层和数据链路层的实现换了,IP层照样工作。这就是"改动不外溢"。

我做项目时体会特别深的一点是:分层带来的其实是责任边界。当一个问题出现,你先判断它归哪一层管,然后只在这个层的范围内排查,不必把所有配置翻一遍。这个思维方式比任何具体协议都值钱。

2.2 七层没死,四层在用:两套模型的对照

OSI七层模型和TCP/IP四层模型的关系,是概述阶段绕不开的一个点。很多人记不住对应关系,其实只要理解两套模型的出发点不同就通了。OSI是一个理论参考模型,先设计框架再填协议,追求完备;TCP/IP是先有实现后有模型,是从实际运行的协议族里归纳出来的。

OSI 七层TCP/IP 四层这一层实际在忙什么典型代表
应用层应用层面向具体业务的数据交换HTTP、DNS、SMTP
表示层应用层数据格式、编码、压缩、加密字符编码、序列化格式
会话层应用层会话的建立、保持与同步会话管理机制
传输层传输层端到端的可靠或高效传输TCP、UDP
网络层网际层跨网络的路由与寻址IP、ICMP
数据链路层网络接口层同一链路上的帧传输与差错检测以太网、PPP
物理层网络接口层比特流的电/光/无线信号传输双绞线、光纤、无线电

表里能看出来,TCP/IP把OSI的上三层合并成了应用层,把下两层合并成了网络接口层。合并的原因很实在:会话层和表示层在实际协议里很少独立存在,它们的职责往往被具体的应用协议顺手做掉了;物理层和数据链路层在网卡驱动和硬件里本来就是一起实现的,硬拆开意义不大。

那为什么还要学OSI七层?因为它拆得细,用来做问题定位特别顺手。比如"页面显示乱码",用OSI一看就知道是表示层的事,跟网络传输没关系;"连接老是断",可能是会话层或者传输层的事。这套细分框架当诊断工具用,比当协议规范用有价值得多。

2.3 分层带来的代价也要认

分层不是白送的,它有成本。最直接的成本是头部开销。你的HTTP请求可能只有几十字节的正文,但加上TCP头20字节、IP头20字节、以太网帧头14字节加尾部4字节,实际在线上跑的可能是原数据的两三倍。对于小包密集的场景,这个开销不小。

第二个成本是层与层之间的信息不透明。TCP不知道下面走的是以太网还是无线,它只能按最保守的假设来设计;应用层也不知道下面的路径质量如何,只能靠超时重试来兜底。这种"各扫门前雪"的设计带来了通用性,也带来了优化上的天花板。后来出现的一些跨层优化方案,本质上都是在突破这个边界,但代价是牺牲了通用性。

第三个成本是抽象泄漏。分层理论上应该让你不用关心下层,但现实中下层的问题总会冒上来。最典型的就是MTU:应用层写了个大报文,传输层老老实实分段,结果中间某条链路MTU更小,包被丢弃,现象是"小文件能传,大文件卡住"。这时候你不得不往下看,去理解分片和路径MTU发现。

所以我的态度是:分层是一张有用的地图,但别把它当法律。真出问题时,该往下钻就得往下钻。

3. 数据从你的键盘爬到网线里:一次完整封装的拆解

3.1 逐层加头:应用数据如何变成以太网帧

封装是概述里最核心的一个动作,理解了它,后面所有协议的位置你都清楚了。我用一个具体例子走一遍。

假设你的程序要向对方发一段内容,长度是100字节。这个过程从应用层开始:

应用层构造出业务数据,比如一个HTTP请求,假设整体100字节。这一层的数据单元一般就叫"报文"。

交给传输层,如果是TCP,它在前面加上TCP头。TCP头最少20字节,里面装着源端口、目的端口、序号、确认号、窗口大小、标志位这些信息。加上之后变成120字节,这个单元叫报文段。

交给网络层,加上IP头。IPv4头最少20字节,装着源IP、目的IP、协议号(标明上层是TCP还是UDP)、TTL、校验和等。变140字节,这个单元叫数据报或者分组。

交给数据链路层,加上以太网帧头。帧头14字节,包含目的MAC、源MAC和类型字段;帧尾4字节,是帧校验序列。变158字节,这个单元叫帧。

最后交给物理层,把这158字节变成比特流,按线路的编码规则发出去。

用一段示意性的结构表示:

[以太网头 14B][IP头 20B][TCP头 20B][应用数据 100B][帧尾 4B]

每一层只关心自己加的那个头,也只读取对方同层加的头。网卡不关心里面是不是HTTP,路由器不关心里面是不是TCP,这就是分层的实际体现。

有个细节值得注意:TCP头里有源端口和目的端口,IP头里有源IP和目的IP,这四样东西合起来(源IP、源端口、目的IP、目的端口)唯一标识一条连接,通常叫四元组。这也是为什么同一台机器能同时开多个浏览器标签访问同一个网站——目的IP和端口相同,但源端口不同,四元组不同,连接就能区分开。

3.2 MTU与MSS:那个1500是怎么卡住一切的

以太网帧的数据部分上限是1500字节,这个值叫MTU(最大传输单元)。这个数字不是随便定的,早期以太网定这个值是在传输效率和冲突检测之间取的折中,后来就成了事实标准。

1500字节要装下IP头和TCP头,所以TCP能携带的数据最多是 1500 - 20 - 20 = 1460 字节。这个值叫MSS(最大报文段长度)。注意MSS不是协商出来的固定值,双方在握手时会各自通告自己能接受的MSS,实际传输取较小的那个。

这个计算在概述阶段就应该弄明白,因为后面很多现象都由它引起。比如你发一个3000字节的报文,TCP会拆成两个报文段,一个1460,一个1540?不对,第二个也得在1460以内,所以是1460加1540这种情况不会出现,实际是拆成1460和1540以内的合理切分,具体取决于实现,但每段都不会超过MSS。

再比如路径上某一段链路的MTU只有1400,而你按1500发的包,这个包到了那里就会被丢弃,或者被分片。IPv4允许中间设备分片,IPv6不允许,只能靠源端做路径MTU发现。现象就是"能ping通但传不了大数据",或者"小请求正常,上传大文件卡死"。这种问题我第一次遇到时排查了很久,最后抓包看到"需要分片"的ICMP消息才反应过来。

注意:如果你的服务出现"小请求正常、大响应超时"的规律性现象,优先怀疑MTU问题,而不是先去查带宽或者代码逻辑。

3.3 收包方向:解封装与层层校验

收包是发包的逆过程,但不是简单倒放,因为每一层都会做校验和判断。

物理层收到信号,还原成比特流交给链路层。链路层检查帧尾的校验序列,如果对不上直接丢弃,不会往上报。这一步能挡住线路噪声造成的错误。校验通过后看帧头的类型字段,知道里面是IP,就剥掉帧头帧尾交给网络层。

网络层检查IP头的校验和,看目的IP是不是自己。如果不是自己且本机开启了转发,就往下一跳送;如果是自己,就看协议号,TCP就交给传输层。这里还会做TTL减一,TTL减到0就丢弃并回一个ICMP超时消息——traceroute就是靠这个机制工作的。

传输层根据目的端口找到对应的进程,检查序号,处理乱序和重传。如果是TCP还要回确认。校验通过后把数据交给对应的应用。

应用层拿到数据,按自己的协议解析。

这中间每一层的校验范围是不一样的。链路层校验的是整个帧,网络层只校验IP头(IPv4如此,IPv6干脆取消了头部校验和),传输层校验头加数据。这种分工也反映了各层的关注点:链路层保证这一段链路没传错,网络层保证头部没被改坏,传输层保证端到端的数据完整。

4. 把抽象摁到地上:五个命令行工具和一次抓包实录

4.1 定位问题的工具顺序:从域名到端口

概述讲完,最容易出现的状态是"概念都懂,一遇到问题就懵"。我的经验是建立一套固定的排查顺序,从上层往下走,每一步都有对应的命令。

第一步,确认域名能不能解析。用nslookup或者dig查一下,看返回的IP是不是你期望的。如果解析不出来,后面所有事都不用谈。这一步能排掉一大类"网站打不开"的问题——很多时候根本不是网络不通,是DNS配错了或者缓存了旧记录。

dig example.com +short nslookup example.com

第二步,确认到目标IP的连通性。ping发的是ICMP回显请求,能通说明网络层是通的。但这里有个大坑:ping不通不代表服务不可用。很多服务器为了安全会禁掉ICMP,但80端口照样开着。反过来,ping通了也不代表端口通,这是新手最容易搞混的。

ping -c 4 203.0.113.10

第三步,确认端口通不通。telnet是个老工具但很直观,或者用nc。这一层通了说明TCP握手能完成,应用层至少有个进程在监听。

nc -zv 203.0.113.10 80

第四步,看本机的连接状态。ss或者netstat能看到当前有哪些连接、处于什么状态。排查"连接数打满"、"大量TIME_WAIT"这类问题全靠它。

ss -tanp | head -30 ss -s

第五步,看路由。traceroute或者 Windows 下的tracert,能看到数据包经过的每一跳。如果在某一跳之后全部超时,问题大概率出在那里。

traceroute 203.0.113.10

这五步走下来,绝大多数"连不上"的问题都能定位到具体层面。关键是顺序不能乱,从域名到IP到端口到连接状态,正好对应着从应用层往下走的路径。

4.2 抓一次自己的包:tcpdump实操与观察点

命令行工具能看到结果,但看不到过程。想真正理解封装,抓一次包是最快的途径。

在Linux上,tcpdump基本是标配。假设我要抓一次对本机某端口的访问,可以这样:

sudo tcpdump -i any -nn -s 0 -w capture.pcap port 8080

参数的含义:-i any监听所有网卡,-nn不做域名和端口名的解析(避免额外的DNS查询干扰),-s 0抓完整包不截断,-w写入文件方便后面用图形工具看。抓够了自己按 Ctrl+C 停掉。

如果只是想快速看一眼,不加-w,直接输出到终端也行:

sudo tcpdump -i any -nn -A port 8080

-A会把包内容按ASCII打印出来,能看到HTTP报文长什么样。我第一次看到自己的HTTP请求头和响应头就这么明文躺在终端里时,对"应用层数据被封装进传输层"这句话才算有了实感。

抓下来的包可以用图形化工具打开分析,也可以用tcpdump -r回读。回读时加上-X能以十六进制加ASCII的形式显示,方便看头部字段。

tcpdump -r capture.pcap -nn -X | head -60

4.3 抓包时该盯哪几个字段

打开一个包,字段几十个,新手容易看花眼。我给自己的习惯是先看四个地方:

第一看帧头的类型字段,判断上层是IPv4还是IPv6,还是ARP。如果抓到的是ARP包,说明此时还在做地址解析,连接根本没开始。

第二看IP头的TTL和协议号。TTL能大致推断包走了多远,协议号能确认上层是TCP(6)还是UDP(17)还是ICMP(1)。

第三看TCP的端口和标志位。三次握手的过程在抓包里非常清晰:第一个包是SYN,第二个是SYN加ACK,第三个是ACK。如果只看到SYN没有回应,说明对方没开端口或者被拦了;如果SYN发出后收到RST,说明端口没在监听。

第四看应用层内容。HTTP是明文的,直接能读。如果是HTTPS,看到的就是加密后的字节,这时候只能看握手阶段的协商信息。

还有一个观察点经常被忽略:时间戳。两个包之间的时间差能反映很多问题。比如握手第一个包发出去到收到回应花了800毫秒,说明网络往返延迟不低;如果重传的间隔是明显的1秒、2秒、4秒递增,那就是典型的超时重传。

提示:抓包时先设过滤条件再开始,不要抓全量。一次抓几万个包,回读的时候光筛选就够你喝一壶。常用的过滤写法直接在命令里加,比如port 80、host 203.0.113.10、tcp[tcpflags] & tcp-syn != 0。

5. 新手最容易拧巴的五组概念

5.1 IP与MAC:一个是终点站,一个是下一跳

这两个地址经常被混为一谈,因为都叫"地址",都是数字串。区别在于它们服务的范围不一样。

IP地址是逻辑地址,它的作用是在整个互联网范围内标识一台主机,让数据能从源头找到终点。它是可以变的,换网络环境就会变,也可以手工配置。

MAC地址是物理地址,烧在网卡里,理论上全球唯一,作用是在同一段链路上把帧从一台设备送到另一台设备。它不关心最终目的地是哪里,只关心"下一跳交给谁"。

一个类比:IP地址像收件地址,写的是最终要送到哪个城市哪条街;MAC地址像每一段运输的交接单,快递从仓库到分拣中心、从分拣中心到网点、从网点到快递员,每一次交接都换一个人,但收件地址始终不变。

这也解释了为什么跨网段通信时,目的MAC不是最终服务器的MAC,而是网关的MAC。你的机器判断出目标IP不在本网段,就把包发给网关,帧头的目的MAC填网关的MAC。到了网关,它再重新封装,换一个新的目的MAC指向下一跳。整个过程中IP头基本不变(除了TTL),MAC头跳跳都换。

MAC地址靠ARP协议获取。你的机器要发给某个IP,先在本网段广播一个ARP请求问"谁是这个IP",对方回应自己的MAC。这个结果会缓存一段时间,可以用arp -a或者ip neigh查看。缓存过期或者条目缺失,就会出现"IP能ping通但网页打不开"这种诡异现象,本质是ARP没解析出来。

5.2 端口与进程:门牌号和屋里的人

IP地址定位到主机,端口定位到主机上的具体服务。这个概念本身不难,难的是几个细节。

第一个细节:端口号范围是0到65535,其中0到1023是知名端口,需要管理员权限才能绑定。HTTP是80,HTTPS是443,SSH是22,DNS是53。1024到49151是注册端口,49152以上是动态端口,通常由系统在建立连接时临时分配。

第二个细节:一个端口同一时刻只能被一个进程监听(除非用了特殊的复用选项)。这就是为什么你启动服务时经常遇到"端口已被占用"。查谁占用了端口:

sudo ss -tlnp | grep 8080 sudo lsof -i :8080

第三个细节:监听地址的选择很关键。绑定127.0.0.1只有本机能访问,绑定0.0.0.0所有网卡都能访问。我见过不少人本地测试没问题,一部署到服务器就外部访问不了,最后发现是代码里写死了127.0.0.1。

第四个细节:客户端的源端口是系统随机分配的,一般不用管。但如果你的程序要发起大量连接,可能会把本地端口耗尽,这时候需要调整系统的端口范围或者复用参数。这个坑在做压力测试时特别容易踩。

5.3 TCP与UDP:可靠不是免费的

概述阶段讲这两个协议,往往只给一句"TCP可靠、UDP不可靠",然后就没了。这个说法不算错,但没说清楚代价,导致很多人无脑选TCP。

TCP的可靠是靠一整套机制堆出来的:建立连接要三次握手,传输过程要序号确认、超时重传、流量控制、拥塞控制,断开要四次挥手。这些机制带来了正确性,也带来了延迟和开销。一次简单的请求响应,TCP至少要经历握手的一个往返,加上数据传输的一个往返,稳妥但慢。

UDP什么都不保证,发出去就不管了,不建连接、不重传、不排序。听起来很糟糕,但在某些场景下恰恰是优点:DNS查询就一个包一问一答,为它建连接纯属浪费;实时音视频宁可丢几帧也不愿意等重传,因为等来的数据已经过期了;游戏的位置同步也是同样道理。

对比项TCPUDP
连接方式面向连接,需握手无连接,直接发
可靠性确认加重传,保证送达不保证送达
顺序性保证按序交付不保证顺序
头部开销最少20字节8字节
传输效率相对低相对高
典型场景网页、文件传输、数据库连接DNS、实时音视频、游戏同步

选型的判断标准其实就一句话:你能否容忍丢数据,以及能否自己在应用层处理丢数据。如果两者都不能,用TCP;如果能容忍或者能自己处理,UDP往往更合适。现在很多实时传输方案在UDP之上自己实现了一套可靠性机制,就是为了在"完全可靠"和"完全不管"之间找个平衡点。

5.4 网关、子网掩码与"为什么我ping不通邻居"

这三个词单独看都懂,凑一起就容易糊涂。我用一个具体场景说明。

假设你的机器IP是192.168.1.10,子网掩码是255.255.255.0,网关是192.168.1.1。现在你要访问192.168.1.20。

系统先用掩码做与运算,算出自己的网络号是192.168.1.0;再算目标的网络号,也是192.168.1.0。两者相同,说明在同一个网段,于是直接用ARP问192.168.1.20的MAC,直接把帧发过去,不经过网关。

如果目标变成192.168.2.20,网络号算出来是192.168.2.0,跟自己不一样,说明跨网段。这时候不能直接发,得交给网关。于是ARP问网关192.168.1.1的MAC,把帧交给它,由它继续转发。

这个判断逻辑解释了两种常见故障。第一种:掩码配错了。比如你的掩码写成255.255.255.192,那192.168.1.10的网络号变成192.168.1.0,但192.168.1.100的网络号变成192.168.1.64,两者不同网段,你就得走网关,而网关可能不认识这个目标,于是不通。第二种:网关配错了或者没配。跨网段的包发不出去,现象就是"能访问同网段的其他机器,访问外网全失败"。

还有一种情况是网关本身不通。你的包发出去了,但网关那台设备没开转发、或者回程路由没配好,包能出去回不来。这时候用traceroute看,往往第一跳之后就没动静了。

排查这类问题的顺序我一般是这样:先ip addr看自己的IP和掩码,再ip route看路由表里默认网关在不在,然后ping网关看二层通不通,最后traceroute看外部路径。这四步基本能定位到是配置问题还是链路问题。

5.5 localhost、127.0.0.1、0.0.0.0的区别

这三个写法在配置里天天见,但含义完全不同,混淆了会出大问题。

localhost是一个主机名,它通常被解析到127.0.0.1,但严格来说它只是一个名字,具体解析成什么由 hosts 文件和解析器决定。理论上你可以把它改成别的地址,虽然没人这么干。

127.0.0.1是回环地址,属于127.0.0.0/8这个网段。发往这个地址的数据不会经过物理网卡,直接在协议栈内部绕一圈就回来了。它的作用是本机自测,即使网线拔了也能用。

0.0.0.0在监听场景下表示"所有本机地址",绑定了它意味着任何网卡上收到的发往本机端口的请求都会接受。在路由场景下它表示"默认路由"或者"任意地址",含义要看上下文。

最容易出错的是第一个和第三个的混用。比如某个服务配置文件里写了listen 127.0.0.1:8080,你在本机用浏览器访问一切正常,但从另一台机器访问就超时。原因就是它只监听了回环地址,外部请求根本到不了。改成0.0.0.0:8080就好了。

反过来也有风险:把只该内部访问的服务绑到0.0.0.0,等于暴露给了整个网络。所以这不是个纯技术问题,配置时想清楚这个服务该让谁访问,比记住这三个写法的区别更重要。

6. 概述学完之后怎么不还给老师:三个能落地的练法

6.1 用一台机器模拟一张小网

看完概念不动手,过两周就忘光。我建议的第一个练法是:用一台机器造出两个网络,让它们互相通信,观察每一层的表现。

具体做法是建两个虚拟网卡,分别配上不同网段的地址,然后用策略路由让它们互通。或者更简单一点,起两个容器,各自配一个网段,中间用一个转发规则连起来。这个过程里,你会被迫去理解路由表、转发开关、地址规划这些概念,比看十遍书管用。

操作完之后可以验证几件事:从A网段ping B网段的地址能不能通;把转发开关关掉会怎样;把路由表里的某条删掉会怎样。每次改动都抓包看看发生了什么,观察ARP请求、观察TTL的变化、观察ICMP的错误类型。这种"改一下、看一看"的循环,建立起来的理解最牢固。

6.2 自己写一个最小可用的收发程序

第二个练法是自己写一个最简单的客户端和服务端,不要用现成的框架,直接调用socket接口。哪怕只有几十行,你也会被迫面对几个问题:服务端要不要绑定0.0.0.0;listen的 backlog 参数是什么;accept之后拿到的地址是什么;客户端要不要显式关闭连接;关闭的时候会不会产生TIME_WAIT。

写完之后把服务端换成UDP版本,你会直观感受到两者的差别:不需要listen、不需要accept、没有连接状态、一次recvfrom就能拿到数据和对方地址。这种对比比任何表格都深刻。

再进一步,可以故意制造一些异常。比如服务端处理到一半直接退出,看客户端收到的是什么;比如把发送的数据调大,超过MSS,看TCP怎么拆分;比如在中间加一个人为的延迟,看超时重传的间隔规律。这些实验做下来,概述里那些抽象描述就都变成了你能观察到的事实。

6.3 建立"故障分层排查"的肌肉记忆

第三个练法是刻意训练排查顺序。我给自己定了个规矩:遇到网络问题,先不猜原因,先按层走一遍。

物理和链路层:网线插了没,网卡状态是不是UP,ip link看一眼。同一网段的机器能不能ping通,能通说明链路和二层没问题。

网络层:ip addr看地址掩码,ip route看路由,ping网关看能不能出去,traceroute看路径在哪断。

传输层:nc探端口,ss看连接状态,看有没有大量SYN_RECV或者TIME_WAIT。

应用层:前面都通了还不 work,那就是应用自己的问题,去看日志、看配置、看代码。

这套流程走熟了之后,排查速度会快很多,因为你不会在错误的层面上浪费时间。我最开始犯的错就是不管什么现象都先重启服务,重启完有时候好了有时候没好,根本没定位到原因,下次还会犯。

最后分享一个我自己的体会。学网络概述这类基础内容,最大的误区是把它当成"入门前的准备工作",想着快点看完去学更酷的东西。实际上这一章建立的是整个知识体系的坐标系,后面每学一个协议,你都要把它往这个坐标系里放。坐标系歪了,后面学得越多越乱。所以宁可在这里多花点时间,把分层、封装、寻址这三件事想透,后面会省下大量返工。

我自己到现在还保留一个习惯,遇到一个新的网络现象,先在纸上画一遍数据流向:从哪台机器的哪个端口出发,经过哪几层封装,中间跨了几个网段,每一跳的目的地址是什么。画完往往问题就清楚了。这个习惯不高级,但好用。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进