IP协议、以太网包头及UNIX域套接字
IP包头结构
IP协议是互联网的核心协议之一,其包头包含了丰富的信息来控制数据包的传输。让我们详细解析IPv4包头结构:
4位版本号(version):标识IP协议版本,IPv4值为4
4位首部长度(header length):以4字节为单位表示IP头长度,基础长度为20字节(5个单位),最大60字节
8位服务类型(Type Of Service):
• 3位优先权字段(已弃用)• 4位TOS字段:可设置最小延时、最大吞吐量、最高可靠性或最小成本
• 1位保留字段(必须为0)
16位总长度(total length):IP报文(头+数据)的总长度
16位标识(id):唯一标识主机发送的报文,分片报文id相同
3位标志字段:
• 第1位:保留• 第2位:禁止分片(DF),置1时若报文超MTU则丢弃
• 第3位:更多分片(MF),分片时除最后一片外都置1
13位片偏移(fragment offset):分片相对于原始数据的偏移,实际偏移=值×8
8位生存时间(TTL):最大跳数,防路由循环,每经一跳减1
8位协议:标识上层协议类型
16位首部检验和:仅校验头部,不检验数据部分
32位源IP地址
32位目标IP地址
选项字段:可变长,最多40字节
最大传输单元(MTU)
MTU(Maximum Transmission Unit)是数据链路层能传输的最大数据帧大小,默认1500字节。当IP数据包超过MTU时:
• 若DF标志为0,则进行分片
• 若DF标志为1,则丢弃数据包
分片规则:
• 除最后一片外,其他分片长度必须是8的整数倍
• 接收方根据标识、片偏移和MF标志重组数据
以太网包头结构
以太网是应用最广泛的局域网技术,其帧结构如下:
前导码(Preamble):7字节0x55,用于时钟同步
帧起始定界符(SFD):1字节0xD5,标识帧开始
目的MAC地址:6字节
• 单播地址:首字节最低位为0• 组播地址:首字节最低位为1
• 广播地址:全FF
源MAC地址:6字节
类型/长度字段:2字节
• <0x0600:表示数据长度• ≥0x0600:表示协议类型(如0x0800为IP)
数据:46-1500字节(MTU)
校验(FCS):4字节CRC校验
帧间隙(IFG):帧间最小间隔(96位时间)
UNIX域套接字详解
UNIX域套接字用于同一主机上的进程间通信(IPC),比网络套接字更高效。
流式套接字(SOCK_STREAM)
服务器端流程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/unix_socket_example"
int main() {
int server_fd, client_fd;
struct sockaddr_un addr;
char buffer[100];
// 1. 创建套接字
server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (server_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 绑定套接字到文件系统路径
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
unlink(SOCKET_PATH); // 确保路径没有被占用
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
perror("bind");
close(server_fd);
exit(EXIT_FAILURE);
}
// 3. 监听连接请求
if (listen(server_fd, 5) == -1) {
perror("listen");
close(server_fd);
exit(EXIT_FAILURE);
}
printf("服务器正在等待连接...\n");
// 4. 接受客户端连接
client_fd = accept(server_fd, NULL, NULL);
if (client_fd == -1) {
perror("accept");
close(server_fd);
exit(EXIT_FAILURE);
}
// 5. 与客户端通信
memset(buffer, 0, sizeof(buffer));
read(client_fd, buffer, sizeof(buffer));
printf("收到客户端消息: %s\n", buffer);
const char *response = "你好,客户端!";
write(client_fd, response, strlen(response) + 1);
// 6. 关闭套接字
close(client_fd);
close(server_fd);
unlink(SOCKET_PATH);
return 0;
}
客户端流程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/unix_socket_example"
int main() {
int client_fd;
struct sockaddr_un addr;
char buffer[100];
// 1. 创建套接字
client_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (client_fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 连接到服务器
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
if (connect(client_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
perror("connect");
close(client_fd);
exit(EXIT_FAILURE);
}
// 3. 与服务器通信
const char *message = "你好,服务器!";
write(client_fd, message, strlen(message) + 1);
memset(buffer, 0, sizeof(buffer));
read(client_fd, buffer, sizeof(buffer));
printf("收到服务器响应: %s\n", buffer);
// 4. 关闭套接字
close(client_fd);
return 0;
}
数据报套接字(SOCK_DGRAM)
服务器端流程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/unix_dgram_socket_example"
int main() {
int sockfd;
struct sockaddr_un addr;
char buffer[100];
ssize_t num_bytes;
struct sockaddr_un client_addr;
socklen_t client_addr_len = sizeof(client_addr);
// 1. 创建套接字
sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 绑定套接字
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
unlink(SOCKET_PATH);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("服务器正在等待消息...\n");
// 3. 接收消息
memset(buffer, 0, sizeof(buffer));
num_bytes = recvfrom(sockfd, buffer, sizeof(buffer), 0,
(struct sockaddr *)&client_addr, &client_addr_len);
if (num_bytes == -1) {
perror("recvfrom");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("收到客户端消息: %s\n", buffer);
// 4. 发送响应
const char *response = "你好,客户端!";
if (sendto(sockfd, response, strlen(response) + 1, 0,
(struct sockaddr *)&client_addr, client_addr_len) == -1) {
perror("sendto");
close(sockfd);
exit(EXIT_FAILURE);
}
// 5. 关闭套接字
close(sockfd);
unlink(SOCKET_PATH);
return 0;
}
客户端流程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/unix_dgram_socket_example"
int main() {
int sockfd;
struct sockaddr_un server_addr;
char buffer[100];
ssize_t num_bytes;
// 1. 创建套接字
sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 2. 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1);
// 3. 发送消息
const char *message = "你好,服务器!";
if (sendto(sockfd, message, strlen(message) + 1, 0,
(struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("sendto");
close(sockfd);
exit(EXIT_FAILURE);
}
// 4. 接收响应
memset(buffer, 0, sizeof(buffer));
num_bytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
if (num_bytes == -1) {
perror("recvfrom");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("收到服务器响应: %s\n", buffer);
// 5. 关闭套接字
close(sockfd);
return 0;
}
sockaddr_un结构体
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sun_family; // 地址族,设置为AF_UNIX
char sun_path[108]; // 文件系统路径名
};
• sun_family:必须设置为AF_UNIX
• sun_path:最大长度108字节(包括终止符),通常以/tmp/开头
注意事项
- 使用UNIX域套接字前应调用unlink()确保路径可用
- 通信完成后应删除套接字文件
- 数据报套接字是无连接的,需在每次通信时指定地址
- 流式套接字提供可靠的双向字节流通信