IP协议、以太网包头及UNIX域套接字

发布于:2025-05-13 ⋅ 阅读:(14) ⋅ 点赞:(0)

IP协议、以太网包头及UNIX域套接字

IP包头结构

IP协议是互联网的核心协议之一,其包头包含了丰富的信息来控制数据包的传输。让我们详细解析IPv4包头结构:

  1. 4位版本号(version):标识IP协议版本,IPv4值为4

  2. 4位首部长度(header length):以4字节为单位表示IP头长度,基础长度为20字节(5个单位),最大60字节

  3. 8位服务类型(Type Of Service):
    • 3位优先权字段(已弃用)

    • 4位TOS字段:可设置最小延时、最大吞吐量、最高可靠性或最小成本

    • 1位保留字段(必须为0)

  4. 16位总长度(total length):IP报文(头+数据)的总长度

  5. 16位标识(id):唯一标识主机发送的报文,分片报文id相同

  6. 3位标志字段:
    • 第1位:保留

    • 第2位:禁止分片(DF),置1时若报文超MTU则丢弃

    • 第3位:更多分片(MF),分片时除最后一片外都置1

  7. 13位片偏移(fragment offset):分片相对于原始数据的偏移,实际偏移=值×8

  8. 8位生存时间(TTL):最大跳数,防路由循环,每经一跳减1

  9. 8位协议:标识上层协议类型

  10. 16位首部检验和:仅校验头部,不检验数据部分

  11. 32位源IP地址

  12. 32位目标IP地址

  13. 选项字段:可变长,最多40字节

最大传输单元(MTU)

MTU(Maximum Transmission Unit)是数据链路层能传输的最大数据帧大小,默认1500字节。当IP数据包超过MTU时:

• 若DF标志为0,则进行分片

• 若DF标志为1,则丢弃数据包

分片规则:
• 除最后一片外,其他分片长度必须是8的整数倍

• 接收方根据标识、片偏移和MF标志重组数据

以太网包头结构

以太网是应用最广泛的局域网技术,其帧结构如下:

  1. 前导码(Preamble):7字节0x55,用于时钟同步

  2. 帧起始定界符(SFD):1字节0xD5,标识帧开始

  3. 目的MAC地址:6字节
    • 单播地址:首字节最低位为0

    • 组播地址:首字节最低位为1

    • 广播地址:全FF

  4. 源MAC地址:6字节

  5. 类型/长度字段:2字节
    • <0x0600:表示数据长度

    • ≥0x0600:表示协议类型(如0x0800为IP)

  6. 数据:46-1500字节(MTU)

  7. 校验(FCS):4字节CRC校验

  8. 帧间隙(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/开头

注意事项

  1. 使用UNIX域套接字前应调用unlink()确保路径可用
  2. 通信完成后应删除套接字文件
  3. 数据报套接字是无连接的,需在每次通信时指定地址
  4. 流式套接字提供可靠的双向字节流通信

网站公告

今日签到

点亮在社区的每一天
去签到