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

news2024/9/20 3:32:59

本机环境为 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

希尔排序(ShellSort)详解

希尔排序的底层是插入排序&#xff0c; 不了解插入排序的友友可以先看我下面这篇文章&#xff1a; 插入排序详解-CSDN博客 思路 就整体而言&#xff0c;希尔排序不过是多用了几次插入排序&#xff0c; 预排序→插入排序&#xff0c; 预排序的作用是让数组元素更接近于有序…

FreeRtos中Task函数详解

前言&#xff1a;本篇笔记参考韦东山老师&#xff0c;B站视频链接放在最后。 Task任务基本概念 在使用FreeRtos的程序中&#xff0c;可以创建多个Task来完成程序功能&#xff0c;Task是轻量级的独立执行单元&#xff0c;被FreeRtos的调度器管理&#xff0c;每个任务有着自己的…

从零开始手搓Transformer#Datawhale组队学习Task1#

从零开始手搓Transformer 目录 缩放点积注意力DotProductAttention 多头注意力Multi-Head Attention 位置编码Position Encoder 前馈神经网络FFN 残差连接和层归一化&#xff08;Add&Norm&#xff09; 编码器Encoder 解码器Decoder 编码器-解码器Encoder-Decoder …

pg入门3—详解tablespaces2

pg默认的tablespace的location为空&#xff0c;那么如果表设置了默认的tablespace&#xff0c;数据实际上是存哪个目录的呢? 在 PostgreSQL 中&#xff0c;如果你创建了一个表并且没有显式指定表空间&#xff08;tablespace&#xff09;&#xff0c;或者表空间的 location 为…

ISSTA 2024盛大开幕:中国学者的录取数和投稿量均位列第一

随着夏日的尾声&#xff0c;全球软件测试领域的专家和学者齐聚在奥地利维也纳。共同参与这场科技盛宴——ISSTA 2024。这场国际会议正如火如荼地进行中&#xff0c;吸引了来自世界各地的专业人士参与。 会议实况&#xff1a; 9月16日与17日&#xff0c;大会安排了丰富的社交活…

Qt:懒汉单例(附带单例使用和内存管理)

前言 本文主要写懒汉单例以及单例的释放&#xff0c;网上很多教程只有单例的创建&#xff0c;但是并没有告诉我们单例的内存管理&#xff0c;这就很头疼。 正文 以下是两种懒汉单例的写法 1. 懒汉式单例&#xff08;多线程不安全&#xff0c;但是在单线程里面是安全的&…

【828华为云征文|华为云Flexus X实例部署指南:轻松搭建可道云KODBOX项目】

文章目录 华为云 Flexus X 实例&#xff1a;引领高效云服务的新时代部署【可道云KODBOX】项目准备工作具体操作指南服务器环境确认宝塔软件商店操作域名解析可道云KODBOX登录页效果验证 总结 华为云 Flexus X 实例&#xff1a;引领高效云服务的新时代 在云计算领域&#xff0c…

【UE5】使用2DFlipbook图作为体积纹理,实现实时绘制体积纹理【第一篇】

这是一篇对“Creating a Volumetric Ray Marcher-Shader Bits”的学习心得 文章时间很早&#xff0c;因此这里针对UE5对原文做出兼容性修正&#xff08;为避免累赘不做出注明。链接如上&#xff0c;有需要自行学习&#xff09; 以及最后对Custom做可能的蓝图移植&#xff0c;做…

虚拟机与物理机的文件共享

之前往虚拟机里传文件都是直接拖拽或者借助工具上传&#xff0c;都不太方便&#xff0c;倘若物理机的文件直接能在虚拟机里读取使用&#xff0c;那多好啊~ 1 虚拟机设置 注意文件夹名称不要中文/空格 2 验证Kali下分享文件夹功能是否启用 vmware-hgfsclient 3 创建挂载目录…

【踩坑】utools黑框和chrome白屏

记录一下bug&#xff0c;后面找到解决方案再同步 刚开机会黑框&#xff0c;但是输入wx能正常打开&#xff0c;功能一切正常 过一段时间会恢复正常 chrome会白屏 过一段时间恢复正常&#xff0c;大概是utools恢复正常时间的三倍 猜测是前两天关机时提示xx应用阻止关机&#…

旷视轻量化网络shufflenet算法解读

目录 预备知识 1. 回顾MobileNet V1的核心思想---深度可分离卷积 2.ShuffleNet主要有两个创新点 2.1 分组卷积与11分组卷积 2.2 channel Shuffle&#xff08;通道重排&#xff09; 2.3 通道重排过程 3. ShuffleNet网络结构 3.1 ShuffleNet unit 3.2 不同分组数的Shu…

Mamba YOLO World

论文地址&#xff1a;https://arxiv.org/pdf/2409.08513v1 代码地址&#xff1a; GitHub - Xuan-World/Mamba-YOLO-World: Mamba-YOLO-World: Marrying YOLO-World with Mamba for Open-Vocabulary Detection 开集检测&#xff08;OVD&#xff09;旨在检测预定义类别之外的物体…

上海做网站公司发展分析报告

上海网站公司发展分析报告 一、行业概况 上海作为中国的经济、金融和科技中心&#xff0c;其网站开发行业在近年来迅速发展。根据统计数据&#xff0c;当地的网站公司数量逐年增加&#xff0c;涵盖了企业网站、电子商务平台、移动应用开发等多个领域。随着互联网技术不断演进&…

维钧团队与广东能源集团携手共创未来

2024 年 9 月 19日&#xff0c;能源与投资领域传出一则令人振奋的消息——多元化投资团队维钧团队与广东能源集团正式达成合作&#xff0c;如同璀璨星辰交汇&#xff0c;开启了双方共同发展的壮丽新篇章。 维钧团队&#xff0c;以其敏锐专业的投资眼光、灵活多元的投资策略和卓…

大牛直播SDK核心音视频模块探究

技术背景 视沃科技旗下”大牛直播SDK”&#xff0c;始于2015年&#xff0c;致力于传统行业极致体验的音视频直播技术解决方案&#xff0c;产品涵盖跨平台的实时RTMP推流、RTMP/RTSP直播播放(支持RTSP|RTMP H.265&#xff0c;Enhanced RTMP H.265)、GB28181设备接入、推送端播放…

redis为什么不使用一致性hash

Redis节点间通信时&#xff0c;心跳包会携带节点的所有槽信息&#xff0c;它能以幂等方式来更新配置。如果采用 16384 个插槽&#xff0c;占空间 2KB (16384/8);如果采用 65536 个插槽&#xff0c;占空间 8KB (65536/8)。 今天我们聊个知识点为什么Redis使用哈希槽而不是一致性…

Llama 3.1 Omni:颠覆性的文本与语音双输出模型

你可能听说过不少关于语言模型的进展,但如果告诉你,有一种模型不仅能生成文本,还能同时生成语音,你会不会觉得特别酷?今天咱们就来聊聊一个相当前沿的项目——Llama 3.1 Omni模型。这个模型打破了传统的文字生成边界,直接让文本和语音同时输出,实现了真正的"多模态…

【mac】MacOS无法打开XXX文件,因为无法验证开发者的问题解决

博主介绍&#xff1a; 大家好&#xff0c;我是想成为Super的Yuperman&#xff0c;互联网宇宙厂经验&#xff0c;17年医疗健康行业的码拉松奔跑者&#xff0c;曾担任技术专家、架构师、研发总监负责和主导多个应用架构。 技术范围&#xff1a; 目前专注java体系&#xff0c;以及…

OpenCv(一)

计算机视觉和机器视觉的区别 计算机视觉&#xff08;Computer Vision&#xff09;和机器视觉&#xff08;Machine Vision&#xff09;是两个密切相关但又有区别的领域。两者在应用、技术和目标上都有所不同。 **计算机视觉&#xff1a;**主要是研究如何使计算机能够理解和处理…

单细胞代谢组学数据分析利器---SCMeTA

今天继续进行新的分享&#xff0c;为了支持单细胞代谢研究的严谨性和可重复性 &#xff0c;今天分享基于时间序列的单细胞代谢数据分析处理工作流程&#xff0c;名为 SCMeTA。它保留了可扩展的接口和插件系统&#xff0c;以适应来自各种仪器的数据。对从 QE-Orbitrap MS 获取的…