首页 > 其他分享 >基于DPDK的用户态协议栈(2)基于DPDK实现UDP的数据接收

基于DPDK的用户态协议栈(2)基于DPDK实现UDP的数据接收

时间:2025-01-13 18:57:32浏览次数:3  
标签:rte 基于 struct mbuf UDP eth 接收 数据包 DPDK

注:本文只实现了数据接收部分

一、使用DPDK实现UDP的数据接收流程

1.1初始化EAL

main(int argc, char *argv[]) {//main函数的标准参数,用于接收命令行参数。argc表示参数的数量,argv是一个指向字符串数组的指针,这些字符串是传递给程序的命令行参数。
    // 初始化EAL。
    if (rte_eal_init(argc, argv) < 0) {//DPDK提供用于初始化EAL的函数,接收与main函数相同的命令行参数,成功返回0 ,如果初始化失败返回一个负值。
        rte_exit(EXIT_FAILURE, "Error with EAL init\n");//DPDK提供的用于出现错误时请理资源并退出程序
    }

        在DPDK中,EAL(Environment Abstraction Layer,环境抽象层)它为用户空间的应用程序提供了一个与底层硬件和操作系统交互的接口。初始化EAL是DPDK应用程序启动时的首要任务,初始化成功后才可以进行后续的配置步骤。

1.2创建mbuf内存池

创建一个mbuf内存池,用于存储接收到的数据包。

#define NUM_MBUFS  4096
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");
    }

rte_pktmbuf_pool_create是DPDK中用于创建专门用于储存数据包内存缓冲区(mbuf)的内存池。mubf是DPDK用于表示和处理网络数据包的基本数据结构。以下是该函数的参数:

struct rte_mempool *rte_pktmbuf_pool_create(const char *name,//内存池的名称
                        unsigned int n,//预先分配并保存在内存池中的mbuf数量
                        unsigned int cache_size,//在内存池的本地缓存中可以存储的mbuf的个数。如果设置为0则不用缓存。适当的设置缓存大小可以减少内存分配和释放的开销。
                        unsigned int private_data_size,//每个mbuf可以附加的私有数据大小。不需要则设置为0.
                        uint16_t mbuf_data_room_size,//mbuf中用于存储数据包数据的空间大小。RTE_MBUF_DEFAULT_BUF_SIZE 通常为一个合理的默认值
                        int socket_id);//指定在哪个NUMA节点上分配内存池。rte_socket_id() 函数可以用于获取当前线程的 NUMA 节点 ID。

1.3初始化网卡端口

static const struct rte_eth_conf port_conf_default = {
	.rxmode = { .max_rx_pkt_len = RTE_ETHER_MAX_LEN }//设置了最大接收数据包长度
};

static int ustack_init_port(struct rte_mempool *mbuf_pool) {
    uint16_t nb_sys_ports = rte_eth_dev_count_avail(); // 获取系统中可用的网卡设备数量。
    if (nb_sys_ports == 0) { // 如果没有可用的网卡设备,则退出程序。
        rte_exit(EXIT_FAILURE, "No Supported eth found\n");
    }
    const int num_rx_queues = 1;

	const int num_tx_queues = 0;

    // 配置以太网端口,设置接收和发送队列的数量,以及使用默认配置(但这里默认配置未定义)。
    rte_eth_dev_configure(global_portid, num_rx_queues, num_tx_queues, &port_conf_default); 

    // 设置接收队列。
    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 (rte_eth_dev_start(global_portid) < 0) {
        rte_exit(EXIT_FAILURE, "Could not start\n");
    }

    return 0; // 初始化成功。
}
rte_eth_dev_count_avail()用于获取当前可用的以太网设备(网卡)数量
rte_eth_dev_configure()用于配置以太网设备(网卡)的参数。允许开发者设置端口的接收(RX)队列和发送(TX)队列的数量,以及应用特定的端口配置。
rte_eth_rx_queue_setup用于配置以太网设备的接收队列
rte_eth_dev_start用于启动一个以太网设备。
int rte_eth_dev_configure(
    uint16_t portid,//要配置的以太网设备的端口ID
    uint16_t nb_rx_queue,//接收队列的数量
    uint16_t nb_tx_queue,//发送队列的数量
    const struct rte_eth_conf *eth_conf);//包含了端口的特定配置选项
int rte_eth_rx_queue_setup(uint16_t portid,//网卡的端口ID
                           uint16_t rx_queue_id,//接收队列的ID
                           uint16_t nb_rx_desc,//接收描述符的数量。用于存储接收到的数据包信息
                           unsigned int socket_id,//指定内存分配所在的NUMA节点ID。有助于优化内存访问性能
                           const struct rte_eth_rxconf *rxconf,//指向一个结构指针,包含了接收队列的配置信息。
                           struct rte_mempool *mp);//指向内存池的指针

1.4接收和处理数据包

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

    // 无限循环,用于持续接收和处理数据包。
    while (1) {
        struct rte_mbuf *mbufs[BURST_SIZE] = {0}; // 定义一个mbuf数组,用于存储从端口接收到的数据包。

        // 从指定的接收队列中批量接收数据包。
        uint16_t num_recvd = rte_eth_rx_burst(global_portid, 0, mbufs, BURST_SIZE);
        if (num_recvd > BURST_SIZE) { // 判断接收是否正常。
            rte_exit(EXIT_FAILURE, "Error receiving from eth\n");
        }

        // 遍历接收到的数据包。
        for (int i = 0; i < num_recvd; i++) {
            // 获取数据包的以太网头部。
            struct rte_ether_hdr *ethhdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr *);
            // 如果数据包不是IPv4类型,则跳过。使用rte_cpu_to_be_16宏将主机的小端字节序转换为大端字节序。因为网络协议通常使用大端字节序。
            if (ethhdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
                continue;
            }

            // 获取数据包的IPv4头部。
            struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
            // 如果下一个协议是UDP,则处理UDP数据包。
            if (iphdr->next_proto_id == IPPROTO_UDP) {
                // 注意:这里的处理方式有误,因为udphdr+1可能指向无效内存。
                struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1);
                // 尝试打印UDP数据包的内容(这里存在安全风险)。
                printf("udp : %s\n", (char*)(udphdr+1)); // 
            }
        }
    }
}
rte_eth_rx_burst用于从指定的以太网设备接收队列中批量获取数据包。

uint16_t rte_eth_rx_burst(uint16_t portid,//网卡端口ID
                          uint16_t rx_queue_id,//接收数据包的接收队列ID
                          struct rte_mbuf **rx_pkts,//指向结构体指针数组的指针,该数组用于存储从接收队列中获取的数据包
                          uint16_t nb_pkts);//期望从接收队列中获取数据包数量

1.5完整代码

#include <stdio.h> 

#include <rte_eal.h> 
#include <rte_ethdev.h>

#include <arpa/inet.h> 

int global_portid = 0; // 定义一个全局变量,用于存储网卡的ID。

// 定义内存池大小和每次接收操作的最大数据包数量。
#define NUM_MBUFS  4096 
#define BURST_SIZE 128

static const struct rte_eth_conf port_conf_default = {
	.rxmode = { .max_rx_pkt_len = RTE_ETHER_MAX_LEN }
};

// 初始化以太网端口的函数。
static int ustack_init_port(struct rte_mempool *mbuf_pool) {
    uint16_t nb_sys_ports = rte_eth_dev_count_avail(); // 获取系统中可用的网卡设备数量。
    if (nb_sys_ports == 0) { // 如果没有可用的网卡设备,则退出程序。
        rte_exit(EXIT_FAILURE, "No Supported eth found\n");
    }
    // 配置以太网端口,设置接收和发送队列的数量,以及使用默认配置(但这里默认配置未定义)。
    rte_eth_dev_configure(global_portid, 1, 0, &port_conf_default); 

    // 设置接收队列。
    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 (rte_eth_dev_start(global_portid) < 0) {
        rte_exit(EXIT_FAILURE, "Could not start\n");
    }

    return 0; // 初始化成功。
}

// 主函数。
int main(int argc, char *argv[]) {
    // 初始化EAL。
    if (rte_eal_init(argc, argv) < 0) {
        rte_exit(EXIT_FAILURE, "Error with EAL init\n");
    }

    // 创建一个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}; // 定义一个mbuf数组,用于存储从端口接收到的数据包。

        // 从指定的接收队列中批量接收数据包。
        uint16_t num_recvd = rte_eth_rx_burst(global_portid, 0, mbufs, BURST_SIZE);
        if (num_recvd > BURST_SIZE) { 
            rte_exit(EXIT_FAILURE, "Error receiving from eth\n");
        }

        // 遍历接收到的数据包。
        for (int i = 0; i < num_recvd; i++) {
            // 获取数据包的以太网头部。
            struct rte_ether_hdr *ethhdr = rte_pktmbuf_mtod(mbufs[i], struct rte_ether_hdr *);
            // 如果数据包不是IPv4类型,则跳过。
            if (ethhdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)) {
                continue;
            }

            // 获取数据包的IPv4头部。
            struct rte_ipv4_hdr *iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *, sizeof(struct rte_ether_hdr));
            // 如果下一个协议是UDP,则处理UDP数据包。
            if (iphdr->next_proto_id == IPPROTO_UDP) {

                struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1);

                printf("udp : %s\n", (char*)(udphdr+1)); 
            }
        }
    }
}

0voice · GitHub

标签:rte,基于,struct,mbuf,UDP,eth,接收,数据包,DPDK
From: https://blog.csdn.net/jinbaotong/article/details/145060671

相关文章

  • 基于STM32F103标准库实现FFT,并实现音乐频谱绘制
    整个工程文件是在江科大的OLED显示屏OLED-V2.0版本IIC四针脚接口UTF-8的工程上编写的,在屏幕显示过程中,只用到了OLED显示屏的绘制直线和绘制像素点两个函数(注意,显示屏的绘制函数坐标可以任意指定,而不是按页写入。任一屏幕只要有上述两个函数均可使用。工程接线:STM32F103C8T6......
  • 基于java的停车场车牌识别系统
    一、系统背景与意义随着城市化进程的加速,停车场管理面临着越来越大的挑战。传统的手工记录车牌号方式不仅费时费力,还容易出错。而基于Java的停车场车牌识别系统的出现,则有效地解决了这一问题。该系统能够自动识别进出停车场的车辆车牌号,实现快速、准确的车辆管理,提高了停车......
  • 基于Java的外卖骑手管理系统
    一、系统背景与意义随着外卖行业的蓬勃发展,外卖配送成为了一个关键环节。传统的骑手管理方式存在调度不合理、信息更新不及时等问题,影响了配送效率和用户满意度。因此,开发一个基于Java的外卖骑手管理系统具有重要意义,它可以实现骑手的自动化管理,优化配送流程,提高配送效率,同......
  • 【最新原创毕设】基于SSM的在线学习平台+09650(免费领源码)可做计算机毕业设计JAVA、PHP
    目 录摘要1绪论1.1选题背景及意义1.2国内外现状分析1.3论文结构与章节安排2 在线学习平台系统分析2.1可行性分析2.2系统业务流程分析2.3系统功能分析2.3.1功能性分析2.3.2非功能性分析2.4系统用例分析2.5本章小结3在线学习平台总体设......
  • 基于Spring Boot+Vue的大学生创业项目的信息管理系统
    一、系统概述该系统旨在为大学生创业项目提供一个高效、便捷的信息管理平台,帮助创业者更好地管理项目信息、团队成员、进度安排等关键数据。通过SpringBoot和Vue的结合,系统实现了前后端分离,提高了开发效率和系统的可维护性。二、技术架构前端技术:系统前端采用Vue.js框架......
  • 基于Java技术的救灾物资调动系统
    一、系统背景与意义随着自然灾害的频发,救灾工作的重要性日益凸显。传统的救灾物资管理方式往往依赖于人工操作,存在信息不透明、响应速度慢、资源分配不均等问题,难以满足快速响应和高效调配的需求。因此,开发基于Java技术的救灾物资调动系统显得尤为重要。该系统通过信息化手......
  • 基于Java农产品系统
    一、系统背景与意义随着农业科技的发展和农业产业化的推进,农产品销售行业面临着越来越多的挑战和机遇。为了提升农产品销售的效率与便捷性,降低运营成本,并为消费者提供更好的购物体验,开发基于Java的农产品系统显得尤为重要。该系统通过信息化手段,整合农产品资源,优化销售流程......
  • 毕业设计 基于Java的网上书城的设计与实现
    源码获取欢迎留言一、摘要本论文旨在通过对网上书城系统的设计与实现,探讨Java语言在电子商务应用中的优势以及系统的性能与用户体验。随着互联网的快速发展,电子商务已经成为现代商业活动中不可或缺的一部分。网上书城作为电子商务的重要形式之一,其设计与实现具有重要意义。......
  • 【AIGC-ChatGPT进阶提示词指令】智慧母婴:打造基于成长树的儿童发展引导系统
    第一次进入全站综合热榜,有点紧张好了,开始今天的内容,今天的内容是基于育儿的系统今天继续回馈大家,最近都是可以在自媒体上使用的提示词。提示词在最下方引言在人工智能时代,如何将传统育儿智慧与现代教育理念有机结合,为父母提供更直观、系统的育儿指导,成为一个值得探......
  • 基于SpringBoot+Vue的宠物医院管理系统的设计与实现(源码+lw+部署+讲解)
    文章目录1.前言2.详细视频演示3.具体实现截图4.技术可行性分析5.技术简介5.1后端框架SpringBoot5.2前端框架Vue5.3系统开发平台6.系统架构设计7.程序操作流程8.业务流程设计9.为什么选择我们9.1自己的公众号9.2海量实战案例10.代码参考11.数据库参考12.源码及文档获取......