大家好,我是V哥。
最近有兄弟在面试的时候被怼了,面试官问:"你输入一个网址,到网页显示出来,中间发生了什么?" 他支支吾吾说了个大概,结果人家问到底层 TCP 握手和 IP 路由的时候,直接卡壳。
说实话,TCP/IP 协议栈这东西,教科书写得太枯燥了。什么 OSI 七层模型、什么报文头格式......看两眼就犯困。但V哥告诉你,这玩意儿是互联网的"空气"和"水"。你不懂它,在这个行业里永远只能算个"码农",成不了"工程师"。
今天,V哥不给你背书,咱们用大白话 + 实操抓包,把这个"互联网潜规则"彻底扒光,让你看个明明白白。
这篇文章适合谁:
- 面试总被网络问题虐的开发者
- 想搞懂网络底层原理的运维工程师
- 网络基础薄弱但想补课的程序员
老规矩,系好安全带,V哥要开始飙车了。
一、先讲个故事:一个数据包的奇幻漂流
在讲协议之前,V哥先给你讲个故事,帮你建立直觉。
假设你在北京,想给在深圳的朋友寄一封信。这个过程是这样的:
你写的信内容
↓
装进信封,写上收件人地址(深圳XX路XX号)
↓
贴上邮票,投进邮筒
↓
邮局收走,盖上北京的邮戳
↓
运到深圳的分拣中心
↓
送到朋友家,朋友拆开信封
↓
朋友读到你写的内容
TCP/IP的工作原理跟这个一模一样!
你的数据就是那封信的内容,协议栈的每一层就像是装信封、贴邮票、分拣运输的过程。
让V哥用表格给你对应一下:
| 寄信过程 | TCP/IP协议栈 | 干的事情 |
|---|---|---|
| 信的内容 | 应用层(HTTP/HTTPS) | 你要发送的数据 |
| 装信封、写地址 | 传输层(TCP/UDP) | 确保数据可靠送达 |
| 贴邮票、投邮筒 | 网络层(IP) | 规划路线,找到目标 |
| 邮车运输 | 数据链路层(以太网) | 在物理网络上传输 |
| 公路/铁路 | 物理层 | 电信号/光信号 |
现在你应该有个大概印象了。接下来,V哥带你深入每一层。
二、TCP/IP四层模型详解
1. 为什么是"四层"而不是"七层"?
你可能听说过OSI七层模型,也听说过TCP/IP四层模型,到底用哪个?
V哥的回答:实际工作中,用四层模型就够了。
OSI七层是理论模型,学术界喜欢;TCP/IP四层是实战模型,工程师用这个。
对应关系:
┌─────────────────────────────────────────────────────────────┐
│ 模型对比 │
├─────────────────────────────────────────────────────────────┤
│ │
│ OSI七层模型 TCP/IP四层模型 实际协议 │
│ │
│ ┌───────────┐ │
│ │ 应用层 │ │
│ ├───────────┤ ┌───────────┐ HTTP, FTP, │
│ │ 表示层 │ ───→ │ 应用层 │ DNS, SSH, │
│ ├───────────┤ └───────────┘ WebSocket │
│ │ 会话层 │ │
│ ├───────────┤ ┌───────────┐ │
│ │ 传输层 │ ───→ │ 传输层 │ TCP, UDP │
│ ├───────────┤ └───────────┘ │
│ │ 网络层 │ ───→ ┌───────────┐ IP, ICMP, │
│ │ │ │ 网络层 │ ARP │
│ ├───────────┤ └───────────┘ │
│ │ 数据链路层│ ───→ ┌───────────┐ Ethernet, │
│ ├───────────┤ │网络接口层 │ WiFi, PPP │
│ │ 物理层 │ └───────────┘ │
│ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
2. 数据封装过程
当你发送数据时,每一层都会给数据"包一层皮":
数据封装过程
应用层 │ HTTP请求数据 │
└──────────────┘
↓ 加上TCP头
传输层 │ TCP头 │ HTTP请求数据 │
└───────┴──────────────┘
↓ 加上IP头
网络层 │ IP头 │ TCP头 │ HTTP请求数据 │
└──────┴───────┴──────────────┘
↓ 加上以太网帧头和帧尾
链路层 │ 帧头 │ IP头 │ TCP头 │ HTTP数据 │ 帧尾 │
└──────┴──────┴───────┴──────────┴──────┘
V哥打个比方:
这就像俄罗斯套娃!你的数据是最里面那个小娃娃,每经过一层就套一个大娃娃。到了目的地,再一层层拆开。
三、物理层和数据链路层:数据怎么在网线里跑
1. 物理层:0和1的旅行
物理层解决的问题是:怎么把0和1变成电信号/光信号/无线电波传出去?
你不用太深入了解这层,知道几个概念就行:
| 概念 | 解释 |
|---|---|
| 比特(bit) | 数据的最小单位,0或1 |
| 带宽 | 单位时间能传多少比特,比如1Gbps |
| 双绞线 | 普通网线,Cat5e/Cat6 |
| 光纤 | 用光传输,速度快、距离远 |
2. 数据链路层:MAC地址与以太网帧
这一层解决的问题是:在同一个局域网内,数据怎么从A电脑传到B电脑?
核心概念是MAC地址 和以太网帧。
MAC地址是什么?
MAC地址是网卡的"身份证号",全球唯一,长这样:00:1A:2B:3C:4D:5E
bash
# 查看你电脑的MAC地址
# Linux/Mac
ip link show
# 或
ifconfig
# Windows
ipconfig /all
以太网帧结构
┌────────────────────────────────────────────────────────────────┐
│ 以太网帧结构 │
├──────────┬──────────┬──────┬─────────────────────┬─────────────┤
│ 目标MAC │ 源MAC │ 类型 │ 数据载荷 │ FCS │
│ 6字节 │ 6字节 │ 2字节│ 46-1500字节 │ 4字节 │
├──────────┴──────────┴──────┴─────────────────────┴─────────────┤
│ │
│ 目标MAC: 要发给谁(比如路由器的MAC) │
│ 源MAC: 我是谁(你网卡的MAC) │
│ 类型: 上层协议类型(0x0800=IPv4, 0x0806=ARP) │
│ 数据载荷: 上层传下来的数据(IP数据包) │
│ FCS: 校验和,用于检测传输错误 │
│ │
└────────────────────────────────────────────────────────────────┘
3. 实战:抓个以太网帧看看
V哥带你动手抓包,眼见为实。
安装Wireshark
bash
# Ubuntu
sudo apt install wireshark
# Mac
brew install wireshark
# Windows直接去官网下载
抓包步骤
bash
# 1. 启动Wireshark
sudo wireshark
# 2. 选择你的网卡(比如eth0或en0)
# 3. 开始抓包
# 4. 在终端执行
ping baidu.com
# 5. 在Wireshark中找到ICMP包,看底层的Ethernet帧
你会看到类似这样的信息:
Ethernet II, Src: YOUR_MAC, Dst: ROUTER_MAC
Destination: 00:11:22:33:44:55
Source: aa:bb:cc:dd:ee:ff
Type: IPv4 (0x0800)
V哥划重点:注意看目标MAC不是百度服务器的MAC,而是你的路由器/网关的MAC。因为以太网帧只负责"一跳",先把数据送到路由器,路由器再负责下一跳。
4. ARP协议:IP地址怎么找到MAC地址?
你知道百度的IP是39.156.66.10,但以太网帧需要MAC地址才能发送。怎么根据IP找到MAC?
答案是ARP协议(Address Resolution Protocol)。
ARP的工作过程
场景:你的电脑(192.168.1.100)想访问路由器(192.168.1.1)
步骤1:你的电脑广播一个ARP请求
"谁是192.168.1.1?请告诉192.168.1.100"
(目标MAC设为FF:FF:FF:FF:FF:FF,表示广播)
步骤2:路由器收到后回复
"我是192.168.1.1,我的MAC是00:11:22:33:44:55"
步骤3:你的电脑记住这个映射关系(ARP缓存)
下次直接用,不用再问了
实战:查看和操作ARP缓存
bash
# 查看ARP缓存
arp -a
# 输出示例:
# ? (192.168.1.1) at 00:11:22:33:44:55 on en0 ifscope [ethernet]
# ? (192.168.1.105) at aa:bb:cc:dd:ee:ff on en0 ifscope [ethernet]
# 清除ARP缓存(需要root)
# Linux
sudo ip neigh flush all
# Mac
sudo arp -d -a
# 手动添加ARP条目
sudo arp -s 192.168.1.200 00:aa:bb:cc:dd:ee
用Wireshark抓ARP包
bash
# 1. 清除ARP缓存
sudo ip neigh flush all
# 2. 开启Wireshark,过滤器设为: arp
# 3. ping网关
ping 192.168.1.1
# 4. 观察ARP请求和响应
你会看到:
ARP Request: Who has 192.168.1.1? Tell 192.168.1.100
ARP Reply: 192.168.1.1 is at 00:11:22:33:44:55
四、网络层:IP协议深度剖析
网络层解决的核心问题是:数据包怎么跨越多个网络,从源头到达目的地?
1. IP地址详解
IPv4地址结构
IPv4地址:32位二进制数,通常用点分十进制表示
示例:192.168.1.100
二进制:11000000.10101000.00000001.01100100
└──网络部分──┘ └──主机部分──┘
网络部分:标识哪个网络
主机部分:标识网络中的哪台主机
子网掩码
子网掩码用来区分IP地址的网络部分和主机部分。
IP地址: 192.168.1.100
子网掩码: 255.255.255.0 (或写成 /24)
二进制:
IP: 11000000.10101000.00000001.01100100
掩码: 11111111.11111111.11111111.00000000
└────────网络部分────────┘└─主机──┘
网络地址 = IP & 掩码 = 192.168.1.0
广播地址 = 192.168.1.255
可用主机 = 192.168.1.1 ~ 192.168.1.254 (共254个)
常见子网划分速查表
| CIDR | 子网掩码 | 可用主机数 | 常用场景 |
|---|---|---|---|
| /8 | 255.0.0.0 | 16,777,214 | A类网络 |
| /16 | 255.255.0.0 | 65,534 | B类网络 |
| /24 | 255.255.255.0 | 254 | 最常用的局域网 |
| /25 | 255.255.255.128 | 126 | 小型办公室 |
| /26 | 255.255.255.192 | 62 | 小型网络 |
| /27 | 255.255.255.224 | 30 | 很小的网络 |
| /28 | 255.255.255.240 | 14 | 点对点+少量主机 |
| /30 | 255.255.255.252 | 2 | 点对点链路 |
| /32 | 255.255.255.255 | 1 | 单个主机 |
实战:计算网络地址
bash
# 使用ipcalc工具(需要安装)
# Ubuntu
sudo apt install ipcalc
# 计算
ipcalc 192.168.1.100/24
# 输出:
# Network: 192.168.1.0/24
# Netmask: 255.255.255.0
# Broadcast: 192.168.1.255
# HostMin: 192.168.1.1
# HostMax: 192.168.1.254
# Hosts/Net: 254
2. IP数据包结构
┌────────────────────────────────────────────────────────────────────┐
│ IP数据包头部 │
├─────────┬─────────┬────────────────┬──────────────────────────────┤
│ 版本(4) │ 头长度 │ 服务类型 │ 总长度 │
│ 4bit │ 4bit │ 8bit │ 16bit │
├─────────┴─────────┴────────────────┼────────┬─────────────────────┤
│ 标识(16bit) │ 标志 │ 片偏移(13bit) │
├────────────────┬───────────────────┴────────┴─────────────────────┤
│ TTL(8bit) │ 协议(8bit) │ 头部校验和(16bit) │
├────────────────┴───────────────────┴──────────────────────────────┤
│ 源IP地址(32bit) │
├───────────────────────────────────────────────────────────────────┤
│ 目的IP地址(32bit) │
├───────────────────────────────────────────────────────────────────┤
│ 选项(可选) │
├───────────────────────────────────────────────────────────────────┤
│ 数据载荷 │
└───────────────────────────────────────────────────────────────────┘
重要字段解释
| 字段 | 作用 | V哥的大白话解释 |
|---|---|---|
| 版本 | 4=IPv4, 6=IPv6 | 告诉别人我用的是哪个版本 |
| TTL | 生存时间 | 每经过一个路由器减1,到0就丢弃,防止数据包在网络里无限循环 |
| 协议 | 上层协议类型 | 1=ICMP, 6=TCP, 17=UDP |
| 源IP | 发送方IP | 我是谁 |
| 目的IP | 接收方IP | 要发给谁 |
3. 路由:数据包怎么找到路?
路由表是什么?
路由表就像是一张"导航地图",告诉数据包:想去某个目的地,下一步该往哪走。
bash
# 查看路由表
# Linux
ip route
# 或
route -n
# Mac
netstat -rn
# Windows
route print
路由表示例解读
bash
$ ip route
default via 192.168.1.1 dev eth0 # 默认路由:不知道去哪就走这
192.168.1.0/24 dev eth0 proto kernel # 本地网络:直接可达
10.0.0.0/8 via 192.168.1.254 dev eth0 # 去10网段,找192.168.1.254
172.16.0.0/16 via 192.168.1.253 dev eth0 # 去172.16网段,找192.168.1.253
V哥的大白话解释
路由决策过程,就像你问路一样:
你:我想去 10.0.1.100
路由表:10.0.0.0/8 的包,交给 192.168.1.254
你:好的,那我先把包发给 192.168.1.254
你:我想去 8.8.8.8(谷歌DNS)
路由表:(查了一圈,没有匹配的)用默认路由,交给 192.168.1.1
你:好的,发给网关
规则:最长前缀匹配
如果有多条规则匹配,选择最具体的那条(掩码最长的)
4. 实战:traceroute追踪路由路径
bash
# Linux
traceroute baidu.com
# 或使用mtr(更好用)
mtr baidu.com
# Windows
tracert baidu.com
输出示例
traceroute to baidu.com (39.156.66.10), 30 hops max
1 192.168.1.1 (192.168.1.1) 1.234 ms # 你的路由器
2 10.0.0.1 (10.0.0.1) 5.678 ms # 小区/公司网关
3 221.130.33.1 (221.130.33.1) 10.123 ms # 运营商接入层
4 221.130.39.45 (221.130.39.45) 15.456 ms # 运营商骨干网
5 219.158.98.13 (219.158.98.13) 20.789 ms # 城域网出口
6 * * * # 该节点不回复
7 39.156.27.1 (39.156.27.1) 25.012 ms # 百度机房入口
8 39.156.66.10 (39.156.66.10) 28.345 ms # 百度服务器
V哥解读:每一行就是数据包经过的一个路由器(一跳)。通过这个命令,你可以看到数据包从你电脑到目的服务器走了哪些路。
5. ICMP协议:网络诊断利器
ICMP(Internet Control Message Protocol)是网络层的"探测兵",用来报告网络问题。
ping命令原理
ping的工作过程:
1. 你的电脑发送ICMP Echo Request(类型8)
"喂,你在吗?"
2. 目标主机收到后回复ICMP Echo Reply(类型0)
"我在呢!"
3. 计算往返时间(RTT)
"从问到答用了多久"
常见ICMP类型
| 类型 | 代码 | 含义 | 什么时候会收到 |
|---|---|---|---|
| 0 | 0 | Echo Reply | ping的回复 |
| 3 | 0 | Network Unreachable | 路由器找不到去目标网络的路 |
| 3 | 1 | Host Unreachable | 目标主机不可达 |
| 3 | 3 | Port Unreachable | 目标端口没有服务监听 |
| 8 | 0 | Echo Request | ping发出的请求 |
| 11 | 0 | TTL Exceeded | TTL减到0了(traceroute利用这个) |
实战:用ping诊断网络
bash
# 基本ping
ping baidu.com
# 指定次数
ping -c 4 baidu.com
# 指定包大小(测试MTU)
ping -s 1472 baidu.com
# 持续ping,显示时间戳
ping -D baidu.com
ping结果分析
PING baidu.com (39.156.66.10): 56 data bytes
64 bytes from 39.156.66.10: icmp_seq=0 ttl=49 time=25.3 ms
64 bytes from 39.156.66.10: icmp_seq=1 ttl=49 time=24.8 ms
64 bytes from 39.156.66.10: icmp_seq=2 ttl=49 time=25.1 ms
--- baidu.com ping statistics ---
3 packets transmitted, 3 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 24.8/25.1/25.3/0.2 ms
| 指标 | 含义 | 正常范围 |
|---|---|---|
| ttl=49 | 剩余生存时间 | 一般30-128 |
| time=25.3ms | 往返时间 | 局域网<1ms,国内<50ms,跨国<300ms |
| packet loss | 丢包率 | 0%最好,<1%可接受 |
五、传输层:TCP和UDP的恩怨情仇
传输层解决的问题是:数据怎么可靠地、完整地送到对方的应用程序?
1. TCP vs UDP:什么时候用谁?
V哥的大白话对比
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 先打电话确认对方在,再说事 | 直接寄明信片,不管对方收没收到 |
| 可靠性 | 保证送达,丢了会重发 | 不保证,丢了就丢了 |
| 顺序 | 保证按顺序到达 | 可能乱序 |
| 速度 | 相对慢(要各种确认) | 快(直接发) |
| 头部开销 | 20字节起 | 8字节固定 |
| 适用场景 | 网页、文件传输、邮件 | 视频直播、游戏、DNS查询 |
生活中的比喻
TCP就像打电话:
- 先拨号等对方接听(三次握手)
- 说一句等对方"嗯"了再说下一句(确认机制)
- 说完了要说再见(四次挥手)
UDP就像发传单:
- 印好了往外扔就完事
- 不管别人接没接到
- 接到的人顺序可能是乱的
2. TCP三次握手:深入骨髓的理解
这是面试必问的问题,V哥带你彻底搞懂。
三次握手过程
┌────────────────────────────────────────────────────────────────┐
│ TCP三次握手 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 客户端 服务端 │
│ │ │ │
│ │ 1. SYN=1, seq=x │ │
│ │ "我想和你建立连接,我的初始序列号是x" │ │
│ │ ─────────────────────────────────────────────→ │ │
│ │ │ │
│ │ 2. SYN=1, ACK=1, seq=y, ack=x+1 │ │
│ │ "好的,我同意,我的序列号是y" │ │
│ │ ←───────────────────────────────────────────── │ │
│ │ │ │
│ │ 3. ACK=1, seq=x+1, ack=y+1 │ │
│ │ "收到,咱们开始吧" │ │
│ │ ─────────────────────────────────────────────→ │ │
│ │ │ │
│ │ 连接建立完成! │ │
│ │ │ │
└────────────────────────────────────────────────────────────────┘
为什么是三次?两次不行吗?
V哥给你举个例子:
假设只有两次握手:
场景:
1. 客户端发送连接请求(SYN),但网络延迟,这个包在网上漂了很久
2. 客户端以为丢了,又发了一个新的连接请求
3. 第二个请求先到了,服务端回复同意(SYN-ACK),连接建立
4. 双方通信完毕,关闭连接
5. 这时候,第一个"迟到"的连接请求终于到了服务端
6. 服务端以为是新的连接请求,回复同意(SYN-ACK)
7. 服务端傻等客户端发数据,客户端根本不知道有这回事
问题:服务端建立了一个"幽灵连接",浪费资源!
三次握手就能避免这个问题:
- 第三次握手时,客户端不会回复那个"迟到"的连接
- 服务端收不到第三次握手,就不会真正建立连接
实战:抓包看三次握手
bash
# 1. 开启Wireshark,过滤器设为: tcp.port == 80
# 2. 用curl访问一个网站
curl http://example.com
# 3. 观察TCP包
你会看到三个包:
1. [SYN] Seq=0 客户端→服务端
2. [SYN,ACK] Seq=0 Ack=1 服务端→客户端
3. [ACK] Seq=1 Ack=1 客户端→服务端
3. TCP四次挥手:优雅地说再见
四次挥手过程
┌────────────────────────────────────────────────────────────────┐
│ TCP四次挥手 │
├────────────────────────────────────────────────────────────────┤
│ │
│ 客户端 服务端 │
│ │ │ │
│ │ 1. FIN=1, seq=u │ │
│ │ "我说完了,想挂电话" │ │
│ │ ─────────────────────────────────────────────→ │ │
│ │ (客户端进入FIN_WAIT_1) │ │
│ │ │ │
│ │ 2. ACK=1, seq=v, ack=u+1 │ │
│ │ "好的,我知道了" │ │
│ │ ←───────────────────────────────────────────── │ │
│ │ (客户端进入FIN_WAIT_2) │ │
│ │ │ │
│ │ 3. FIN=1, seq=w, ack=u+1 │ │
│ │ "我也说完了" │ │
│ │ ←───────────────────────────────────────────── │ │
│ │ │ │
│ │ 4. ACK=1, seq=u+1, ack=w+1 │ │
│ │ "好的,再见" │ │
│ │ ─────────────────────────────────────────────→ │ │
│ │ (客户端进入TIME_WAIT,等待2MSL后关闭) │ │
│ │ │ │
└────────────────────────────────────────────────────────────────┘
为什么是四次?不能三次吗?
打电话的比喻:
你:我说完了,准备挂了(FIN)
对方:好的,我知道了(ACK)
但是我还有几句话要说...(服务端可能还有数据要发)
...
...好了,我也说完了(FIN)
你:好的,再见(ACK)
第2步和第3步不能合并,因为服务端可能还有数据没发完!
TIME_WAIT状态
客户端发完最后一个ACK后,不是立即关闭,而是进入TIME_WAIT状态,等待2MSL(Maximum Segment Lifetime,通常是60秒)。
为什么要等?
1. 确保最后一个ACK能到达服务端
如果ACK丢了,服务端会重发FIN,客户端还能再发ACK
2. 让"迟到"的数据包在网络中消失
防止它们干扰新的连接
实战:查看TIME_WAIT连接
bash
# 查看所有TCP连接状态
netstat -ant | grep TIME_WAIT
# 或用ss命令(更快)
ss -ant | grep TIME-WAIT
# 统计各状态的连接数
ss -ant | awk '{print $1}' | sort | uniq -c
4. TCP可靠传输机制
TCP保证可靠传输,靠的是这几个机制:
4.1 序列号和确认号
原理:
发送方:我发了1000字节,序列号从1开始
接收方:我收到了1000字节,期待下一个是1001(ACK=1001)
发送方:好的,我发接下来的1000字节,序列号1001
接收方:收到了,期待2001(ACK=2001)
如果接收方没收到1001-2000的数据,它会一直回复ACK=1001
发送方发现ACK没变,就知道要重传了
4.2 滑动窗口
滑动窗口解决的问题是:不用每发一个包就等一次确认,可以连续发多个包。
┌──────────────────────────────────────────────────────────────┐
│ 滑动窗口示意 │
├──────────────────────────────────────────────────────────────┤
│ │
│ 已确认 │ 已发送未确认 │ 可以发送 │ 不能发送 │
│ │ (在窗口内) │ (窗口内空闲) │ (窗口外) │
│ │ │ │ │
│ 1-1000 │ 1001-2000 2001-3000 │ 3001-4000 │ 4001+ │
│ │ │ │ │
│ └─────────窗口大小(4000字节)────────┘ │
│ │
│ 当收到ACK=2001时: │
│ 窗口向右滑动1000字节 │
│ │
│ 已确认 │ 已发送未确认 │ 可以发送 │ 不能发送 │
│ 1-2000 │ 2001-3000 3001-4000│ 4001-5000 │ 5001+ │
│ │
└──────────────────────────────────────────────────────────────┘
窗口大小由谁决定?
接收方通过TCP头部的"窗口大小"字段告诉发送方:"我的缓冲区还能收多少数据。"
这就是流量控制。
4.3 拥塞控制
流量控制是接收方控制发送速度,拥塞控制是根据网络状况控制发送速度。
拥塞控制的四个阶段
发送速率
│
│ ╭──────────────
│ ╱ 拥塞避免(线性增加)
│ ╱
│ ╭───────╯
│ ╱ 慢启动
│ ╱ (指数增长)
│ ╱
│ ─────╯
│ 慢启动阈值
└────────────────────────────────────→ 时间
│ │
│ │发生丢包
│ │↓
│ │快重传+快恢复
│ │(阈值减半,窗口减半)
用大白话解释
慢启动:刚开始不知道网络多宽,小心翼翼地试探,每次加倍
拥塞避免:到了阈值后,改成每次加1,别太激进
快重传:收到3个重复ACK,说明某个包丢了,立即重传,不等超时
快恢复:丢包后不从1开始,而是从阈值的一半开始
5. UDP:简单粗暴但高效
UDP头部结构,简单得令人发指:
┌─────────────────────────────────────────────────────────────┐
│ UDP数据包头部 │
├──────────────────────────┬──────────────────────────────────┤
│ 源端口 (16bit) │ 目的端口 (16bit) │
├──────────────────────────┼──────────────────────────────────┤
│ 长度 (16bit) │ 校验和 (16bit) │
├──────────────────────────┴──────────────────────────────────┤
│ 数据 │
└─────────────────────────────────────────────────────────────┘
就这?就这!才8字节!TCP头部至少20字节。
UDP的应用场景
| 场景 | 为什么用UDP |
|---|---|
| DNS查询 | 请求小、响应快,偶尔丢一次重查就行 |
| 视频直播 | 实时性要求高,丢几帧不如延迟大卡顿 |
| 游戏 | 位置信息实时更新,丢了用新数据覆盖就行 |
| QUIC(HTTP/3) | 基于UDP实现,自己做可靠性控制 |
六、应用层:HTTP协议实战
1. HTTP请求响应流程
┌─────────────────────────────────────────────────────────────────┐
│ HTTP请求响应过程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 浏览器 服务器 │
│ │ │ │
│ │ 1. DNS解析:baidu.com → 39.156.66.10 │ │
│ │ │ │
│ │ 2. TCP三次握手 │ │
│ │ ←──────────────────────────────────────────────→ │ │
│ │ │ │
│ │ 3. 发送HTTP请求 │ │
│ │ GET / HTTP/1.1 │ │
│ │ Host: baidu.com │ │
│ │ ─────────────────────────────────────────────────→│ │
│ │ │ │
│ │ 4. 返回HTTP响应 │ │
│ │ HTTP/1.1 200 OK │ │
│ │ Content-Type: text/html │ │
│ │ <html>... │ │
│ │ ←─────────────────────────────────────────────────│ │
│ │ │ │
│ │ 5. TCP四次挥手(或保持长连接) │ │
│ │ │ │
└─────────────────────────────────────────────────────────────────┘
2. 实战:用telnet手动发HTTP请求
这是V哥最喜欢的学习方式------手动发请求,理解每一个字节。
bash
# 连接到百度的80端口
telnet www.baidu.com 80
# 连接成功后,输入以下内容(注意最后要按两次回车):
GET / HTTP/1.1
Host: www.baidu.com
# 你会看到HTTP响应
HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store...
Content-Type: text/html
...
3. 实战:用netcat做更多事情
bash
# 安装nc
sudo apt install netcat
# 发送HTTP请求
echo -e "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n" | nc example.com 80
# 监听端口,做一个简易服务器
nc -l 8080
# 另一个终端访问
curl http://localhost:8080
# 你会在nc那边看到HTTP请求内容
4. 实战:完整抓包分析
让我们用Wireshark抓一个完整的HTTP请求过程:
bash
# 1. 开启Wireshark,选择网卡
# 2. 设置过滤器:tcp.port == 80
# 3. 执行curl
curl http://example.com
# 4. 停止抓包,分析
你应该能看到这样的包序列:
No. Time Source Destination Protocol Info
1 0.000000 192.168.1.100 93.184.216.34 TCP SYN
2 0.050000 93.184.216.34 192.168.1.100 TCP SYN,ACK
3 0.050100 192.168.1.100 93.184.216.34 TCP ACK
4 0.050200 192.168.1.100 93.184.216.34 HTTP GET / HTTP/1.1
5 0.100000 93.184.216.34 192.168.1.100 TCP ACK
6 0.100100 93.184.216.34 192.168.1.100 HTTP HTTP/1.1 200 OK
7 0.100200 192.168.1.100 93.184.216.34 TCP ACK
8 0.100300 192.168.1.100 93.184.216.34 TCP FIN,ACK
9 0.150000 93.184.216.34 192.168.1.100 TCP FIN,ACK
10 0.150100 192.168.1.100 93.184.216.34 TCP ACK
V哥解读
- 第1-3包:TCP三次握手
- 第4包:HTTP请求
- 第5包:服务端确认收到请求
- 第6包:HTTP响应
- 第7包:客户端确认收到响应
- 第8-10包:TCP四次挥手(这里简化成了三次,因为FIN和ACK合并了)
七、网络诊断工具箱
V哥把常用的网络诊断工具给你总结一下:
1. 连通性测试
bash
# ping - 测试基本连通性
ping -c 4 google.com
# traceroute - 追踪路由路径
traceroute google.com
# 或者更好用的mtr
mtr google.com
# telnet - 测试端口连通性
telnet google.com 80
# nc - 更强大的网络工具
nc -zv google.com 80 # 测试端口
nc -l 8080 # 监听端口
2. DNS诊断
bash
# nslookup - 基本DNS查询
nslookup google.com
# dig - 更详细的DNS查询
dig google.com
dig google.com +trace # 追踪DNS解析过程
dig @8.8.8.8 google.com # 指定DNS服务器
# host - 简洁的DNS查询
host google.com
3. 连接状态
bash
# netstat - 查看网络连接
netstat -ant # 所有TCP连接
netstat -anu # 所有UDP连接
netstat -tlnp # 监听中的端口(带进程)
# ss - netstat的现代替代品(更快)
ss -ant # 所有TCP连接
ss -tlnp # 监听中的端口
ss -s # 统计信息
# lsof - 查看端口被谁占用
lsof -i :80 # 谁在用80端口
lsof -i -P -n # 所有网络连接
4. 抓包分析
bash
# tcpdump - 命令行抓包
tcpdump -i eth0 # 抓取eth0的所有包
tcpdump -i eth0 port 80 # 只抓80端口
tcpdump -i eth0 host 192.168.1.1 # 只抓特定主机
tcpdump -i eth0 -w capture.pcap # 保存到文件
tcpdump -r capture.pcap # 读取文件
# Wireshark - 图形界面抓包(前面已经用过了)
5. 网络性能测试
bash
# iperf3 - 带宽测试
# 服务端
iperf3 -s
# 客户端
iperf3 -c server_ip
# 测试UDP
iperf3 -c server_ip -u -b 100M
# curl - 测试HTTP性能
curl -o /dev/null -s -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" http://example.com
八、常见问题排查实战
案例1:网站打不开,怎么排查?
bash
# 第一步:DNS能解析吗?
nslookup example.com
# 如果失败:DNS问题,检查/etc/resolv.conf
# 第二步:能ping通吗?
ping example.com
# 如果失败:网络不通,检查路由
# 第三步:端口通吗?
telnet example.com 80
# 如果失败:端口被防火墙挡了,或服务没启动
# 第四步:HTTP正常吗?
curl -v http://example.com
# 看具体报错信息
案例2:连接超时,怎么定位?
bash
# 抓包看看
tcpdump -i eth0 host target_ip and port 80 -nn
# 可能的情况:
# 1. 只有SYN没有SYN-ACK → 服务端没收到或没响应
# 2. 有SYN-ACK但没有最后的ACK → 客户端问题
# 3. 三次握手完成但请求没响应 → 应用层问题
案例3:大量TIME_WAIT,怎么处理?
bash
# 查看TIME_WAIT数量
ss -ant | grep TIME-WAIT | wc -l
# 如果太多(几万个),可以调整内核参数
# 注意:生产环境慎用,要理解原理
# 允许TIME_WAIT的socket重用
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
# 快速回收TIME_WAIT
echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle # 注意:NAT环境下可能有问题
# 减少TIME_WAIT超时时间(默认60秒)
# 这个不能直接改,需要重新编译内核
案例4:网络时好时坏,怎么监控?
bash
# 使用mtr持续监控
mtr --report-cycles 100 google.com
# 会输出统计信息:
# Loss% - 丢包率
# Snt - 发送数量
# Avg - 平均延迟
# Best - 最小延迟
# Wrst - 最大延迟
# StDev - 标准差
# 看哪一跳丢包严重,定位问题节点
九、V哥的总结图
最后,V哥给你画一张全景图,帮你串起来:
┌─────────────────────────────────────────────────────────────────┐
│ TCP/IP协议栈全景图 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 应用层 │ │
│ │ │ │
│ │ HTTP HTTPS FTP SSH DNS SMTP ... │ │
│ │ │ │ │ │ │ │ │ │
│ └───┼──────┼─────┼────┼────┼────┼────────────────────────┘ │
│ │ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 传输层 │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────┐ │ │
│ │ │ TCP │ │ UDP │ │ │
│ │ │ • 可靠传输 │ │ • 不可靠传输 │ │ │
│ │ │ • 三次握手 │ │ • 无连接 │ │ │
│ │ │ • 四次挥手 │ │ • 头部仅8字节 │ │ │
│ │ │ • 流量控制 │ │ • 快速高效 │ │ │
│ │ │ • 拥塞控制 │ │ │ │ │
│ │ └────────┬─────────┘ └────────┬─────────┘ │ │
│ └───────────┼──────────────────────┼──────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 网络层 │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ IP │ │ │
│ │ │ • IP地址:标识网络中的设备 │ │ │
│ │ │ • 路由:规划数据包的传输路径 │ │ │
│ │ │ • 分片:大包拆成小包 │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ │ │
│ │ │ ICMP │ │ ARP │ │ │
│ │ │ ping/trace │ │ IP→MAC │ │ │
│ │ └───────────┘ └───────────┘ │ │
│ └─────────────────────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 数据链路层 + 物理层 │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────┐ │ │
│ │ │ 以太网帧 │ │ │
│ │ │ • MAC地址:设备的物理地址 │ │ │
│ │ │ • 帧格式:目标MAC + 源MAC + 类型 + 数据 + FCS │ │ │
│ │ └───────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ 物理介质:双绞线、光纤、无线电波 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
数据封装过程:
发送数据时,从上往下,每层加个头:
应用层数据
↓ +TCP头
[TCP头][应用层数据]
↓ +IP头
[IP头][TCP头][应用层数据]
↓ +以太网帧头+帧尾
[帧头][IP头][TCP头][应用层数据][帧尾]
↓
电信号发出去~~~
接收数据时,从下往上,每层拆一层:
[帧头][IP头][TCP头][应用层数据][帧尾]
↓ 拆帧头帧尾
[IP头][TCP头][应用层数据]
↓ 拆IP头
[TCP头][应用层数据]
↓ 拆TCP头
应用层数据 → 交给应用程序处理
十、面试高频问题速答
最后,V哥给你准备了几个面试必问的问题,背熟了:
Q1:TCP三次握手过程?为什么是三次?
过程:
1. 客户端→服务端:SYN=1, seq=x(我想连接,我的序列号是x)
2. 服务端→客户端:SYN=1, ACK=1, seq=y, ack=x+1(同意,我的序列号是y)
3. 客户端→服务端:ACK=1, seq=x+1, ack=y+1(收到,开始通信)
为什么三次:
- 确认双方的收发能力都正常
- 防止历史连接请求造成混乱
- 两次可能导致服务端建立无效连接
Q2:TCP四次挥手过程?为什么是四次?
过程:
1. 主动方→被动方:FIN=1(我要关了)
2. 被动方→主动方:ACK=1(知道了,但我可能还有数据要发)
3. 被动方→主动方:FIN=1(我也发完了,可以关了)
4. 主动方→被动方:ACK=1(好的,再见)
为什么四次:
- 关闭连接时,被动方可能还有数据没发完
- 第2步和第3步不能合并
- (但如果没有数据要发,有时会看到三次挥手,因为FIN和ACK合并了)
Q3:TCP如何保证可靠传输?
1. 序列号和确认号:每个字节都有编号,接收方确认收到
2. 超时重传:发出去没收到确认就重发
3. 滑动窗口:流量控制,防止发送方发太快
4. 拥塞控制:根据网络状况调整发送速度
5. 校验和:检测数据是否损坏
Q4:TCP和UDP的区别?各自适用场景?
区别:
- TCP:可靠、有序、有连接、慢
- UDP:不可靠、可能乱序、无连接、快
场景:
- TCP:网页、文件传输、邮件、数据库连接
- UDP:DNS、视频直播、游戏、VoIP
Q5:浏览器输入URL到页面展示,发生了什么?
1. DNS解析:域名→IP地址
2. TCP连接:三次握手
3. 发送HTTP请求
4. 服务器处理请求,返回HTTP响应
5. 浏览器解析HTML、CSS、JS
6. 渲染页面
7. 断开连接(或保持长连接)
写在最后
好了兄弟们,TCP/IP这套东西今天就讲到这里。
从底层的物理层、数据链路层的以太网帧和MAC地址,到网络层的IP地址和路由,再到传输层的TCP三次握手、四次挥手、滑动窗口,最后到应用层的HTTP------这一路下来,你应该对网络有了一个系统的认识。
V哥想说的是 :网络这东西,光看书是学不会的。一定要动手抓包,亲眼看到那些SYN、ACK在飞,才能真正理解。
建议你把这篇文章收藏了,然后:
- 装个Wireshark
- 按照V哥的步骤抓几个包
- 面试前再复习一遍
有问题评论区见,V哥看到都会回复。
下期咱们聊聊Linux内核网络协议栈源码解析------是的,看看内核里这些协议到底是怎么实现的。不见不散!
V哥碎碎念:这篇文章写了快12000字,手指头都敲木了。如果对你有帮助,点个赞、转发一下,让更多兄弟告别"面向面试背协议"!