注:本文只实现了数据接收部分
一、使用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));
}
}
}
}
标签:rte,基于,struct,mbuf,UDP,eth,接收,数据包,DPDK
From: https://blog.csdn.net/jinbaotong/article/details/145060671