目录
[1. TCP/IP模型概述](#1. TCP/IP模型概述)
[四层模型 vs OSI七层模型](#四层模型 vs OSI七层模型)
[2. 各层协议详解](#2. 各层协议详解)
[2.1 应用层协议](#2.1 应用层协议)
[2.2 传输层协议](#2.2 传输层协议)
[TCP vs UDP 对比](#TCP vs UDP 对比)
[2.3 网络层协议](#2.3 网络层协议)
[2.4 网络接口层协议](#2.4 网络接口层协议)
[3. TCP协议深度解析](#3. TCP协议深度解析)
[3.1 TCP头部结构](#3.1 TCP头部结构)
[3.2 TCP三次握手](#3.2 TCP三次握手)
[3.3 TCP四次挥手](#3.3 TCP四次挥手)
[3.4 TCP可靠性机制](#3.4 TCP可靠性机制)
[4. UDP协议深度解析](#4. UDP协议深度解析)
[4.1 UDP头部结构](#4.1 UDP头部结构)
[4.2 UDP特点和应用](#4.2 UDP特点和应用)
[4.3 基于UDP的可靠传输协议](#4.3 基于UDP的可靠传输协议)
[5. 端口和套接字](#5. 端口和套接字)
[5.1 知名端口](#5.1 知名端口)
[编程视角:Socket API 简析](#编程视角:Socket API 简析)
[6. 网络编程实践](#6. 网络编程实践)
[6.1 TCP粘包处理](#6.1 TCP粘包处理)
[7. 面试常见问题](#7. 面试常见问题)
[Q1: TCP和UDP的主要区别?](#Q1: TCP和UDP的主要区别?)
[Q2: 为什么需要三次握手?](#Q2: 为什么需要三次握手?)
[Q3: TIME_WAIT状态的作用?](#Q3: TIME_WAIT状态的作用?)
[Q4: TCP拥塞控制算法有哪些?](#Q4: TCP拥塞控制算法有哪些?)
[Q5: UDP如何实现可靠性?](#Q5: UDP如何实现可靠性?)
1. TCP/IP模型概述
四层模型 vs OSI七层模型
cpp
OSI七层模型:理论模型
TCP/IP四层模型:实际实现的模型
+----------------+----------------+
| OSI模型 | TCP/IP模型 |
+----------------+----------------+
| 应用层 | |
| 表示层 | 应用层 |
| 会话层 | |
+----------------+----------------+
| 传输层 | 传输层 |
+----------------+----------------+
| 网络层 | 网络层 |
+----------------+----------------+
| 数据链路层 | 网络接口层 |
| 物理层 | |
+----------------+----------------+
数据封装过程
cpp
应用数据 → TCP/UDP段 → IP数据报 → 帧 → 比特流
↓ ↓ ↓ ↓ ↓
应用层 传输层 网络层 数据链路层 物理层
2. 各层协议详解
2.1 应用层协议
bash
# 常见的应用层协议
HTTP/HTTPS # 超文本传输协议(Web)
FTP/SFTP # 文件传输协议
SMTP # 简单邮件传输协议
DNS # 域名系统
DHCP # 动态主机配置协议
SSH # 安全外壳协议
2.2 传输层协议
TCP vs UDP 对比
特性 | TCP (传输控制协议) | UDP (用户数据报协议) |
---|---|---|
连接性 | 面向连接的 | 无连接的 |
可靠性 | 可靠交付(确认、重传、拥塞控制) | 尽最大努力交付(不保证不丢失、不重复) |
有序性 | 保证数据按序到达 | 不保证顺序 |
数据形式 | 面向字节流(无边界) | 面向报文(有边界,一次发送一个报文) |
首部开销 | 大 (20-60字节) | 小 (仅8字节) |
速度 | 慢(需要建立连接、确认等) | 快 |
控制机制 | 有流量控制 、拥塞控制 | 无 |
应用场景 | 可靠性 > 实时性的场景: Web (HTTP/HTTPS) 、Email 、文件传输(FTP)、数据库 | 实时性 > 可靠性的场景: 视频会议 、语音通话 、直播 、DNS 、QUIC |
核心记忆 :应用层 管内容 ,传输层 管端口 ,网络层 管IP地址 ,链路层 管MAC地址。
-
TCP是打电话:需要先建立连接,可靠,但啰嗦。
-
UDP是发短信:直接发,可能丢,但高效。
2.3 网络层协议
bash
# 核心网络层协议
IP # 网际协议(IPv4/IPv6)
ICMP # 互联网控制消息协议(ping)
ARP # 地址解析协议
RIP/OSPF/BGP # 路由协议
2.4 网络接口层协议
bash
# 数据链路层和物理层协议
Ethernet # 以太网
Wi-Fi # 无线局域网
PPP # 点对点协议
VLAN # 虚拟局域网
3. TCP协议深度解析
3.1 TCP头部结构
cpp
// TCP头部(20字节 + 可选字段)
struct tcp_header {
uint16_t src_port; // 源端口(2字节)
uint16_t dst_port; // 目的端口(2字节)
uint32_t seq_num; // 序列号(4字节)
uint32_t ack_num; // 确认号(4字节)
uint8_t data_offset; // 数据偏移(4位)
uint8_t flags; // 标志位(6位)
uint16_t window; // 窗口大小(2字节)
uint16_t checksum; // 校验和(2字节)
uint16_t urgent_ptr; // 紧急指针(2字节)
// 可选选项(0-40字节)
};
3.2 TCP三次握手
目的 :双方确认彼此的发送和接收能力正常,并同步初始序列号(ISN)。
bash
客户端 → 服务器:SYN=1, Seq=x
客户端 ← 服务器:SYN=1, ACK=1, Seq=y, Ack=x+1
客户端 → 服务器:ACK=1, Seq=x+1, Ack=y+1
建立连接,协商初始序列号
cpp
// 非常形象的伪代码描述
// 客户端 (Client) 服务端 (Server)
int client_sock = socket(); int server_sock = socket();
bind(server_sock); bind(server_sock);
listen(server_sock); // 进入LISTEN状态
// 1. SYN_SENT状态
send(client_sock, SYN=1, seq=J); -------> // 收到SYN
// 2. SYN_RCVD状态
<------- send(server_sock, SYN=1, ACK=1, seq=K, ack=J+1);
// 3. ESTABLISHED状态
send(client_sock, ACK=1, seq=J+1, ack=K+1); -> // 收到ACK
// 4. ESTABLISHED状态
为什么是三次?
-
根本原因 :防止已失效的连接请求报文突然又传送到服务器,导致错误。
-
简单理解 :两次握手不够。如果客户端的第一个SYN报文滞留了,客户端重发一个并建立连接。完成后,那个滞留的SYN报文才到达服务器,服务器会认为客户端又想建立新连接并同意,但客户端已关闭,导致服务器资源空等。三次握手避免了这个问题。
3.3 TCP四次挥手
目的:双方都确认要关闭连接。
cpp
客户端 → 服务器:FIN=1, Seq=u
客户端 ← 服务器:ACK=1, Seq=v, Ack=u+1
客户端 ← 服务器:FIN=1, ACK=1, Seq=w, Ack=u+1
客户端 → 服务器:ACK=1, Seq=u+1, Ack=w+1
优雅关闭连接,确保数据完整传输
cpp
// 客户端 (Client) 服务端 (Server)
// (双方都处于ESTABLISHED状态)
// 1. FIN_WAIT_1状态
send(client_sock, FIN=1, seq=U); -------> // 收到FIN
// 2. CLOSE_WAIT状态
<------- send(server_sock, ACK=1, seq=V, ack=U+1); // 第一次挥手完成
// 3. FIN_WAIT_2状态
... (等待服务器发FIN) ... (服务器可能还有数据要发送)
<------- send(server_sock, FIN=1, ACK=1, seq=W, ack=U+1); // 服务器数据发完了
// 4. TIME_WAIT状态
send(client_sock, ACK=1, seq=U+1, ack=W+1); -> // 收到ACK, 进入CLOSED状态
// 等待2MSL后进入CLOSED状态
为什么是四次?
因为TCP连接是全双工的,每个方向都必须单独关闭。一方发送FIN只表示它没有数据要发送了,但还可以接收数据。
为什么需要TIME_WAIT状态?等待2MSL(最大报文段寿命)的原因:
-
可靠地终止连接:确保最后一个ACK能到达服务器。如果ACK丢失,服务器会重发FIN,客户端还能响应。
-
让旧连接的报文在网络中消散:避免之前连接的延迟报文被新建立的相同IP和端口的连接错误接收。
3.4 TCP可靠性机制
-
确认与重传:接收方收到数据后要发送ACK确认。发送方在一定时间(RTO)内没收到ACK,会重传数据。
-
序列号与确认号:保证数据按序到达。
-
流量控制 :使用滑动窗口 协议,接收方通过通告窗口大小来告诉发送方自己还能接收多少数据,防止发送过快导致接收方缓冲区溢出。
-
拥塞控制:防止网络过载。核心算法:
-
慢启动:窗口从1开始,按指数增长。
-
拥塞避免:窗口超过慢启动阈值(ssthresh)后,按线性增长。
-
快重传:收到3个重复ACK立即重传,而不等超时。
-
快恢复:快重传后,直接进入拥塞避免,而非慢启动。
-
超时重传
cpp
// 重传计时器管理
void tcp_retransmit(struct tcp_connection* conn) {
if (!ack_received_within_timeout()) {
retransmit_packet(); // 重传数据包
double_timeout_interval(); // 指数退避
}
}
滑动窗口
cpp
发送方窗口:[已确认][已发送未确认][可发送][不可发送]
接收方窗口:[已接收][可接收][不可接收]
通过窗口大小实现流量控制
拥塞控制算法
cpp
// TCP拥塞控制状态机
void tcp_congestion_control(struct tcp_connection* conn) {
switch (conn->state) {
case SLOW_START:
// 指数增长:cwnd *= 2每个RTT
if (conn->cwnd >= conn->ssthresh) {
conn->state = CONGESTION_AVOIDANCE;
}
break;
case CONGESTION_AVOIDANCE:
// 线性增长:cwnd += 1每个RTT
break;
case FAST_RECOVERY:
// 快速恢复算法
break;
}
}
4. UDP协议深度解析
4.1 UDP头部结构
bash
// UDP头部(8字节)
struct udp_header {
uint16_t src_port; // 源端口(2字节)
uint16_t dst_port; // 目的端口(2字节)
uint16_t length; // 长度(2字节)
uint16_t checksum; // 校验和(2字节)
};
4.2 UDP特点和应用
优点
-
低延迟:无连接建立开销
-
低开销:头部只有8字节
-
无拥塞控制:适合实时应用
典型应用场景
cpp
// 1. DNS查询
void dns_query() {
// UDP包:查询域名对应的IP
// 快速、简单,适合小数据包
}
// 2. 视频流媒体
void video_streaming() {
// 容忍少量丢包,但不能容忍延迟
// 使用UDP传输视频帧
}
// 3. 在线游戏
void online_gaming() {
// 需要低延迟,状态更新频繁
// 使用UDP传输游戏状态
}
// 4. VoIP语音通话
void voice_call() {
// 实时性要求高,可以容忍少量丢包
// 使用UDP传输语音数据
}
4.3 基于UDP的可靠传输协议
自定义可靠性机制
cpp
// 在应用层实现可靠性
struct reliable_udp {
uint32_t seq_num; // 序列号
uint32_t ack_num; // 确认号
uint8_t flags; // 标志位
uint16_t checksum; // 校验和
char data[]; // 数据
};
void udp_reliability() {
// 应用层实现:
// - 序列号和确认机制
// - 超时重传
// - 流量控制
// 例子:QUIC协议(HTTP/3)
}
5. 端口和套接字
5.1 知名端口
bash
# 常见TCP端口
80 # HTTP
443 # HTTPS
21 # FTP
22 # SSH
25 # SMTP
53 # DNS(也用于UDP)
# 常见UDP端口
53 # DNS
67/68 # DHCP
123 # NTP
161 # SNMP
编程视角:Socket API 简析
理解协议最终要落到编程上。
TCP Socket 编程流程:
cpp
// 服务器端
int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 1. 创建TCP Socket
bind(sockfd, ...); // 2. 绑定IP和端口
listen(sockfd, backlog); // 3. 开始监听
int connfd = accept(sockfd, ...); // 4. 接受连接 (阻塞,直到三次握手完成)
recv(connfd, buf, size, 0); // 5. 接收数据
send(connfd, buf, size, 0); // 6. 发送数据
close(connfd); // 7. 关闭连接 (触发四次挥手)
UDP Socket 编程流程:
cpp
// 服务器端
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 1. 创建UDP Socket
bind(sockfd, ...); // 2. 绑定IP和端口
recvfrom(sockfd, buf, size, 0, client_addr, ...); // 3. 接收数据 (同时获取客户端地址)
sendto(sockfd, buf, size, 0, client_addr, ...); // 4. 向该地址发送数据
-
必背题:
-
TCP和UDP的区别(至少说出连接性、可靠性、有序性、速度、首部开销)。
-
三次握手和四次挥手 的详细过程及为什么是三次/四次。
-
TIME_WAIT状态的作用。
-
-
必会分析:
-
能画出握手和挥手的状态变迁图。
-
能解释滑动窗口 、流量控制 和拥塞控制的基本思想。
-
能说出SYN洪泛攻击的原理(伪造IP发SYN,耗尽服务器的半连接队列)。
-
-
实战编程:
-
能说出TCP粘包/拆包的原因及解决方法(消息头定长、包尾加分隔符、自定义协议如 length + body)。
-
能简单描述如何使用Socket API编写TCP/UDP的服务器和客户端。
-
6. 网络编程实践
6.1 TCP粘包处理
cpp
// 解决方案1:定长消息
struct fixed_message {
uint32_t length; // 消息长度
char data[]; // 消息数据
};
// 解决方案2:分隔符
void send_with_delimiter(int sockfd, const char* data) {
send(sockfd, data, strlen(data), 0);
send(sockfd, "\r\n", 2, 0); // 添加分隔符
}
// 解决方案3:TLV格式(Type-Length-Value)
struct tlv_message {
uint8_t type; // 消息类型
uint32_t length; // 数据长度
char value[]; // 数据值
};
7. 面试常见问题
Q1: TCP和UDP的主要区别?
答:TCP是面向连接的、可靠的、基于流的协议,有流量控制和拥塞控制;UDP是无连接的、不可靠的、基于数据报的协议,传输效率更高。
Q2: 为什么需要三次握手?
答:三次握手确保双方都能确认对方的发送和接收能力正常,防止已失效的连接请求报文突然传到服务器导致错误。
Q3: TIME_WAIT状态的作用?
答:1) 确保最后一个ACK能够到达对方;2) 让旧连接的重复报文在网络中消失,避免影响新连接。
Q4: TCP拥塞控制算法有哪些?
答:慢启动、拥塞避免、快速重传、快速恢复。现代TCP还有CUBIC、BBR等算法。
Q5: UDP如何实现可靠性?
答:在应用层实现序列号、确认机制、重传计时器等,如QUIC协议所做的那样。
网络调试工具
cpp
# 常用网络工具
netstat # 查看网络连接状态
ss # netstat的替代品
tcpdump # 网络抓包分析
wireshark # 图形化抓包分析
iperf # 网络性能测试
nc # 网络调试工具
总结
TCP/IP模型是现代互联网的基石:
-
✅ 分层设计:各层职责明确,易于实现和维护
-
✅ TCP:可靠传输,适合需要数据完整性的应用
-
✅ UDP:高效传输,适合实时和延迟敏感的应用
-
✅ 协议选择:根据应用需求选择合适的传输层协议
-
✅ 性能优化:理解协议特性进行针对性调优