简介:这份PDF资料面向准备技术面试的IT求职者与网络初学者,系统梳理计算机网络核心考点,帮助读者在面试与工程实践中快速定位知识盲区。内容围绕OSI七层参考模型与TCP/IP四层模型展开,涵盖TCP/IP协议族、TCP/UDP/SCTP协议对比、三次握手与四次挥手、TCP状态机、TIME_WAIT状态、端口号、超时重传与快速重传、TCP Header结构、可靠传输、流量控制与拥塞控制,以及IPv4、IPv6、ICMP、ARP、RARP、IGMP等网络层协议,并附常见面试问题解析。资源包共1个PDF文件,约2.08MB,结构按章节递进,便于按模块查阅与复习。目前已有969人学习,适合需要系统梳理网络知识体系、对照面试高频题查漏补缺的读者。
1. 计算机网络基础知识:从一根网线到一次 HTTP 请求,中间到底发生了什么
很多人对「计算机网络基础知识」的印象停留在背 OSI 七层模型、记 TCP 三次握手,考完就忘。但真正让一线工程师翻车的,往往不是这些概念本身,而是概念和现实对不上号:明明 ping 得通却连不上服务,明明 TCP 建连成功却收不到响应,明明代码里 bind 了端口却报「address already in use」。这些问题的根因,几乎都能回溯到分层模型里某一层的职责边界没搞清楚。
这篇笔记不打算把《计算机网络》教材重讲一遍,而是按「一个数据包从应用层发出,到对端应用层收到」这条真实链路,把 OSI 七层模型、TCP/IP 四层模型、TCP 三次握手、端口与连接状态这些核心知识点,落到能动手验证的命令和能复现的代码上。适合两类人:一是正在学计算机网络、想摆脱死记硬背的在校生;二是做后端、DevOps、嵌入式联网(比如 Modbus TCP、ESP 模块发 TCP 消息)时被网络问题卡住的工程师。读完你应该能做到:看到一个网络故障,先判断它大概率出在哪一层,再用对应工具去验证,而不是盲目重启。
2. OSI 七层与 TCP/IP 四层:分层设计到底解决了什么问题
2.1 为什么要有分层:一次「改一层不影响另一层」的工程妥协
分层设计的本质是关注点分离。假设没有分层,你要写一个「通过网线把文件传到另一台机器」的程序,就得同时处理:网卡电气信号、帧的起止、IP 寻址、路由选择、丢包重传、数据顺序、字符编码……任何一处改动都可能牵动全身。
分层之后,每一层只对上一层提供服务,只依赖下一层的接口。最直接的好处是:你换一块网卡(物理层、链路层变了),上层的 TCP 代码一行都不用改;你把 HTTP 换成 WebSocket(应用层变了),底下的 TCP/IP 照样跑。这就是热词里「体会分层设计」的真正含义——它不是让你背七层名字,而是让你理解每一层可以独立替换。
OSI 是理论参考模型,分七层;TCP/IP 是工程落地模型,分四层。两者的对应关系是面试和排障的高频考点,我整理成一张表:
| OSI 七层 | TCP/IP 四层 | 典型协议/设备 | 这一层出问题的典型现象 |
|---|---|---|---|
| 应用层 / 表示层 / 会话层 | 应用层 | HTTP、DNS、Modbus、MQTT | 服务返回 4xx/5xx、域名解析失败 |
| 传输层 | 传输层 | TCP、UDP | 端口不通、连接被拒、丢包重传 |
| 网络层 | 网络层 | IP、ICMP、ARP(部分) | ping 不通、路由不可达 |
| 数据链路层 | 网络接口层 | 以太网、MAC、网卡驱动 | 同网段不通、ARP 表异常 |
| 物理层 | 网络接口层 | 网线、光模块、电信号 | 网卡灯不亮、链路 down |
提示:排障时从下往上查更省时间。物理层和链路层没问题(网卡 up、能 ping 通网关),再往上看 IP 和端口,最后才怀疑应用代码。
2.2 数据封装与解封装:一个包是怎么被「套娃」的
数据从上往下走叫封装,每经过一层就加一个头部(有时还有尾部)。以一次 HTTP 请求为例:
- 应用层产生 HTTP 报文;
- 传输层加上 TCP 头(含源端口、目的端口、序号),变成 TCP 段;
- 网络层加上 IP 头(含源 IP、目的 IP),变成 IP 包;
- 链路层加上以太网头(含源 MAC、目的 MAC)和帧尾校验,变成帧;
- 物理层把帧变成电信号/光信号发出去。
对端收到后反向解封装,每层剥掉自己的头,把载荷交给上一层。理解这个过程,你就能解释很多现象:比如为什么抓包工具 Wireshark 能同时看到以太网头、IP 头、TCP 头和 HTTP 内容——因为它就是在链路层「截获」了完整的帧。
2.3 用一条命令把分层「看」出来
光讲理论容易飘,动手验证一次印象最深。在 Linux/macOS 上,用traceroute(Windows 用tracert)可以看到数据包经过的每一跳,这直接对应网络层的路由行为:
# -n 表示不反解域名,直接显示 IP,速度更快 # 目标可以是任意公网地址,这里用常见的 DNS 服务器举例 traceroute -n 8.8.8.8 # 查看本机网络接口和 IP,对应网络层和链路层 ip addr show # Linux ifconfig # macOS / 老版本 Linux # 查看 ARP 表,对应链路层的 MAC 地址映射 arp -a逻辑说明:traceroute利用 IP 包的 TTL 字段,逐跳递增 TTL,让沿途每个路由器回一个 ICMP 超时报文,从而画出路径。ip addr看的是本机网络层地址(IP)和链路层状态(网卡是否 UP)。arp -a看的是 IP 到 MAC 的映射,如果这里缺了网关的条目,同网段通信就会出问题。
参数说明:-n关闭 DNS 反解,排障时强烈建议加上,否则每一跳都要等域名解析,慢得让人怀疑人生。traceroute默认发 UDP 包,某些网络会过滤,可以换-I用 ICMP,或-T用 TCP。
3. TCP 三次握手与四次挥手:连接到底是怎么建立和断开的
3.1 三次握手每一步在干什么
TCP 是面向连接的协议,通信前必须先建连。三次握手的过程:
- 客户端发SYN(seq=x),进入 SYN_SENT;
- 服务端回SYN+ACK(seq=y, ack=x+1),进入 SYN_RCVD;
- 客户端回ACK(ack=y+1),双方进入 ESTABLISHED。
为什么是三次而不是两次?核心原因是双方都要确认对方的收发能力都正常。两次握手只能让服务端确认「客户端能发、我能收」,但客户端无法确认「服务端能发、我能收」。第三次 ACK 补上了这个确认。另外,三次握手还能防止历史失效的连接请求突然到达服务端,导致服务端建立无效连接、白白占用资源。
3.2 用代码亲手触发一次握手
理论看再多,不如自己写一个最小 TCP 服务端和客户端,用抓包看握手。下面是一个 Python 最小示例:
# server.py —— 最小 TCP 服务端 import socket # AF_INET 表示 IPv4,SOCK_STREAM 表示 TCP server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SO_REUSEADDR 让端口在 TIME_WAIT 状态下也能快速复用,避免重启报 address already in use server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind(("0.0.0.0", 9000)) # 监听所有网卡的 9000 端口 server.listen(5) # backlog 队列长度 5 print("listening on 9000") conn, addr = server.accept() # 阻塞等待客户端连接,这一步背后就是三次握手 print("connected from", addr) data = conn.recv(1024) # 接收数据 print("recv:", data) conn.sendall(b"hello from server") conn.close() server.close()# client.py —— 最小 TCP 客户端 import socket client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 9000)) # 触发三次握手 client.sendall(b"hello from client") resp = client.recv(1024) print("resp:", resp) client.close()逻辑说明:accept()返回的那一刻,三次握手已经完成,连接进入 ESTABLISHED。connect()在客户端侧发起 SYN 并等待 SYN+ACK,收到后才返回。SO_REUSEADDR是服务端必设项,否则服务端主动关闭连接后进入 TIME_WAIT,短时间内重启会报端口占用。
参数说明:listen(5)的 5 是 backlog,表示已完成握手但还没被accept()取走的连接队列长度。高并发场景这个值要调大,否则新连接会被丢弃。recv(1024)的 1024 是一次最多读多少字节,TCP 是字节流,不保证一次 recv 就读完一条完整消息,这是新手最容易踩的坑。
3.3 四次挥手与 TIME_WAIT
关闭连接需要四次挥手,因为 TCP 是全双工的,每个方向要单独关闭:
- 主动关闭方发FIN;
- 被动方回ACK;
- 被动方数据发完后发FIN;
- 主动方回ACK,并进入TIME_WAIT,等待 2MSL 后彻底关闭。
TIME_WAIT 常被误解为「bug」,其实它是必要的:一是保证最后一个 ACK 能到达对端,二是让本次连接的残余报文在网络中消散,避免影响下一个复用相同四元组的新连接。服务端如果出现大量 TIME_WAIT,通常是它主动关闭了大量连接,可以考虑用连接池或调整内核参数,而不是简单粗暴地关掉 TIME_WAIT。
用下面命令可以观察连接状态分布:
# 统计各 TCP 状态的数量,排障时一眼看出问题 ss -ant | awk 'NR>1 {print $1}' | sort | uniq -c | sort -rn # 只看 9000 端口的连接 ss -ant sport = :90004. 端口、连接与常见网络故障排查
4.1 端口和四元组:连接的唯一标识
一条 TCP 连接由四元组唯一确定:源 IP、源端口、目的 IP、目的端口。这也是为什么同一台机器可以用同一个本地端口连不同的服务,或者用不同端口连同一个服务——只要四元组不同,就是不同的连接。
端口范围分三类:0–1023 是知名端口(HTTP 80、HTTPS 443、SSH 22),1024–49151 是注册端口,49152–65535 是动态/临时端口。客户端connect()时如果不指定本地端口,操作系统会从临时端口范围里分配一个。
注意:报错「address already in use」不一定是端口真被占用,也可能是上一个连接处于 TIME_WAIT。用
ss -ant看状态,如果是 TIME_WAIT,加SO_REUSEADDR即可;如果是 LISTEN,那才是真被别的进程占了。
4.2 分层排障的固定套路
我一般按这个顺序查,基本能覆盖 90% 的问题:
- 物理/链路层:网卡是否 UP(
ip link),网线灯是否亮,同网段能否 ping 通。 - 网络层:能否 ping 通网关和目标 IP(
ping),路由是否正确(ip route)。 - 传输层:目标端口是否开放(
nc -zv host port或telnet host port),本机是否在监听(ss -tlnp)。 - 应用层:服务日志、返回码、抓包看实际收发的数据(
tcpdump)。
# 测试目标端口是否可达,-z 只探测不发送数据,-v 显示详情 nc -zv 127.0.0.1 9000 # 查看本机所有监听中的 TCP 端口及对应进程 ss -tlnp # 抓取 9000 端口的包,-i any 表示所有网卡,-w 写入文件供 Wireshark 分析 sudo tcpdump -i any port 9000 -w tcp9000.pcap逻辑说明:nc -zv走的是完整的 TCP 三次握手,能连上说明传输层通。ss -tlnp里-t是 TCP,-l是 LISTEN,-n不解析服务名,-p显示进程。tcpdump抓到的 pcap 文件可以用 Wireshark 打开,直接看到三次握手的三个包,这是验证理论最直观的方式。
4.3 UDP 和 TCP 的选型边界
热词里高频出现「UDP 和 TCP 的区别」,这里给一个工程视角的判断标准,而不是背特性表:
- 要可靠、有序、有连接,选 TCP:HTTP、数据库连接、文件传输、Modbus TCP。
- 要低延迟、能容忍丢包、自己控制重传,选 UDP:实时音视频、DNS 查询、游戏同步、部分物联网上报。
关键点:UDP 不是「不可靠就不能用」,而是把可靠性控制权交给了应用层。很多实时场景宁可丢一帧也不要卡顿,这时 TCP 的重传机制反而是负担。
5. 避坑与常见问题:那些教材不会告诉你的翻车现场
5.1 坑一:ping 得通却连不上服务
现象:ping 目标IP正常,但nc -zv 目标IP 端口超时或被拒。原因:ping 走的是 ICMP,属于网络层;连服务走的是 TCP,属于传输层。网络层通不代表传输层通——可能是目标端口没监听、被防火墙拦了、或服务只绑定了 127.0.0.1 而非 0.0.0.0。解决:先在目标机ss -tlnp确认端口在监听且绑定地址正确;再检查防火墙规则;最后用tcpdump看 SYN 有没有到达、有没有回 RST。
5.2 坑二:recv 一次没读完就当成完整消息
现象:客户端发了一条 1000 字节的消息,服务端recv(1024)却只收到 300 字节,解析报错。原因:TCP 是字节流协议,没有消息边界。发送方的sendall和接收方的recv不是一一对应的,网络会把数据切成任意大小的段。解决:应用层自己定边界。常见做法是「长度前缀」(先发 4 字节表示消息长度,再发内容)或「分隔符」(如换行)。这是写 TCP 程序必须处理的第一件事。
5.3 坑三:服务端重启报 address already in use
现象:刚 Ctrl+C 停掉服务,立刻重启就报端口占用。原因:服务端主动关闭连接后,连接进入 TIME_WAIT,默认持续 2MSL(Linux 上约 60 秒),期间端口不能被新连接绑定。解决:设置SO_REUSEADDR(见 3.2 的代码)。注意它解决的是 TIME_WAIT 复用,不是让两个进程同时监听同一端口。
5.4 坑四:把 OSI 七层当成必须严格对应的实现
现象:纠结「ARP 到底属于哪一层」「Modbus TCP 算应用层还是传输层」,越想越乱。原因:OSI 是理论参考模型,实际协议栈并不严格按七层划分。ARP 横跨链路层和网络层,Modbus TCP 是把 Modbus 报文封装进 TCP 载荷,本质是应用层协议。解决:把分层当成分析工具而非分类标准。排障时问「这个问题属于寻址、传输还是应用逻辑」,比纠结它「属于第几层」有用得多。
5.5 坑五:抓包抓不到想要的流量
现象:tcpdump跑着却什么都没抓到。原因:抓错了网卡(比如服务在 eth0 却抓了 lo),或者过滤表达式写错,或者没有 sudo 权限。解决:先用tcpdump -D列出所有网卡,用-i any抓所有网卡;过滤表达式先用最简单的port 9000验证,再逐步加条件;确认有 root 权限。
6. 进阶技巧:用一条命令验证你对 TCP 状态的全部理解
学完理论,怎么知道自己真懂了?我的验证方法是:不看任何资料,用ss命令把一条连接从建立到关闭的完整状态变化复现出来。
具体做法:开两个终端。终端 A 跑一个服务端并让它保持连接不关闭;终端 B 用nc连上去,然后反复执行ss -ant | grep 9000,观察状态从LISTEN→SYN_RECV(瞬间)→ESTABLISHED。接着在终端 B 按 Ctrl+C 主动关闭,再立刻看,会看到FIN_WAIT1→FIN_WAIT2→TIME_WAIT的完整过程。如果服务端先关,你会在服务端看到CLOSE_WAIT。
# 终端 A:起一个只监听不干活的 TCP 服务端 nc -l 9000 # 终端 B:连上去 nc 127.0.0.1 9000 # 第三个终端:持续观察状态变化 watch -n 0.5 "ss -ant | grep 9000"逻辑说明:nc -l 9000进入 LISTEN;终端 B 连接时,服务端短暂出现 SYN_RECV,握手完成后双方都是 ESTABLISHED。终端 B 断开时,作为主动关闭方,它会经历 FIN_WAIT1、FIN_WAIT2、TIME_WAIT;服务端作为被动关闭方,会经历 CLOSE_WAIT,直到它自己也调用 close 才发 FIN。
参数说明:watch -n 0.5每 0.5 秒刷新一次,能捕捉到快速变化的状态。ss -ant里-a显示所有状态(包括 LISTEN 和非 LISTEN),这是关键,不加-a会漏掉 LISTEN。
能把这套状态变化和三次握手、四次挥手一一对应上,说明你对 TCP 连接生命周期的理解已经落地了。我当年就是靠反复跑这个实验,才把「CLOSE_WAIT 堆积说明应用没正确关闭连接」这个结论真正记住——后来线上排查连接泄漏,第一反应就是去看 CLOSE_WAIT 的数量,比翻文档快得多。
网络这东西,理论背得再熟,不动手抓一次包、不看一次状态机,遇到问题还是抓瞎。建议你今天就找个空闲机器,把上面这几段代码和命令跑一遍,比看十篇文章都管用。希望帮到你。
本文还有配套的精品资源,点击获取