TCP/IP协议栈深度解析:从底层搞懂网络通信

大家好,我是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在飞,才能真正理解。

建议你把这篇文章收藏了,然后:

  1. 装个Wireshark
  2. 按照V哥的步骤抓几个包
  3. 面试前再复习一遍

有问题评论区见,V哥看到都会回复。

下期咱们聊聊Linux内核网络协议栈源码解析------是的,看看内核里这些协议到底是怎么实现的。不见不散!


V哥碎碎念:这篇文章写了快12000字,手指头都敲木了。如果对你有帮助,点个赞、转发一下,让更多兄弟告别"面向面试背协议"!

相关推荐
J_liaty2 小时前
RPC、Feign与OpenFeign技术对比详解
网络·网络协议·rpc·openfeign·feign
wb043072012 小时前
TCP/IP(IP、TCP、UDP、ICMP)与 HTTP/HTTPS
tcp/ip·http·udp
venus602 小时前
多网卡如何区分路由,使用宽松模式测试网络
开发语言·网络·php
专家大圣2 小时前
Tomcat+cpolar 让 Java Web 应用跨越局域网随时随地可访问
java·前端·网络·tomcat·内网穿透·cpolar
guygg882 小时前
C#实现的TCP/UDP网络调试助手
网络·tcp/ip·c#
头发还没掉光光3 小时前
Linux网络之IP协议
linux·运维·网络·c++·tcp/ip
csdn_aspnet10 小时前
TCP/IP协议栈深度解析:从基石到前沿
服务器·网络·tcp/ip
LaoZhangGong12312 小时前
学习TCP/IP的第3步:和SYN相关的数据包
stm32·单片机·网络协议·tcp/ip·以太网