TCP/IP协议栈详细解析
一、TCP头部结构详解
TCP头部格式(20字节基本头部 + 最多40字节选项)
text
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16位源端口 | 16位目的端口 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32位序列号 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 32位确认号 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据 | 保留 | U | A | P | R | S | F | 16位窗口大小 |
| 偏移 | | R | C | S | S | Y | I |(接收缓冲区剩余大小)|
| 4位 | 6位 | G | K | H | T | N | N | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 16位校验和 | 16位紧急指针 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 可选选项(最多40字节) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 数据 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
各字段详细说明
1. 端口相关字段
-
16位源端口(Source Port):发送方应用程序的端口号
-
16位目的端口(Destination Port):接收方应用程序的端口号
-
端口范围:0-65535(0-1023为系统保留端口)
2. 序列控制字段
-
32位序列号(Sequence Number):当前数据包的序号
-
用于标识数据包的顺序
-
防止数据包乱序
-
初始序列号在建立连接时协商
-
-
32位确认号(Acknowledgment Number):期望收到的下一个数据包的序号
-
表示已经成功收到的数据
-
采用累积确认机制
-
3. 头部长度和标志位
-
4位头部长度(Data Offset):TCP头部长度(以4字节为单位)
-
最小值为5(表示20字节)
-
最大值为15(表示60字节)
-
-
6位保留位(Reserved):保留未使用,必须设为0
4. 6个控制标志位(各占1位)
URG ACK PSH RST SYN FIN
1 1 0 0 1 0 ← 例如:SYN+ACK包
-
URG(Urgent):紧急数据标志
-
当URG=1时,表示有紧急数据
-
紧急指针字段有效
-
-
ACK(Acknowledgment):确认标志
-
当ACK=1时,确认号字段有效
-
建立连接后所有数据包ACK都应为1
-
-
PSH(Push):推送标志
-
当PSH=1时,要求接收方立即将数据交给应用程序
-
避免缓冲区延迟
-
-
RST(Reset):重置连接标志
- 当RST=1时,表示连接出现严重错误,需要重建连接
-
SYN(Synchronize):同步序列号标志
-
当SYN=1时,表示请求建立连接
-
在三次握手的前两个包中使用
-
-
FIN(Finish):结束连接标志
-
当FIN=1时,表示发送方数据已发送完毕,请求关闭连接
-
在四次挥手时使用
-
5. 流量控制字段
-
16位窗口大小(Window Size):接收方缓冲区剩余空间
-
用于流量控制,防止发送方发送过快
-
实现滑动窗口协议的基础
-
接收端通过此字段告知发送端还能接收多少数据
-
6. 校验和紧急指针
-
16位校验和(Checksum):用于检测头部和数据的错误
-
覆盖TCP头部、数据和伪头部
-
接收方通过校验和验证数据完整性
-
-
16位紧急指针(Urgent Pointer):紧急数据的偏移量
-
当URG=1时有效
-
指向紧急数据的最后一个字节的位置
-
7. 可选选项
-
可选选项(Options):最多40字节的额外信息
-
最大报文段大小(MSS)
-
窗口扩大因子
-
时间戳
-
选择性确认(SACK)
-
无操作(NOP)等
-
紧急数据(带外数据)处理
// 发送紧急数据(带外数据)
send(conn_fd, buf, sizeof(buf), MSG_OOB); // MSG_OOB标志表示紧急数据
// 接收紧急数据
recv(conn_fd, buf, sizeof(buf), MSG_OOB);
// 设置socket选项(一次一个紧急数据)
int opt = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_OOBINLINE, &opt, sizeof(opt));
TCP缓冲区大小
-
发送缓冲区:默认约2KB
-
接收缓冲区:默认约256KB
-
可以通过setsockopt()函数调整
二、IP头部结构详解
IP头部格式(20字节基本头部 + 最多40字节选项)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 版本 | 头长 | 服务类型 | 总长度 |
| 4位 | 4位 | TOS 8位 | 16位 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 标识(16位) | 标志 | 段偏移 |
| | 3位 | 13位 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 生存时间 | 协议 | 头部校验和 |
| TTL 8位 | 8位 | 16位 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 源IP地址(32位) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 目的IP地址(32位) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 可选选项(最多40字节) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
各字段详细说明
1. 版本和头部长度
-
4位版本号(Version):IP协议版本
-
IPv4:值为4
-
IPv6:值为6
-
-
4位头部长度(IHL):IP头部长度(以4字节为单位)
-
最小值为5(表示20字节)
-
最大值为15(表示60字节)
-
2. 服务类型和总长度
-
8位服务类型(TOS,Type of Service):服务质量参数
-
用于区分不同服务的优先级
-
包含:优先级、延迟、吞吐量、可靠性、成本
-
-
16位总长度(Total Length):IP数据包总长度
-
包括IP头部和数据部分
-
最大值为65535字节
-
受MTU限制
-
3. 分片相关字段
-
16位标识(Identification):数据包标识
-
用于标识属于同一原始数据包的所有分片
-
发送方为每个数据包分配唯一标识
-
-
3位标志(Flags):分片控制标志
-
第1位:保留,必须为0
-
第2位:不分片(DF,Don't Fragment)
-
DF=1:不允许分片
-
DF=0:允许分片
-
-
第3位:更多分片(MF,More Fragments)
-
MF=1:后面还有分片
-
MF=0:这是最后一个分片或没有分片
-
-
-
13位段偏移(Fragment Offset):分片偏移量
-
表示当前分片在原数据包中的位置
-
以8字节为单位
-
4. 生存时间和协议
-
8位生存时间(TTL,Time To Live):数据包生存时间
-
每经过一个路由器减1
-
当TTL=0时,数据包被丢弃
-
防止数据包在网络中无限循环
-
-
8位协议(Protocol):上层协议类型
-
ICMP:1
-
TCP:6
-
UDP:17
-
其他协议有相应的编号
-
-
16位头部校验和(Header Checksum):IP头部校验和
-
只校验IP头部
-
每个路由器都需要重新计算
-
5. IP地址
-
32位源IP地址(Source Address):发送方IP地址
-
32位目的IP地址(Destination Address):接收方IP地址
6. MTU概念
-
MTU(Maximum Transmission Unit):最大传输单元
-
指数据链路层能传输的最大数据包大小
-
以太网MTU通常为1500字节
-
当IP数据包超过MTU时需要分片
-
三、以太网数据帧格式
以太网帧结构
+----------------+----------------+----------------+----------------+----------------+
| 目的MAC地址 | 源MAC地址 | 类型 | 数据 | CRC校验 |
| 6字节(48位) | 6字节(48位) | 2字节(16位) | 46-1500字节 | 4字节(32位) |
+----------------+----------------+----------------+----------------+----------------+
各字段详细说明
1. MAC地址字段
-
6字节目的物理地址(Destination MAC Address):接收方的MAC地址
-
如果目的MAC为FF:FF:FF:FF:FF:FF,表示广播地址
-
如果目的MAC为01:00:5E:xx:xx:xx,表示组播地址
-
-
6字节源物理地址(Source MAC Address):发送方的MAC地址
2. 类型字段
-
2字节类型(Type/EtherType):上层协议类型
-
0x0800:IPv4协议
-
0x0806:ARP协议(地址解析协议)
-
0x86DD:IPv6协议
-
0x8035:RARP协议(反向地址解析协议)
-
3. 数据字段
-
46-1500字节数据(Data/Payload):有效载荷
-
最小46字节:如果数据不足46字节需要填充
-
最大1500字节:受MTU限制
-
包含IP头部、传输层头部和应用层数据
-
4. 校验字段
-
4字节CRC校验(Frame Check Sequence):循环冗余校验
-
用于检测数据帧在传输过程中是否出错
-
接收方通过CRC校验验证数据完整性
-
特殊协议说明
-
ping命令:使用ICMP协议(Internet控制报文协议)
-
ARP协议:用于IP地址到MAC地址的解析
四、TCP服务器/客户端编程模型
TCP服务器编程流程
// 1. 创建socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 绑定地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 3. 监听连接
listen(sock_fd, 5); // 5为等待队列长度
// 4. 接受连接
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int conn_fd = accept(sock_fd, (struct sockaddr*)&client_addr, &addr_len);
// 5. 发送数据
send(conn_fd, buffer, strlen(buffer), 0);
// 6. 接收数据
recv(conn_fd, buffer, sizeof(buffer), 0);
// 7. 关闭连接
close(conn_fd);
close(sock_fd);
TCP客户端编程流程
// 1. 创建socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 可以bind(但不建议,系统会自动分配)
// struct sockaddr_in client_addr;
// bind(sock_fd, (struct sockaddr*)&client_addr, sizeof(client_addr));
// 3. 连接服务器
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
inet_pton(AF_INET, "192.168.1.100", &server_addr.sin_addr);
connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 4. 接收数据
recv(sock_fd, buffer, sizeof(buffer), 0);
// 5. 发送数据
send(sock_fd, buffer, strlen(buffer), 0);
// 6. 关闭连接
close(sock_fd);
关键点说明
-
服务器必须bind:指定监听端口
-
客户端不建议bind:系统会自动分配端口
-
listen的第二个参数:等待连接队列的最大长度
-
accept会阻塞:直到有客户端连接
-
connect会阻塞:直到连接成功或失败
-
send/recv可能阻塞:取决于socket是否设置为非阻塞模式