DPDK 简易应用开发之路 2:UDP数据包发送及实现

本机环境为 Ubuntu20.04 ,dpdk-stable-20.11.10

发送数据包的通用步骤

初始化DPDK环境: 调用 rte_eal_init() 来初始化DPDK的EAL(环境抽象层),这是所有DPDK应用程序的第一步,用于初始化硬件、内存和逻辑核心。

创建内存池使用: rte_pktmbuf_pool_create() 创建一个内存池(mempool),用于存储将要发送的网络数据包。每个数据包都会被分配一个内存缓冲区来存储数据。

初始化网卡设备:

  1. 调用rte_eth_dev_configure() 配置网卡,指定要使用的发送和接收队列数量。
  2. 调用rte_eth_rx_queue_setup() rte_eth_tx_queue_setup() 分别设置接收队列和发送队列。
  3. 使用 rte_eth_dev_start() 启动网卡设备。

创建UDP数据包 : 使用 rte_pktmbuf_alloc() 从内存池中分配一个 mbuf,并为数据包填充数据。构建UDP包的各个层次(以太网头部、IP头部、UDP头部)

发送数据包: 使用 rte_eth_tx_burst() 将数据包发送到指定的网卡端口和队列。该函数会发送一个或多个数据包,并返回实际发送的包数。

处理未发送成功的数据包: 如果数据包未成功发送,需要检查返回的数量,并适时释放未发送的 mbuf,避免内存泄漏。

信号处理机制

信号注册: 在 main() 函数中, 将 SIGINT(Ctrl+C) 和 SIGTERM 信号绑定到自定义的信号处理函数 signal_handler() 上。当程序运行时,如果用户发送这些信号(例如按下 Ctrl+C),就会调用 signal_handler() 函数。

signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);

信号处理函数: signal_handler() 是一个处理信号的函数,接收信号类型作为参数(signum)。该函数的作用是处理接收到的信号,并将全局变量 force_quit 设为 true,以通知主程序需要退出。

static void signal_handler(int signum)
{
	if (signum == SIGINT || signum == SIGTERM) {
		printf("\n\nSignal %d received, preparing to exit...\n",
				signum);
		force_quit = true;
	}
}

退出条件: 在主程序循环(例如 app_lcore_main_loop() 函数)中,会周期性地检查 force_quit 变量的状态。如果检测到 force_quit 为 true,则会打破循环,准备执行程序的清理和退出工作。

IP 和 UDP 头部设置

根据协议内容:完成IPv4和UDP头部的初始化和设置,包括端口号、数据包长度、IP地址、TTL等字段的填充,同时计算并设置了IP首部的校验和。

// 设置并初始化 IPv4 和 UDP 的头部信息
static void setup_pkt_udp_ip_headers(struct rte_ipv4_hdr *ip_hdr,
    struct rte_udp_hdr *udp_hdr,uint16_t pkt_data_len)
{
    uint16_t pkt_len;

    // 初始化UDP头部
    pkt_len = (uint16_t)(pkt_data_len + sizeof(struct rte_udp_hdr));
    udp_hdr->src_port = rte_cpu_to_be_16(UDP_SRC_PORT);
    udp_hdr->dst_port = rte_cpu_to_be_16(UDP_DST_PORT);
    udp_hdr->dgram_len = rte_cpu_to_be_16(pkt_len);
    udp_hdr->dgram_cksum = 0;  // 不使用UDP校验

    // 初始化IP头部
    pkt_len = (uint16_t) (pkt_len + sizeof(struct rte_ipv4_hdr));
	ip_hdr->version_ihl   =IP_VERSION|IP_HDRLEN;
	ip_hdr->type_of_service   = 0;
	ip_hdr->fragment_offset = 0;
	ip_hdr->time_to_live   = IP_DEFTTL;
	ip_hdr->next_proto_id = IPPROTO_UDP;
	ip_hdr->packet_id = 0;
	ip_hdr->total_length   = rte_cpu_to_be_16(pkt_len);
	ip_hdr->src_addr = rte_cpu_to_be_32(IP_SRC_ADDR); // 换为网络字节序
	ip_hdr->dst_addr = rte_cpu_to_be_32(IP_DST_ADDR);

    // IP 首部校验和
    ip_hdr->hdr_checksum = 0; 
    uint32_t ip_cksum = 0;

    // 将 IP 头部作为 16 位无符号整数数组处理
    uint16_t *ptr16 = (uint16_t *)ip_hdr;
        for (int i = 0; i < sizeof(struct rte_ipv4_hdr) / 2; i++) {
        if (i != 5) { // 校验和字段需要跳过
            ip_cksum += ptr16[i];
        }
    }
    // 循环进位,将结果压缩为 16 位并处理溢出。
    while (ip_cksum >> 16) {
        ip_cksum = (ip_cksum & 0xFFFF) + (ip_cksum >> 16);
    }
    ip_hdr->hdr_checksum = (uint16_t)(~ip_cksum & 0xFFFF);
}

数据包的组装

创建一组数据包缓冲区,并将指定的以太网头部(Ethernet header)、IPv4头部(IPv4 header)、UDP头部(UDP header)和数据(buf)拷贝到每个数据包的适当位置。

数据包的内存布局是以链表形式管理的。每个数据包(rte_mbuf结构体)可能由多个片段(segments)组成,每个片段包含一部分数据。如果直接将数据拷贝到错误的位置或者跨越多个片段拷贝而不考虑片段边界,会导致数据包的结构不正确,可能导致网络协议栈无法正确解析或处理这些数据包。

//  将一个内存缓冲区的内容(buf)拷贝到一个 DPDK 的数据包缓冲区中的多个片段中
static void copy_buf_to_pkt_segs(void *buf, unsigned len, 
    struct rte_mbuf *pkt, unsigned offset)
{
    struct rte_mbuf *seg = pkt;
    unsigned copy_len;
    void *seg_buf;

    // 定位到正确的片段
    while (offset >= seg->data_len) {
        offset -= seg->data_len;
        seg = seg->next;
    }

    // 从当前片段开始拷贝数据
    while (len > 0) {
        // 计算当前片段中需要拷贝的数据长度
        copy_len = seg->data_len - offset;
        if (len < copy_len) {
            copy_len = len;
        }

        seg_buf = rte_pktmbuf_mtod_offset(seg, char *, offset);
        rte_memcpy(seg_buf, buf, copy_len);

        len -= copy_len;
        buf = (char *)buf + copy_len;
        offset = 0;

        if (len > 0) {
            seg = seg->next;
            if (seg == NULL) {
                break; // 防止访问空片段
            }
        }
    }
}


static inline void copy_buf_to_pkt(void* buf, unsigned len, 
    struct rte_mbuf *pkt, unsigned offset)
{
	if (offset + len <= pkt->data_len) {
		rte_memcpy(rte_pktmbuf_mtod_offset(pkt, char *, offset),buf, (size_t) len);
		return;
	}
    // 处理跨多个片段的拷贝操作
	copy_buf_to_pkt_segs(buf, len, pkt, offset);
}

// 创建一组数据包缓冲区
static void create_pkt_mbuf_array(){

    struct rte_mbuf *pkt;
    struct rte_ether_hdr eth_hdr;
    unsigned pkt_data_len = sizeof(struct rte_ether_hdr) + 
        sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr) + sizeof(buf);
    for (uint16_t i = 0; i <MAX_PKT_BURST ; i++)
    {
        // 分配一个buf
        pkt = rte_mbuf_raw_alloc(pktmbuf_pool);
        if (pkt == NULL) {
            printf("error: no enough pool!\n");
            continue; // 处理分配失败情况,继续下一个循环
        }
        // 重置pkt头部空间
        rte_pktmbuf_reset_headroom(pkt);
        pkt->data_len = pkt_data_len;
        pkt->next = NULL;
        // 设置以太网头部
        rte_ether_addr_copy(&des_eth_addrs, &eth_hdr.d_addr);
        rte_ether_addr_copy(&src_eth_addrs, &eth_hdr.s_addr);
        eth_hdr.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
        // copy 
        copy_buf_to_pkt(&eth_hdr, sizeof(eth_hdr), pkt, 0); // Eth
        copy_buf_to_pkt(&pkt_ip_hdr, sizeof(pkt_ip_hdr), pkt, sizeof(struct rte_ether_hdr)); // IP header
        copy_buf_to_pkt(&pkt_udp_hdr, sizeof(pkt_udp_hdr), pkt, sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr)); // UDP header
        copy_buf_to_pkt(&buf, sizeof(buf), pkt, sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr)); // Data
        
       // 设置 rte_mbuf 参数:
       pkt->nb_segs = 1; 
       pkt->pkt_len = pkt->data_len;
       pkt->ol_flags = 0;
       pkt->vlan_tci = 0;
       pkt->vlan_tci_outer = 0;
       pkt->l2_len = sizeof(struct rte_ether_hdr);
       pkt->l3_len = sizeof(struct rte_ipv4_hdr);
       pkt->l4_len = sizeof(struct rte_udp_hdr);
       mbuf_list[i] = pkt;      
    }
}

发送主循环

主要工作就是:设置数据包的IP和UDP头部,通过一个while循环发送数据包,调用create_pkt_mbuf_array()函数准备数据包。调用send_burst(port_id, tx_queue_id)函数发送数据包,并返回成功发送的数据包数量。使用rte_eth_tx_done_cleanup()清理已发送的数据包。

// 在一个网口发送数据包 
static inline int
send_burst(uint8_t portid, uint8_t queueid)
{
    uint16_t send;
    // 上锁,防止多线程同时访问发送队列
    rte_spinlock_lock(&spinlock_conf);
    // 发送数据包,send 是实际发送的数据包数量
        send = rte_eth_tx_burst(portid, queueid, mbuf_list, MAX_PKT_BURST);
    printf("-------- Data sent: %d packets\n", send);
    // 解锁
    rte_spinlock_unlock(&spinlock_conf);
    // 如果未能全部发送,释放未发送的数据包
    if (unlikely(send < MAX_PKT_BURST)) {
        for (uint16_t i = send; i < MAX_PKT_BURST; i++) {
            rte_pktmbuf_free(mbuf_list[i]);
        }
    }
    // 统计已发送数据包总数
    send_total += send;
    return send;
}
static int app_lcore_main_loop(__attribute__((unused)) void *arg)
{
    unsigned lcoreid;
    uint32_t count = 0; // 记录发送的包数
    uint32_t num = 0;   // 记录发送的包总数
    uint16_t ret;       // 记录每次发送成功的包数
    uint16_t pkt_data_len = sizeof(buf);  // 数据包的总长度
    struct rte_eth_stats port_stats;      // 记录端口统计数据
    struct timeval tv;                    // 用于计算时间

    lcoreid = rte_lcore_id(); // 获取当前核心 ID

    if (lcoreid == lcore_id) 
    {
        printf("------- Sending from core %u\n", lcore_id);

        // 重置端口统计数据
        rte_eth_stats_reset(port_id);

        // 打印初始的统计数据
        if (rte_eth_stats_get(port_id, &port_stats) == 0) 
        {
            printf("Initial stats:\n");
            printf("Received packets: %ld    Sent packets: %ld\n", port_stats.ipackets, port_stats.opackets);
            printf("Received bytes: %ld      Sent bytes: %ld\n", port_stats.ibytes, port_stats.obytes);
            printf("Receive errors: %ld      Send errors: %ld\n", port_stats.ierrors, port_stats.oerrors);
            printf("Missed packets: %ld     RX no buffer: %ld\n", port_stats.imissed, port_stats.rx_nombuf);
        }

        // 开始计时
        gettimeofday(&tv, NULL);
        int starttime = tv.tv_sec * 1000000 + tv.tv_usec; // 转换为微秒

        // 设置数据包的 IP 和 UDP 头部
        setup_pkt_udp_ip_headers(&pkt_ip_hdr, &pkt_udp_hdr, pkt_data_len);

        printf("setup_pkt_udp_ip_headers\n");
        while (num < SEND_TOTAL) 
        {
            if (force_quit) // 检查是否需要退出
                break;

            // 准备数据包并发送
            create_pkt_mbuf_array(); // 组装数据包
            ret = send_burst(port_id, tx_queue_id); // 发送数据包
            rte_eth_tx_done_cleanup(port_id, tx_queue_id, 0); // 清理已发送的包
            count += ret; // 累加成功发送的包数
            num++;        // 累加总发送包数
        }

        // 结束计时
        gettimeofday(&tv, NULL);
        int endtime = tv.tv_sec * 1000000 + tv.tv_usec; // 转换为微秒
        int time = endtime - starttime; // 计算总耗时

        // 打印发送后的统计数据
        if (rte_eth_stats_get(port_id, &port_stats) == 0) 
        {
            printf("Final stats:\n");
            printf("Received packets: %ld    Sent packets: %ld\n", port_stats.ipackets, port_stats.opackets);
            printf("Received bytes: %ld      Sent bytes: %ld\n", port_stats.ibytes, port_stats.obytes);
            printf("Receive errors: %ld      Send errors: %ld\n", port_stats.ierrors, port_stats.oerrors);
            printf("Missed packets: %ld     RX no buffer: %ld\n", port_stats.imissed, port_stats.rx_nombuf);
        }

        // 打印发送的总包数和耗时
        printf("------- Total sent: %d  Count: %d  Time: %d microseconds\n", send_total, count, time);    
    }

    return 0;
}

编译运行

在这里插入图片描述

完整代码

完整代码也可见 github

// 标准C库头文件
#include <stdio.h>         // 标准输入输出库
#include <stdlib.h>        // 标准库,包含内存分配、进程控制等
#include <stdint.h>        // 定义固定大小的整数类型
#include <inttypes.h>      // 提供用于格式化固定大小整数类型的宏
#include <string.h>        // 字符串处理函数库
#include <stdarg.h>        // 用于处理变长参数的宏
#include <errno.h>         // 错误号处理
#include <stdbool.h>       // 布尔类型定义
#include <time.h>          // 时间处理函数
#include <sys/types.h>     // 定义数据类型,如`size_t`,`ssize_t`
#include <linux/if_ether.h> // 以太网常量

// Linux系统库头文件
#include <sys/queue.h>     // 定义队列和链表的宏和类型
#include <getopt.h>        // 命令行选项解析
#include <signal.h>        // 信号处理库
#include <sys/time.h>      // 获取时间的库函数(用于高精度时间)

// DPDK 核心库头文件
#include <rte_common.h>        // DPDK中的通用定义
#include <rte_log.h>           // DPDK日志系统
#include <rte_memory.h>        // DPDK内存管理相关函数
#include <rte_memcpy.h>        // 高效的内存拷贝函数
#include <rte_memzone.h>       // DPDK内存区域管理
#include <rte_malloc.h>        // DPDK内存分配函数
#include <rte_ring.h>          // DPDK环形缓冲区
#include <rte_mempool.h>       // DPDK内存池管理
#include <rte_mbuf.h>          // DPDK数据包缓冲区结构和操作

// DPDK 环境初始化库头文件
#include <rte_eal.h>           // DPDK环境抽象层(EAL)初始化
#include <rte_per_lcore.h>     // DPDK每核特定变量
#include <rte_launch.h>        // 启动函数
#include <rte_atomic.h>        // 原子操作
#include <rte_spinlock.h>      // 自旋锁
#include <rte_cycles.h>        // CPU周期数函数
#include <rte_prefetch.h>      // 缓存预取函数
#include <rte_lcore.h>         // 核心绑定函数
#include <rte_branch_prediction.h> // 分支预测优化
#include <rte_interrupts.h>    // 中断处理
#include <rte_pci.h>           // PCI设备管理
#include <rte_random.h>        // 随机数生成
#include <rte_debug.h>         // 调试相关功能

// DPDK 网络相关头文件
#include <rte_ether.h>         // 以太网帧的定义和处理
#include <rte_ethdev.h>        // DPDK中的以太网设备驱动

// DPDK IP、TCP、UDP协议栈相关头文件
#include <rte_ip.h>            // IP协议处理
#include <rte_tcp.h>           // TCP协议处理
#include <rte_udp.h>           // UDP协议处理

// DPDK 字符串操作头文件
#include <rte_string_fns.h>    // 字符串处理函数库



// IP地址  UDP端口
#define IP_SRC_ADDR ((192U << 24) | (168 << 16) | (131 << 8) | 152)
#define IP_DST_ADDR ((192U << 24) | (168 << 16) | (131 << 8) | 130)
#define UDP_SRC_PORT 1024
#define UDP_DST_PORT 1024

#define MAX_PKT_BURST 32
#define RX_RING_SIZE 128  //发送环形缓冲区
#define NUM_MBUFS 8191  //数据包缓冲池
#define MBUF_CACHE_SIZE 256  //内存池中每个缓存的大小(以数据包为单位)
#define BURST_SIZE 32  //批量处理的大小
#define SEND_TOTAL 1 //发送


#define IP_DEFTTL 64 
#define IP_VERSION 0x40 
#define IP_HDRLEN 0x05 //默认头部为20字节

static volatile bool force_quit;//程序强制退出标识符
struct rte_mempool *pktmbuf_pool;
struct rte_mbuf *mbuf_list[MAX_PKT_BURST];//对应的rte_mbuf结构指针数组。32


static struct rte_ipv4_hdr  pkt_ip_hdr;
static struct rte_udp_hdr pkt_udp_hdr;
struct rte_ether_addr des_eth_addrs;//目的mac
struct rte_ether_addr src_eth_addrs;//源mac
struct rte_ether_addr eth_addrs;

unsigned socket_id;
unsigned port_id;
unsigned lcore_id;
unsigned rx_queue_id;
unsigned tx_queue_id;

uint32_t send_total = 0;

rte_spinlock_t spinlock_conf = RTE_SPINLOCK_INITIALIZER; //自旋锁,来保证对一个网口竞争访问;

static const struct rte_eth_conf port_conf_default = {
    .rxmode = { 
        .max_rx_pkt_len = RTE_ETHER_MAX_LEN,
        /*.offloads = DEV_RX_OFFLOAD_VLAN_STRIP|
                    DEV_RX_OFFLOAD_VLAN_FILTER|
                    DEV_RX_OFFLOAD_MACSEC_STRIP,
        // 启用硬件 VLAN 过滤功能 启用硬件 VLAN 标签剥离功能 启用硬件 CRC 去除功能
        */
    },
    /*.rx_adv_conf={
        .rss_conf={
            .rss_key = NULL,
			.rss_hf = ETH_RSS_IP,
        },
    },
    */
    .txmode={
        .mq_mode = ETH_MQ_TX_NONE, // 不使用多队列模式
    }
};

char buf[64]="Partial string initialization";

static void signal_handler(int signum)
{
	if (signum == SIGINT || signum == SIGTERM) {
		printf("\n\nSignal %d received, preparing to exit...\n",
				signum);
		force_quit = true;
	}
}

static void print_ethaddr(const char *name, const struct ether_addr *eth_addr)
{
    char buf[48];
	rte_ether_format_addr(buf, 48, eth_addr);
	printf("%s%s \n", name, buf);
}

// 设置并初始化 IPv4 和 UDP 的头部信息
static void setup_pkt_udp_ip_headers(struct rte_ipv4_hdr *ip_hdr,
    struct rte_udp_hdr *udp_hdr,uint16_t pkt_data_len)
{
    uint16_t pkt_len;

    // 初始化UDP头部
    pkt_len = (uint16_t)(pkt_data_len + sizeof(struct rte_udp_hdr));
    udp_hdr->src_port = rte_cpu_to_be_16(UDP_SRC_PORT);
    udp_hdr->dst_port = rte_cpu_to_be_16(UDP_DST_PORT);
    udp_hdr->dgram_len = rte_cpu_to_be_16(pkt_len);
    udp_hdr->dgram_cksum = 0;  // 不使用UDP校验

    // 初始化IP头部
    pkt_len = (uint16_t) (pkt_len + sizeof(struct rte_ipv4_hdr));
	ip_hdr->version_ihl   =IP_VERSION|IP_HDRLEN;
	ip_hdr->type_of_service   = 0;
	ip_hdr->fragment_offset = 0;
	ip_hdr->time_to_live   = IP_DEFTTL;
	ip_hdr->next_proto_id = IPPROTO_UDP;
	ip_hdr->packet_id = 0;
	ip_hdr->total_length   = rte_cpu_to_be_16(pkt_len);
	ip_hdr->src_addr = rte_cpu_to_be_32(IP_SRC_ADDR); // 换为网络字节序
	ip_hdr->dst_addr = rte_cpu_to_be_32(IP_DST_ADDR);

    // IP 首部校验和
    ip_hdr->hdr_checksum = 0; 
    uint32_t ip_cksum = 0;

    // 将 IP 头部作为 16 位无符号整数数组处理
    uint16_t *ptr16 = (uint16_t *)ip_hdr;
        for (int i = 0; i < sizeof(struct rte_ipv4_hdr) / 2; i++) {
        if (i != 5) { // 校验和字段需要跳过
            ip_cksum += ptr16[i];
        }
    }
    // 循环进位,将结果压缩为 16 位并处理溢出。
    while (ip_cksum >> 16) {
        ip_cksum = (ip_cksum & 0xFFFF) + (ip_cksum >> 16);
    }
    ip_hdr->hdr_checksum = (uint16_t)(~ip_cksum & 0xFFFF);
}

//  将一个内存缓冲区的内容(buf)拷贝到一个 DPDK 的数据包缓冲区中的多个片段中
static void copy_buf_to_pkt_segs(void *buf, unsigned len, 
    struct rte_mbuf *pkt, unsigned offset)
{
    struct rte_mbuf *seg = pkt;
    unsigned copy_len;
    void *seg_buf;

    // 定位到正确的片段
    while (offset >= seg->data_len) {
        offset -= seg->data_len;
        seg = seg->next;
    }

    // 从当前片段开始拷贝数据
    while (len > 0) {
        // 计算当前片段中需要拷贝的数据长度
        copy_len = seg->data_len - offset;
        if (len < copy_len) {
            copy_len = len;
        }

        seg_buf = rte_pktmbuf_mtod_offset(seg, char *, offset);
        rte_memcpy(seg_buf, buf, copy_len);

        len -= copy_len;
        buf = (char *)buf + copy_len;
        offset = 0;

        if (len > 0) {
            seg = seg->next;
            if (seg == NULL) {
                break; // 防止访问空片段
            }
        }
    }
}


static inline void copy_buf_to_pkt(void* buf, unsigned len, 
    struct rte_mbuf *pkt, unsigned offset)
{
	if (offset + len <= pkt->data_len) {
		rte_memcpy(rte_pktmbuf_mtod_offset(pkt, char *, offset),buf, (size_t) len);
		return;
	}
    // 处理跨多个片段的拷贝操作
	copy_buf_to_pkt_segs(buf, len, pkt, offset);
}

// 创建一组数据包缓冲区
static void create_pkt_mbuf_array(){

    struct rte_mbuf *pkt;
    struct rte_ether_hdr eth_hdr;
    unsigned pkt_data_len = sizeof(struct rte_ether_hdr) + 
        sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr) + sizeof(buf);
    for (uint16_t i = 0; i <MAX_PKT_BURST ; i++)
    {
        // 分配一个buf
        pkt = rte_mbuf_raw_alloc(pktmbuf_pool);
        if (pkt == NULL) {
            printf("error: no enough pool!\n");
            continue; // 处理分配失败情况,继续下一个循环
        }
        // 重置pkt头部空间
        rte_pktmbuf_reset_headroom(pkt);
        pkt->data_len = pkt_data_len;
        pkt->next = NULL;
        // 设置以太网头部
        rte_ether_addr_copy(&des_eth_addrs, &eth_hdr.d_addr);
        rte_ether_addr_copy(&src_eth_addrs, &eth_hdr.s_addr);
        eth_hdr.ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
        // copy 这里优化可以使用dpdk的函数
        copy_buf_to_pkt(&eth_hdr, sizeof(eth_hdr), pkt, 0); // Eth
        copy_buf_to_pkt(&pkt_ip_hdr, sizeof(pkt_ip_hdr), pkt, sizeof(struct rte_ether_hdr)); // IP header
        copy_buf_to_pkt(&pkt_udp_hdr, sizeof(pkt_udp_hdr), pkt, sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr)); // UDP header
        copy_buf_to_pkt(&buf, sizeof(buf), pkt, sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr) + sizeof(struct rte_udp_hdr)); // Data
        
        /* dpdk优化版
        struct rte_ether_hdr* eth_hdr;
        struct rte_ipv4_hdr *ip_hdr;
        struct rte_udp_hdr *udp_hdr;
        ip_hdr = (struct rte_ipv4_hdr *)(eth_hdr + 1);
        *ip_hdr = pkt_ip_hdr;
        ip_hdr->total_length = rte_cpu_to_be_16(pkt_data_len - sizeof(struct rte_ether_hdr));

        udp_hdr = (struct rte_udp_hdr *)(ip_hdr + 1);
        *udp_hdr = pkt_udp_hdr;
        udp_hdr->dgram_len = rte_cpu_to_be_16(pkt_data_len - sizeof(struct rte_ether_hdr) - sizeof(struct rte_ipv4_hdr));

        void *pkt_data = (void *)(udp_hdr + 1);
        rte_memcpy(pkt_data, buf, sizeof(buf));
        */
       // 设置 rte_mbuf 参数:
       pkt->nb_segs = 1; 
       pkt->pkt_len = pkt->data_len;
       pkt->ol_flags = 0;
       pkt->vlan_tci = 0;
       pkt->vlan_tci_outer = 0;
       pkt->l2_len = sizeof(struct rte_ether_hdr);
       pkt->l3_len = sizeof(struct rte_ipv4_hdr);
       pkt->l4_len = sizeof(struct rte_udp_hdr);
       mbuf_list[i] = pkt;      
    }
}

// 在一个网口发送数据包 
static inline int
send_burst(uint8_t portid, uint8_t queueid)
{
    uint16_t send;
    
    // 上锁,防止多线程同时访问发送队列
    rte_spinlock_lock(&spinlock_conf);

    // 发送数据包,send 是实际发送的数据包数量
    
        send = rte_eth_tx_burst(portid, queueid, mbuf_list, MAX_PKT_BURST);
    printf("-------- Data sent: %d packets\n", send);

    // 解锁
    rte_spinlock_unlock(&spinlock_conf);

    // 如果未能全部发送,释放未发送的数据包
    if (unlikely(send < MAX_PKT_BURST)) {
        for (uint16_t i = send; i < MAX_PKT_BURST; i++) {
            rte_pktmbuf_free(mbuf_list[i]);
        }
    }
    // 统计已发送数据包总数
    send_total += send;
    return send;
}

static int app_lcore_main_loop(__attribute__((unused)) void *arg)
{
    unsigned lcoreid;
    uint32_t count = 0; // 记录发送的包数
    uint32_t num = 0;   // 记录发送的包总数
    uint16_t ret;       // 记录每次发送成功的包数
    uint16_t pkt_data_len = sizeof(buf);  // 数据包的总长度
    struct rte_eth_stats port_stats;      // 记录端口统计数据
    struct timeval tv;                    // 用于计算时间

    lcoreid = rte_lcore_id(); // 获取当前核心 ID

    if (lcoreid == lcore_id) 
    {
        printf("------- Sending from core %u\n", lcore_id);

        // 重置端口统计数据
        rte_eth_stats_reset(port_id);

        // 打印初始的统计数据
        if (rte_eth_stats_get(port_id, &port_stats) == 0) 
        {
            printf("Initial stats:\n");
            printf("Received packets: %ld    Sent packets: %ld\n", port_stats.ipackets, port_stats.opackets);
            printf("Received bytes: %ld      Sent bytes: %ld\n", port_stats.ibytes, port_stats.obytes);
            printf("Receive errors: %ld      Send errors: %ld\n", port_stats.ierrors, port_stats.oerrors);
            printf("Missed packets: %ld     RX no buffer: %ld\n", port_stats.imissed, port_stats.rx_nombuf);
        }

        // 开始计时
        gettimeofday(&tv, NULL);
        int starttime = tv.tv_sec * 1000000 + tv.tv_usec; // 转换为微秒

        // 设置数据包的 IP 和 UDP 头部
        setup_pkt_udp_ip_headers(&pkt_ip_hdr, &pkt_udp_hdr, pkt_data_len);

        printf("setup_pkt_udp_ip_headers\n");
        while (num < SEND_TOTAL) 
        {
            if (force_quit) // 检查是否需要退出
                break;

            // 准备数据包并发送
            create_pkt_mbuf_array(); // 组装数据包
            ret = send_burst(port_id, tx_queue_id); // 发送数据包
            rte_eth_tx_done_cleanup(port_id, tx_queue_id, 0); // 清理已发送的包
            count += ret; // 累加成功发送的包数
            num++;        // 累加总发送包数
        }

        // 结束计时
        gettimeofday(&tv, NULL);
        int endtime = tv.tv_sec * 1000000 + tv.tv_usec; // 转换为微秒
        int time = endtime - starttime; // 计算总耗时

        // 打印发送后的统计数据
        if (rte_eth_stats_get(port_id, &port_stats) == 0) 
        {
            printf("Final stats:\n");
            printf("Received packets: %ld    Sent packets: %ld\n", port_stats.ipackets, port_stats.opackets);
            printf("Received bytes: %ld      Sent bytes: %ld\n", port_stats.ibytes, port_stats.obytes);
            printf("Receive errors: %ld      Send errors: %ld\n", port_stats.ierrors, port_stats.oerrors);
            printf("Missed packets: %ld     RX no buffer: %ld\n", port_stats.imissed, port_stats.rx_nombuf);
        }

        // 打印发送的总包数和耗时
        printf("------- Total sent: %d  Count: %d  Time: %d microseconds\n", send_total, count, time);    
    }

    return 0;
}


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

	int ret;
	uint32_t nb_lcores;
	uint32_t nb_ports;
	unsigned lcoreid;

	uint8_t  nb_rx_queue, nb_tx_queue;
	uint16_t nb_rx_desc, nb_tx_desc;
	
	struct rte_eth_dev_info default_eth_dev_info_before;
	struct rte_eth_dev_info default_eth_dev_info_after;
	struct rte_eth_rxconf default_rxconf;
	struct rte_eth_txconf default_txconf;
	struct rte_eth_desc_lim 	rx_desc_lim;
	struct rte_eth_desc_lim 	tx_desc_lim;
	
	nb_rx_queue = 1;    //端口接收队列数量
	nb_tx_queue = 1;    //端口传输队列数量
	nb_rx_desc = 128;   //端口接收队列描述符数量
	nb_tx_desc = 512;   //端口传输队列描述符数量
	rx_queue_id = 0;    //仅使用接收队列 0 
	tx_queue_id = 0;    //仅使用传输队列 0 
	port_id = 0;		//仅使用端口 0 
	lcore_id = 1;       //仅使用的逻辑核 1
	force_quit = false;

	ret = rte_eal_init(argc, argv);
	if (ret < 0)
		rte_panic("Cannot init EAL\n");
	
	signal(SIGINT, signal_handler);
	signal(SIGTERM, signal_handler);
	
	//端口数量
	nb_ports = rte_eth_dev_count_total();
	if (nb_ports > RTE_MAX_ETHPORTS)
		nb_ports = RTE_MAX_ETHPORTS;
	//逻辑核数量
	nb_lcores = rte_lcore_count();
	printf("number of lcores: %d    number of ports: %d\n", nb_lcores, nb_ports);
	//主逻辑核 CPU 插槽编号
	socket_id = rte_lcore_to_socket_id(rte_get_master_lcore());
	
	//创建内存池
	char s[64];//内存池名称
	snprintf(s, sizeof(s), "mbuf_pool_%d", socket_id);
	pktmbuf_pool = rte_pktmbuf_pool_create(s,NUM_MBUFS, MBUF_CACHE_SIZE, 0,RTE_MBUF_DEFAULT_BUF_SIZE, socket_id);
	if (pktmbuf_pool == NULL)
		rte_exit(EXIT_FAILURE, "Cannot init mbuf pool on socket %d\n", socket_id);
	else
		printf("Allocated mbuf pool on socket %d\n", socket_id);
	
	//获取端口mac 地址
	rte_eth_macaddr_get(port_id, &src_eth_addrs);
	print_ethaddr("SRC1  Mac Address:", &src_eth_addrs);
	
	rte_eth_macaddr_get(port_id + 1, &eth_addrs);
	print_ethaddr("SRC2  Mac Address:", &eth_addrs);
	
	//目的mac 地址
	void *tmp;
	tmp = &des_eth_addrs.addr_bytes[0];
	//*((uint64_t *)tmp) = (((uint64_t)0x59 << 40) | ((uint64_t)0x41 << 32) | ((uint64_t)0x02 << 24) | ((uint64_t)0x4A << 16) | ((uint64_t)0x53 << 8) | (uint64_t)0x2C);
	//*((uint64_t *)tmp) = (((uint64_t)0x30 << 40) | ((uint64_t)0x05 << 32) | ((uint64_t)0x05 << 24) | ((uint64_t)0x0A << 16) | ((uint64_t)0x11 << 8) | (uint64_t)0x00);
     *((uint64_t *)tmp) = (((uint64_t)0xFF << 40) | ((uint64_t)0xFF << 32) | ((uint64_t)0xFF << 24) | ((uint64_t)0xFF << 16) | ((uint64_t)0xFF << 8) | (uint64_t)0xFF);
	print_ethaddr("DES  Mac Address:", &des_eth_addrs);
	
	//端口配置
	ret = rte_eth_dev_configure(port_id, nb_rx_queue, nb_tx_queue, &port_conf_default);
	if (ret < 0)
		rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d, port=%d\n",ret, port_id);
	//检查Rx和Tx描述符的数量是否满足来自以太网设备信息的描述符限制,否则将其调整为边界 nb_rx_desc =128,nb_tx_desc=128
	ret = rte_eth_dev_adjust_nb_rx_tx_desc(port_id, &nb_rx_desc,&nb_tx_desc);
	
	//获取端口默认配置信息
	rte_eth_dev_info_get(port_id, &default_eth_dev_info_before);
	
	
	//端口 TX 队列配置
	fflush(stdout);
	
	default_txconf = default_eth_dev_info_before.default_txconf;
	tx_desc_lim = default_eth_dev_info_before.tx_desc_lim;
	printf("config before ---- tx_free_thresh : %d ,desc_max :%d ,desc_min : %d \n",default_txconf.tx_free_thresh, tx_desc_lim.nb_max, tx_desc_lim.nb_min);
	
	default_txconf.tx_free_thresh = (uint16_t) MAX_PKT_BURST;
	ret = rte_eth_tx_queue_setup(port_id, tx_queue_id, nb_tx_desc, socket_id, NULL);
	if (ret < 0)
		rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup: err=%d, port=%d\n", ret, port_id);
		
	
	//端口 RX 队列配置
	fflush(stdout);
	
	default_rxconf = default_eth_dev_info_before.default_rxconf;
	rx_desc_lim = default_eth_dev_info_before.rx_desc_lim;
	printf("config before ---- rx_free_thresh : %d ,desc_max :%d ,desc_min : %d \n",default_rxconf.rx_free_thresh, rx_desc_lim.nb_max, rx_desc_lim.nb_min);
	
	default_rxconf.rx_free_thresh = (uint16_t) MAX_PKT_BURST;
	ret = rte_eth_rx_queue_setup(port_id, rx_queue_id, nb_rx_desc, socket_id, NULL, pktmbuf_pool);
	if (ret < 0)
		rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup: err=%d,port=%d\n", ret, port_id);
	
	rte_delay_ms(5000);//延迟5秒
	memset(&default_txconf, 0, sizeof(default_txconf));
	memset(&default_rxconf, 0, sizeof(default_rxconf));
	
	memset(&tx_desc_lim, 0, sizeof(tx_desc_lim));
	memset(&rx_desc_lim, 0, sizeof(rx_desc_lim));
	
	//获取端口默认配置信息
	rte_eth_dev_info_get(port_id, &default_eth_dev_info_after);
	
	default_txconf = default_eth_dev_info_after.default_txconf;
	tx_desc_lim = default_eth_dev_info_after.tx_desc_lim;
	printf("config after  ---- tx_free_thresh : %d ,desc_max :%d ,desc_min : %d \n",default_txconf.tx_free_thresh, tx_desc_lim.nb_max, tx_desc_lim.nb_min);
	default_rxconf = default_eth_dev_info_after.default_rxconf;
	rx_desc_lim = default_eth_dev_info_after.rx_desc_lim;
	printf("config after  ---- rx_free_thresh : %d ,desc_max :%d ,desc_min : %d \n",default_rxconf.rx_free_thresh, rx_desc_lim.nb_max, rx_desc_lim.nb_min);
	
	
	
	/*开启端口网卡 */
	ret = rte_eth_dev_start(port_id);
	if (ret < 0)
		rte_exit(EXIT_FAILURE, "rte_eth_dev_start: err=%d, port=%d\n",ret, port_id);

	printf("started: Port %d\n", port_id);
	
	/* 设置端口网卡混杂模式 */
    rte_eth_promiscuous_enable(port_id);
	
	/*等待网卡启动成功*/
	#define CHECK_INTERVAL 100 /* 100ms */	
	#define MAX_CHECK_TIME 50 /* 5s (50 * 100ms) in total */
	uint8_t count;
	struct rte_eth_link link;
	for (count = 0; count <= MAX_CHECK_TIME; count++) {
		if (force_quit)
			return 0;
		memset(&link, 0, sizeof(link));
		rte_eth_link_get_nowait(port_id, &link);
		if (link.link_status)
			printf("Port %d Link Up - speed %u Mbps - %s\n", (uint8_t)port_id,(unsigned)link.link_speed,
					(link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
						("full-duplex") : ("half-duplex\n"));
		else
			printf("Port %d Link Down\n",(uint8_t)port_id);
		rte_delay_ms(CHECK_INTERVAL);
	}
	printf("调用逻辑核执行任务\n");
	/*调用逻辑核执行任务*/
	rte_eal_mp_remote_launch(app_lcore_main_loop, NULL, CALL_MASTER);
	
	/*等待逻辑核退出*/
	RTE_LCORE_FOREACH_SLAVE(lcoreid) {
		if (rte_eal_wait_lcore(lcoreid) < 0) {
			return -1;
		}
	}
	printf("Bye...\n");
	printf("Closing port %d...\n", port_id);
	
	/*停止端口网卡*/
	rte_eth_dev_stop(port_id);
	/*关闭端口网卡*/
	rte_eth_dev_close(port_id);
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/883461.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[Linux]:线程(一)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 1. 初识线程 1.1 线程的概念 在操作系统中&#xff0c;进程与线程一直是我们…

简单了解Redis(初识阶段)

1.认识Redis 对于Redis有一个很重要的点就是&#xff0c;它存储数据是在内存中存储的。 但是对于单机程序&#xff0c;直接通过变量存储数据的方式是更优的&#xff0c;在分布式系统下 Redis才能发挥威力 因为进程是有隔离性的&#xff0c;Redis可以基于网络&#xff0c;把进…

CentOS 7 YUM源不可用

CentOS 7 操作系统在2024年6月30日后将停止官方维护&#xff0c;并且官方提供的YUM源将不再可用。 修改&#xff1a;nano /etc/yum.repos.d/CentOS-Base.repo # CentOS-Base.repo [base] nameCentOS-$releasever - Base baseurlhttp://mirrors.aliyun.com/centos/$rel…

前端——flex布局

flex布局——弹性布局 传统布局: 浮动 定位 行内块等 1. flex布局 方法简单 不需要计算 能自动分配父级里面的子元素排版 对齐方式等等 >flex布局 可以适应不同屏幕布局 2. flex布局使用 - 给父级盒子 display: flex 开启弹性盒模型 - 子元素就会默…

html中为div添加展开与收起功能2(div隐藏与显示)

效果图&#xff1a; 1、单个隐藏div项 html布局 <div class"question-detail active"><div class"item-handle"><span class"btn-detail">作答详情 <i class"layui-icon layui-icon-down layui-font-12"><…

数据分析师之Excel学习

前言 excel作为职场人来说&#xff0c;已经是人人必备的技能了&#xff0c;所以还不知道这个的小伙伴&#xff0c;一定要抓紧时间学习&#xff0c;紧跟时代的步伐。 Excel 几个重要的版本 97-2003版本是国内最早流行的版本 .xlsx后缀的表格文件&#xff0c;基本是07版本及…

【数据结构】Java的HashMap 和 HashSet 大全笔记,写算法用到的时候翻一下,百度都省了!(实践篇)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. &#x1f92d;&#x1f92d;&#x1f92d;可能说的不是那么严谨.但小编初心是能让更多人…

1.随机事件与概率

第一章 随机时间与概率 1. 随机事件及其运算 1.1 随机现象 ​ 确定性现象&#xff1a;只有一个结果的现象 ​ 确定性现象&#xff1a;结果不止一个&#xff0c;且哪一个结果出现&#xff0c;人们事先并不知道 1.2 样本空间 ​ 样本空间&#xff1a;随机现象的一切可能基本…

什么是智慧党建?可视化大屏如何推动高质量党建?

在数字化时代&#xff0c;党建工作迎来了新的发展机遇。智慧党建&#xff0c;作为新时代党建工作的创新模式&#xff0c;正逐渐成为推动党的建设向高质量发展的重要力量。它不仅改变了传统的党建工作方式&#xff0c;还通过现代信息技术的应用&#xff0c;提升了党建工作的效率…

【CSS】鼠标 、轮廓线 、 滤镜 、 堆叠层级

cursor 鼠标outline 轮廓线filter 滤镜z-index 堆叠层级 cursor 鼠标 值说明值说明crosshair十字准线s-resize向下改变大小pointer \ hand手形e-resize向右改变大小wait表或沙漏w-resize向左改变大小help问号或气球ne-resize向上右改变大小no-drop无法释放nw-resize向上左改变…

AI绘画Stable Diffusion 自制素材工具: layerdiffusion插件—你的透明背景图片生成工具

大家好&#xff0c;我是灵魂画师向阳 今天给大家分享一款AI绘画的神级插件—LayerDiffusion。 Layerdiffusion是一个用于stable-diffusion-webui 的透明背景生成&#xff08;不是生成图再工具扣图&#xff0c;是直接生成透明背景透明图像&#xff09;插件扩展&#xff0c;它可…

Java笔试面试题AI答之设计模式(2)

文章目录 6. 什么是单例模式&#xff0c;以及他解决的问题&#xff0c;应用的环境 &#xff1f;解决的问题应用的环境实现方式 7. 什么是工厂模式&#xff0c;以及他解决的问题&#xff0c;应用的环境 &#xff1f;工厂模式简述工厂模式解决的问题工厂模式的应用环境工厂模式的…

音乐服务器测试报告

项目背景 该音乐服务器系统使用的是前后端分离的方式来实现,将相关数据存储到数据库中, 且将其部署到云服务器上. 前端主要构成部分有: 登录页面, 列表页面, 喜欢页面, 添加歌曲4个页面组成. 通过结合后端实现了主要的功能: 登录, 播放音乐, 添加音乐, 收藏音乐, 删除音乐, 删…

vscode 配置django

创建运行环境 使用pip安装Django&#xff1a;pip install django。 创建一个新的Django项目&#xff1a;django-admin startproject myproject。 打开VSCode&#xff0c;并在项目文件夹中打开终端。 在VSCode中安装Python扩展&#xff08;如果尚未安装&#xff09;。 在项…

MySQL InnoDB MVCC读写逻辑分析与调测

目标 1、构建MVCC读写场景 2、gdb调试MVCC过程&#xff0c;输出流程图&#xff08;函数级别调用过程&#xff09; 前提 准备1 打开服务端 查询mysqld进程号 线程树 打开客户端&#xff0c;想创建几个事务号就打开几个客户端 准备2 数据库mvcc&#xff0c;两个表test和stu…

Spring Boot框架在甘肃非遗文化网站设计中的运用

3 系统分析 当用户确定开发一款程序时&#xff0c;是需要遵循下面的顺序进行工作&#xff0c;概括为&#xff1a;系统分析–>系统设计–>系统开发–>系统测试&#xff0c;无论这个过程是否有变更或者迭代&#xff0c;都是按照这样的顺序开展工作的。系统分析就是分析系…

数据库——sql语言学习 查找语句

一、什么是sql SQL是结构化查询语言&#xff08;Structured Query Language&#xff09;的缩写&#xff0c;它是一种专门为数据库设计的操作命令集&#xff0c;用于管理关系数据库管理系统&#xff08;RDBMS&#xff09;。 二、查找相关语句 ‌‌首先&#xff0c;我们已经设…

【洛谷】P10417 [蓝桥杯 2023 国 A] 第 K 小的和 的题解

【洛谷】P10417 [蓝桥杯 2023 国 A] 第 K 小的和 的题解 题目传送门 题解 CSP-S1 补全程序&#xff0c;致敬全 A 的答案&#xff0c;和神奇的预言家。 写一下这篇的题解说不定能加 CSP 2024 的 RP 首先看到 k k k 这么大的一个常数&#xff0c;就想到了二分。然后写一个判…

Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】

Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】 目录 Unity 设计模式 之 创建型模式 -【单例模式】【原型模式】 【建造者模式】 一、简单介绍 二、单例模式 (Singleton Pattern) 1、什么时候使用单例模式 2、单例模式的好处 3、使用单例模式的…

sheng的学习笔记-logback

基础知识 什么是logback Logback是一个用于Java应用程序的日志框架&#xff0c;提供了更好的性能、可扩展性和灵活性。 与Log4j相比&#xff0c;Logback提供了更快的速度和更低的内存占用&#xff0c;这使得它成为大型企业级应用程序的理想选择。 ‌logback和slf4j的关系是…