【网络编程】基于 DPDK 的 UDP/TCP 抓包与最简协议栈实现

一、前言:为什么要用 DPDK?

在传统的网络程序中,我们通常通过 Linux Socket APIrecvfrom / sendto / recv / send)来收发数据包。

但这种方式存在几个天然瓶颈:

  • 数据包必须经过 Linux 内核协议栈

  • 存在 系统调用开销

  • 中断、上下文切换频繁

  • 在高性能场景(10GbE、25GbE、100GbE)下容易成为瓶颈

为了解决这些问题,Intel 推出了 DPDK(Data Plane Development Kit)。

一句话理解 DPDK:

👉 它绕过 Linux 内核,直接让用户态程序接管网卡,用轮询的方式"抢"数据包。

本文将基于一份完整可运行的 DPDK 示例代码,实现:

  • UDP 数据包的 抓取 + 打印

  • UDP 数据包的 原样回包

  • TCP 三次握手的 最小状态机

  • TCP 数据的 解析与打印

二、整体功能概览

这份程序启动后,会做下面几件事:

  1. 初始化 DPDK 环境

  2. 直接接管网卡

  3. 轮询接收数据包

  4. 如果是:

    • UDP 包 → 打印 payload,并原样回包

    • TCP 包 → 手写 TCP 状态机,完成三次握手

  5. 整个过程完全绕过 Linux 内核

你可以把它理解为一个:

"用户态实现的、极简 UDP/TCP 协议栈 Demo"

三、核心宏定义与全局变量说明

1、mbuf 与 burst 相关参数

cpp 复制代码
#define NUM_MBUFS    4096
#define BURST_SIZE  128
  • NUM_MBUFS

    表示一次性创建多少个数据包缓冲区(mbuf)

  • BURST_SIZE

    每次从网卡批量收多少个包(批量处理能显著提高性能)

DPDK 的核心思想之一:批量 + 轮询

2、功能开关宏

cpp 复制代码
#define ENABLE_SEND  1
#define ENABLE_TCP   1
  • ENABLE_SEND:是否开启回包功能

  • ENABLE_TCP:是否处理 TCP 协议

3、TCP 状态机枚举

cpp 复制代码
typedef enum __USTACK_TCP_STATUS{
    USTACK_TCP_STATUS_CLOSED = 0,
    USTACK_TCP_STATUS_LISTEN,
    USTACK_TCP_STATUS_SYN_RCVD,
    USTACK_TCP_STATUS_ESTABLISHED,
    ...
}USTACK_TCP_STATUS;

这是一个简化版 TCP 状态机,用于:

  • 模拟服务器端 TCP 行为

  • 支持最基本的三次握手

四、DPDK 初始化流程

1、EAL 初始化(DPDK 的入口)

cpp 复制代码
rte_eal_init(argc, argv);

它会完成:

  • 大页内存初始化

  • CPU 核心绑定

  • PCI 网卡扫描

  • 驱动加载

如果这一步失败,后面一切都无法进行

2、创建 mbuf 内存池

cpp 复制代码
rte_pktmbuf_pool_create(
    "mbuf pool",
    NUM_MBUFS,
    0, 0,
    RTE_MBUF_DEFAULT_BUF_SIZE,
    rte_socket_id()
);

为什么要这么做?

  • DPDK 不允许你在收包时 malloc

  • 所有内存必须提前准备好

  • mbuf 是 DPDK 中 数据包的基本载体

性能的根基就在这里

五、网卡端口初始化

cpp 复制代码
rte_eth_dev_configure(...)
rte_eth_rx_queue_setup(...)
rte_eth_tx_queue_setup(...)
rte_eth_dev_start(...)

这一整套流程的作用是:

步骤 作用
dev_configure 配置 RX / TX 队列数量
rx_queue_setup 配置接收队列
tx_queue_setup 配置发送队列
dev_start 启动网卡

从这一刻开始:

Linux 内核协议栈基本"失业"了

六、主循环:DPDK 程序的灵魂

cpp 复制代码
while(1){
    rte_eth_rx_burst(...);
}

为什么是 while(1)?

  • DPDK 不使用中断

  • 完全靠 用户态轮询

  • 用 CPU 换极致低延迟

接收数据包(批量)

cpp 复制代码
uint16_t num_recvd = rte_eth_rx_burst(
    global_portid, 0,
    mbufs, BURST_SIZE
);
  • 一次最多收 BURST_SIZE 个包

  • 返回值是真实收到的数量

七、协议解析:从二层到四层

1、以太网头(L2)

cpp 复制代码
struct rte_ether_hdr *eth_hdr =
    rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr *);

判断是否是 IPv4:

cpp 复制代码
eth_hdr->ether_type == RTE_ETHER_TYPE_IPV4

2、IP 头(L3)

cpp 复制代码
struct rte_ipv4_hdr *iphdr =
    rte_pktmbuf_mtod_offset(...);

根据 next_proto_id 判断:

  • UDP

  • TCP

八、UDP 处理流程

1、读取 UDP payload

cpp 复制代码
printf("udp : %s\n", (char*)(udphdr + 1));

2、手动封装 UDP 回包

cpp 复制代码
ustack_encode_udp_pkt(...)

这个函数里手写了:

  • Ethernet Header

  • IPv4 Header

  • UDP Header

  • 校验和计算

这是"造包"的完整过程

3、发送数据包

cpp 复制代码
rte_eth_tx_burst(global_portid, 0, &mbuf, 1);

九、TCP 处理:最简三次握手实现

1、收到 SYN → 回 SYN-ACK

cpp 复制代码
if(global_flags & RTE_TCP_SYN_FLAG){
    if(tcp_status == LISTEN){
        send SYN-ACK;
        tcp_status = SYN_RCVD;
    }
}

2、收到 ACK → 连接建立

cpp 复制代码
if(global_flags & RTE_TCP_ACK_FLAG){
    if(tcp_status == SYN_RCVD){
        tcp_status = ESTABLISHED;
    }
}

3、收到 PSH → 读取数据

cpp 复制代码
uint8_t *data = ((uint8_t*)tcphdr + hdrlen);
printf("tcp data: %s\n", data);

这是 TCP payload 的真实位置

九、完整代码

cpp 复制代码
// 它绕过了Linux操作系统内核(Kernel),直接接管网卡,不断地从网卡"抢"数据包。
// 如果发现抢到的包是 IPv4 协议下的 UDP 数据包,它就去读取包里的内容并在屏幕上打印出来。

#include <stdio.h>              // 标准输入输出库,用于 printf 等
#include <rte_eal.h>            // DPDK 环境抽象层 (EAL) 头文件,负责底层资源管理
#include <rte_ethdev.h>         // DPDK 以太网设备 API,用于控制网卡收发包
#include <arpa/inet.h>          // 提供 IP 地址转换函数,如 inet_ntoa
#include <rte_ip.h>             // DPDK 定义的 IP 协议头结构
#include <rte_udp.h>            // DPDK 定义的 UDP 协议头结构

int global_portid = 0;          // 指定要使用的网卡端口 ID,通常从 0 开始

#define NUM_MBUFS    4096       // 内存池中 mbuf (数据包缓冲区) 的总数量
#define BURST_SIZE   128        // 每次从网卡收发数据包的批量大小 (批量处理减少开销)

#define ENABLE_SEND  1          // 宏开关:是否开启数据包发送功能 (回包)
#define ENABLE_TCP   1          // 宏开关:是否开启 TCP 协议处理

#define TCP_INIT_WINDOWS        14600  // 定义 TCP 初始窗口大小

#if ENABLE_SEND

uint8_t global_smac[RTE_ETHER_ADDR_LEN]; // 全局变量:用于存储源 MAC 地址
uint8_t global_dmac[RTE_ETHER_ADDR_LEN]; // 全局变量:用于存储目的 MAC 地址

uint32_t global_sip;            // 全局变量:源 IP 地址 (网络字节序)
uint32_t global_dip;            // 全局变量:目的 IP 地址 (网络字节序)

uint16_t global_sport;          // 全局变量:源端口号
uint16_t global_dport;          // 全局变量:目的端口号

#endif

#if ENABLE_TCP

uint8_t global_flags;           // 存储接收到的 TCP 标志位 (SYN, ACK, PSH 等)
uint32_t global_seqnum;         // 存储接收到的 TCP 序列号
uint32_t global_acknum;         // 存储接收到的 TCP 确认号

// 定义 TCP 连接状态枚举,对应 TCP 状态机
typedef enum __USTACK_TCP_STATUS{

    USTACK_TCP_STATUS_CLOSED = 0,      // 关闭状态
    USTACK_TCP_STATUS_LISTEN,          // 监听状态
    USTACK_TCP_STATUS_SYN_RCVD,        // 收到 SYN 包,半连接状态
    USTACK_TCP_STATUS_SYN_SENT,        // 发送了 SYN 包 (客户端模式用,此处未用)
    USTACK_TCP_STATUS_ESTABLISHED,     // 连接已建立
    USTACK_TCP_STATUS_FIN_WAIT_1,      // 断开连接第一阶段
    USTACK_TCP_STATUS_FIN_WAIT_2,      // 断开连接第二阶段
    USTACK_TCP_STATUS_CLOSING,         // 双方同时关闭
    USTACK_TCP_STATUS_TIMEWAIT,        // 等待足够时间以确保远端收到 ACK
    USTACK_TCP_STATUS_CLOSE_WAIT,      // 等待本地关闭
    USTACK_TCP_STATUS_LAST_ACK         // 等待最后的 ACK

}USTACK_TCP_STATUS;

uint8_t tcp_status = USTACK_TCP_STATUS_LISTEN; // 初始状态设置为 LISTEN (服务器模式)

#endif

// 定义默认的端口配置结构体
static const struct rte_eth_conf port_conf_default = {
    .rxmode = { .max_rx_pkt_len = RTE_ETHER_MAX_LEN } // 设置最大接收包长度为以太网标准最大长度 (1518)
};

// 这是 DPDK 的启动引擎。它会负责绑定 CPU 核心、扫描 PCI 设备(网卡)、配置大页内存。
static int ustack_init_port(struct rte_mempool *mbuf_pool){

    uint16_t nb_sys_ports = rte_eth_dev_count_avail(); // 获取系统中可用的 DPDK 网卡端口数量
    if(nb_sys_ports == 0){                             // 如果没有可用端口
        rte_exit(EXIT_FAILURE, "No available ports\n"); // 报错并退出程序
    }

    struct rte_eth_dev_info dev_info;                  // 用于存储网卡硬件信息的结构体
    rte_eth_dev_info_get(global_portid, &dev_info);    // 获取指定端口的硬件信息

    const int num_rx_queues = 1;                       // 配置 1 个接收队列
#if ENABLE_SEND
    const int num_tx_queues = 1;                       // 如果开启发送,配置 1 个发送队列
#else
    const int num_tx_queues = 0;                       // 否则配置 0 个发送队列
#endif
    // 配置网卡:端口ID、接收队列数、发送队列数、端口配置参数
    rte_eth_dev_configure(global_portid, num_rx_queues, num_tx_queues, &port_conf_default);

    // 设置接收队列:队列ID 0,队列深度 128,Socket ID,默认配置,内存池
    if(rte_eth_rx_queue_setup(global_portid, 0, 128, rte_eth_dev_socket_id(global_portid), NULL, mbuf_pool) < 0){
        rte_exit(EXIT_FAILURE, "Could not setup RX queue\n"); // 设置失败则退出
    }

#if ENABLE_SEND

    struct rte_eth_txconf txq_conf = dev_info.default_txconf; // 获取默认发送配置
    txq_conf.offloads = port_conf_default.rxmode.offloads;    // 同步 Offload 配置
    // 设置发送队列:队列ID 0,队列深度 512
    if(rte_eth_tx_queue_setup(global_portid, 0, 512, rte_eth_dev_socket_id(global_portid), &txq_conf) < 0){
        rte_exit(EXIT_FAILURE, "Could not setup TX queue\n"); // 设置失败则退出
    }

#endif

    // 真正启动网卡设备,开始工作
    if(rte_eth_dev_start(global_portid) < 0) {
        rte_exit(EXIT_FAILURE,"Could not start\n");
    }
    return 0;
}

// 辅助函数:手动封装 UDP 数据包
static int ustack_encode_udp_pkt(uint8_t *msg, uint8_t *data, uint16_t total_len){

    // ether header
    struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg; // 将缓冲区首地址强制转换为以太网头指针
    rte_memcpy(eth->d_addr.addr_bytes, global_dmac, RTE_ETHER_ADDR_LEN); // 填入目的 MAC 地址
    rte_memcpy(eth->s_addr.addr_bytes, global_smac, RTE_ETHER_ADDR_LEN); // 填入源 MAC 地址
    eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);                        // 设置上层协议类型为 IPv4 (主机字节序转网络字节序)

    // ip header
    struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr *)(eth +1); // 指针偏移,跳过以太网头,指向 IP 头
    ip->version_ihl = 0x45;                                    // 版本号4,头部长度5 (20字节)
    ip->type_of_service = 0;                                   // 服务类型 TOS
    ip->total_length = htons(total_len - sizeof(struct rte_ether_hdr)); // IP 包总长度 (总长减去以太网头)
    ip->packet_id = 0;                                         // 包 ID
    ip->fragment_offset = 0;                                   // 分片偏移,0 表示不分片
    ip->time_to_live = 64;                                     // TTL 生存时间
    ip->next_proto_id = IPPROTO_UDP;                           // 上层协议设置为 UDP
    ip->src_addr = global_sip;                                 // 填入源 IP
    ip->dst_addr = global_dip;                                 // 填入目的 IP

    ip->hdr_checksum = 0;                                      // 计算校验和前先置零
    ip->hdr_checksum = rte_ipv4_cksum(ip);                     // 计算 IP 头部校验和

    // udp header
    struct rte_udp_hdr *udp = (struct rte_udp_hdr *)(ip + 1);  // 指针偏移,指向 UDP 头
    udp->src_port = global_sport;                              // 填入源端口
    udp->dst_port = global_dport;                              // 填入目的端口
    // 计算 UDP 数据长度
    uint16_t udp_len = total_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr);
    udp->dgram_len = htons(udp_len);                           // 填入 UDP 长度
    
    // uint16_t payload_len = udp_len - sizeof(struct rte_udp_hdr);
    rte_memcpy((uint8_t *)(udp + 1), data, udp_len);           // 将用户数据拷贝到 UDP 头部之后
    udp->dgram_cksum = 0;                                      // 校验和置零
    udp->dgram_cksum = rte_ipv4_udptcp_cksum(ip, udp);         // 计算 UDP 校验和 (包含伪头部)

    return 0;
}

// 辅助函数:手动封装 TCP 数据包 (主要用于发送 SYN-ACK)
static int ustack_encode_tcp_pkt(uint8_t *msg, uint16_t total_len){

    // ether header
    struct rte_ether_hdr *eth = (struct rte_ether_hdr *)msg;   // 强转为以太网头
    rte_memcpy(eth->d_addr.addr_bytes, global_dmac, RTE_ETHER_ADDR_LEN); // 填目的 MAC
    rte_memcpy(eth->s_addr.addr_bytes, global_smac, RTE_ETHER_ADDR_LEN); // 填源 MAC
    eth->ether_type = htons(RTE_ETHER_TYPE_IPV4);                        // 类型 IPv4

    // ip header
    struct rte_ipv4_hdr *ip = (struct rte_ipv4_hdr *)(eth +1); // 指向 IP 头
    ip->version_ihl = 0x45;                                    // 标准 IPv4 头
    ip->type_of_service = 0;
    ip->total_length = htons(total_len - sizeof(struct rte_ether_hdr)); // IP 层长度
    ip->packet_id = 0;
    ip->fragment_offset = 0;
    ip->time_to_live = 64;
    ip->next_proto_id = IPPROTO_TCP;                           // 上层协议 TCP
    ip->src_addr = global_sip;                                 // 源 IP
    ip->dst_addr = global_dip;                                 // 目的 IP

    ip->hdr_checksum = 0;
    ip->hdr_checksum = rte_ipv4_cksum(ip);                     // IP 校验和

    // tcp header
    struct rte_tcp_hdr *tcp = (struct rte_tcp_hdr *)(ip + 1);  // 指向 TCP 头
    tcp->src_port = global_sport;                              // 源端口
    tcp->dst_port = global_dport;                              // 目的端口
    tcp->sent_seq = htonl(12345);                              // 填入我方的序列号 (此处写死为示例)
    tcp->recv_ack = htonl(global_seqnum + 1);                  // 填入确认号 (对方 Seq + 1)
    tcp->data_off = 0x50;                                      // TCP 头长度 (5 * 4 = 20字节)
    tcp->tcp_flags = RTE_TCP_SYN_FLAG | RTE_TCP_ACK_FLAG;      // 设置标志位:SYN + ACK
    tcp->rx_win = TCP_INIT_WINDOWS;                            // 设置接收窗口大小
    tcp->cksum = 0;
    tcp->cksum = rte_ipv4_udptcp_cksum(ip, tcp);               // 计算 TCP 校验和

    return 0;
}

int main(int argc, char *argv[]){

    if(rte_eal_init(argc,argv) < 0){                           // 初始化 DPDK EAL 环境
        rte_exit(EXIT_FAILURE, "EAL initialization failed\n"); // 失败则退出
    }

    // 造内存池。DPDK 为了快,不会在收包时临时去申请内存(malloc 太慢)。它是在程序启动时,一次性申请好几千个固定大小的内存块(Mbuf)。
    struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("mbuf pool", NUM_MBUFS,0,0,RTE_MBUF_DEFAULT_BUF_SIZE,rte_socket_id());
    if(mbuf_pool == NULL){                                     // 内存池创建失败
        rte_exit(EXIT_FAILURE, "Could not create mbuf pool\n");
    }

    ustack_init_port(mbuf_pool);                               // 初始化网卡端口
    
    while(1){                                                  // 死循环,负责不断轮询收包

        struct rte_mbuf *mbufs[BURST_SIZE] = {0};              // 定义指针数组,存放接收到的包

        // 从网卡接收一批数据包,返回值是实际收到的包数量
        uint16_t num_recvd = rte_eth_rx_burst(global_portid, 0, mbufs, BURST_SIZE);
        if(num_recvd > BURST_SIZE){
            rte_exit(EXIT_FAILURE, "Received more packets than burst size\n");
        }

        int i = 0;
        for(i = 0; i < num_recvd; i++){                        // 遍历处理每一个收到的包
            
            // 将 mbuf 转换为以太网头指针
            struct rte_ether_hdr *eth_hdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr *);
            // 过滤:如果不是 IPv4 协议,释放内存并跳过
            if(eth_hdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)){
                rte_pktmbuf_free(mbufs[i]);
                continue;
            }

            // 根据以太网头长度偏移,获取 IPv4 头部指针
            struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
            
            // ---> 处理 UDP 数据包
            if(iphdr->next_proto_id == IPPROTO_UDP){

                struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1); // 获取 UDP 头
#if ENABLE_SEND

                // 交换源目 MAC 地址,准备回包
                rte_memcpy(global_smac, eth_hdr->d_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
                rte_memcpy(global_dmac, eth_hdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);

                // 交换源目 IP 地址
                rte_memcpy(&global_sip, &iphdr->dst_addr, sizeof(uint32_t));
                rte_memcpy(&global_dip, &iphdr->src_addr, sizeof(uint32_t));

                // 交换源目 端口
                rte_memcpy(&global_sport, &udphdr->dst_port, sizeof(uint16_t));
                rte_memcpy(&global_dport, &udphdr->src_port, sizeof(uint16_t));

                struct in_addr addr;
                addr.s_addr = iphdr->src_addr;
                printf("sip %s:%d --> ", inet_ntoa(addr),ntohs(udphdr->src_port)); // 打印源 IP:Port

                addr.s_addr = iphdr->dst_addr;
                printf("dip %s:%d --> ", inet_ntoa(addr),ntohs(udphdr->dst_port)); // 打印目 IP:Port

                uint16_t length = ntohs(udphdr->dgram_len); // 获取 UDP 数据包总长度
                // 计算加上以太网头和 IP 头后的总长度
                uint16_t total_len = length + sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr); 

                struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool); // 申请一个新的 mbuf 用于发送
                if(!mbuf){
                    rte_exit(EXIT_FAILURE, "Could not allocate mbuf\n");
                }

                mbuf->pkt_len = total_len;                 // 设置 mbuf 包长度属性
                mbuf->data_len = total_len;                // 设置数据长度
                        
                uint8_t *msg = rte_pktmbuf_mtod(mbuf, uint8_t *); // 获取新 mbuf 的数据指针

                // 调用 UDP 封装函数,填入数据
                ustack_encode_udp_pkt(msg, (uint8_t*)(udphdr + 1), total_len);

                rte_eth_tx_burst(global_portid, 0, &mbuf, 1);      // 发送回包
#endif
                printf("udp : %s\n", (char*)(udphdr + 1));         // 打印 UDP 载荷内容

            // ---> 处理 TCP 数据包
            }else if(iphdr->next_proto_id == IPPROTO_TCP){

                struct rte_tcp_hdr *tcphdr = (struct rte_tcp_hdr *)(iphdr + 1); // 获取 TCP 头

                // 同样保存 MAC、IP、端口信息,用于状态记录和回包
                rte_memcpy(global_smac, eth_hdr->d_addr.addr_bytes, RTE_ETHER_ADDR_LEN);
                rte_memcpy(global_dmac, eth_hdr->s_addr.addr_bytes, RTE_ETHER_ADDR_LEN);

                rte_memcpy(&global_sip, &iphdr->dst_addr, sizeof(uint32_t));
                rte_memcpy(&global_dip, &iphdr->src_addr, sizeof(uint32_t));

                rte_memcpy(&global_sport, &tcphdr->dst_port, sizeof(uint16_t));
                rte_memcpy(&global_dport, &tcphdr->src_port, sizeof(uint16_t));

                global_flags = tcphdr->tcp_flags;           // 保存当前包的 TCP 标志位
                global_seqnum = ntohl(tcphdr->sent_seq);    // 保存序列号
                global_acknum = ntohl(tcphdr->recv_ack);    // 保存确认号

                struct in_addr addr;
                addr.s_addr = iphdr->src_addr;
                printf("tcp pkt sip %s:%d --> ", inet_ntoa(addr),ntohs(tcphdr->src_port)); // 打印源信息

                addr.s_addr = iphdr->dst_addr;
                printf("dip %s:%d , flags: %x, seqnum: %d, acknum: %d\n", inet_ntoa(addr),ntohs(tcphdr->dst_port),global_flags,global_seqnum,global_acknum); // 打印目的信息和 TCP 状态

                // [三次握手阶段 1] 如果收到 SYN 包
                if(global_flags & RTE_TCP_SYN_FLAG){

                    if(tcp_status == USTACK_TCP_STATUS_LISTEN){ // 且当前处于 LISTEN 状态
                        // 计算回包长度:Eth + IP + TCP
                        uint16_t total_len = sizeof(struct rte_ether_hdr) + sizeof(struct rte_tcp_hdr) + sizeof(struct rte_ipv4_hdr); 

                        struct rte_mbuf *mbuf = rte_pktmbuf_alloc(mbuf_pool); // 申请内存
                        if(!mbuf){
                            rte_exit(EXIT_FAILURE, "Could not allocate mbuf\n");
                        }

                        mbuf->pkt_len = total_len; 
                        mbuf->data_len = total_len;    
                            
                        uint8_t *msg = rte_pktmbuf_mtod(mbuf, uint8_t *);

                        ustack_encode_tcp_pkt(msg,total_len); // 封装 TCP SYN-ACK 包

                        rte_eth_tx_burst(global_portid, 0, &mbuf, 1); // 发送出去

                        tcp_status = USTACK_TCP_STATUS_SYN_RCVD; // 状态迁移:LISTEN -> SYN_RCVD
                    }
                }
                // [三次握手阶段 3] 如果收到 ACK 包
                if(global_flags & RTE_TCP_ACK_FLAG){

                    if(tcp_status == USTACK_TCP_STATUS_SYN_RCVD){ // 且当前处于 SYN_RCVD 状态

                        printf("enter established\n");
                        tcp_status = USTACK_TCP_STATUS_ESTABLISHED; // 状态迁移:SYN_RCVD -> ESTABLISHED (连接建立)
                    }
                }
                // [数据传输阶段] 如果收到 PSH 包 (Push Data)
                if(global_flags & RTE_TCP_PSH_FLAG){

                    printf("enter established: %d\n", tcp_status);
                    if(tcp_status == USTACK_TCP_STATUS_ESTABLISHED){ // 必须是已连接状态
                        
                        // 计算 TCP 数据偏移量 (Header Length)
                        uint8_t hdrlen = (tcphdr->data_off >> 4) * sizeof(uint32_t);

                        // 定位到 TCP 数据 payload 的起始位置
                        uint8_t *data = ((uint8_t*)tcphdr + hdrlen);

                        printf("tcp data: %s\n", data); // 打印 TCP 携带的数据
                    }
                }
            }
            rte_pktmbuf_free(mbufs[i]); // 重要:处理完毕后释放 mbuf 内存回内存池
        }
    }
    return 0;
}

0voice · GitHub

相关推荐
zbtlink2 小时前
路由器桥接:原理、差异与操作指南
网络·智能路由器
mjr2 小时前
基于Netty的WebSocket实时消息推送系统
网络·websocket·网络协议
jiayong232 小时前
Kubernetes 网络与服务发现面试题详解
网络·kubernetes·服务发现
少云清2 小时前
【性能测试】3_Locust _locust实现混合业务实现
网络·性能测试·locust
venus603 小时前
网络运维之ping与telnet的区别
运维·服务器·网络
WinyQ03 小时前
【DeepStream】整合出现的问题
linux·运维·网络
Getgit3 小时前
mysql批量更新语句
java·数据库·mysql·udp·eclipse
小魏每天都学习3 小时前
【网络拓扑部署-网络设备-网络安全】
运维·网络
zd8451015003 小时前
CubeMX H743 lwip ETH初始化流程
网络·stm32·单片机