书接上文,我们配好了dpdk的环境,我的dpdk网卡使用的ip地址和MAC地址如下:
192.168.3.100 00-0c-29-b5-5a-7c
使用的网卡名字为eth1, PCI 地址为 0000:0b:00.0
**一、**DPDK 实现的轻量级 UDP 回显服务程序
1.send_recv.c:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
// DPDK使用的头文件
#include <rte_eal.h>
#include <rte_mbuf.h>
#include <rte_ethdev.h>
#include <rte_mempool.h>
#include <rte_ether.h>
#include <rte_ip.h>
#include <rte_udp.h>
#define NUM_MBUFS (4096 - 1)
#define BURST_SIZE 32
#define UDP_PAYLOAD "Hello DPDK! This is a response packet."
// 新增:定义负载打印缓冲区大小(防止超长负载溢出)
#define MAX_PAYLOAD_PRINT 1024
int gDpdkPortId = 0;
// 修正:删除19.08.2不支持的max_lro_pkt_size字段
static const struct rte_eth_conf port_conf_default = {
.rxmode = {},
.txmode = {}
};
static void ng_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 Support eth found\n");
}
struct rte_eth_dev_info dev_info;
rte_eth_dev_info_get(gDpdkPortId,&dev_info);
const int num_rx_queues = 1;
const int num_tx_queues = 1;
struct rte_eth_conf port_conf = port_conf_default;
rte_eth_dev_configure(gDpdkPortId,num_rx_queues,num_tx_queues,&port_conf);
if(rte_eth_rx_queue_setup(gDpdkPortId,0,512,rte_eth_dev_socket_id(gDpdkPortId),NULL,mbuf_pool) < 0){
rte_exit(EXIT_FAILURE,"Could not setup RX queue\n");
}
struct rte_eth_txconf txq_conf = dev_info.default_txconf;
if(rte_eth_tx_queue_setup(gDpdkPortId,0,512,rte_eth_dev_socket_id(gDpdkPortId),
&txq_conf) < 0){
rte_exit(EXIT_FAILURE,"Could not setup TX queue\n");
}
if(rte_eth_dev_start(gDpdkPortId) < 0){
rte_exit(EXIT_FAILURE,"Could not start Ethernet port\n");
}
rte_eth_promiscuous_enable(gDpdkPortId);
printf("Port %d initialized successfully, promiscuous mode enabled\n", gDpdkPortId);
}
static int create_eth_ip_udp(struct rte_mbuf *mbuf,
uint8_t *dst_mac, uint8_t *src_mac,
uint32_t src_ip, uint32_t dst_ip,
uint16_t src_port, uint16_t dst_port,
const uint8_t *payload, uint32_t payload_len){
if(mbuf == NULL || dst_mac == NULL || src_mac == NULL || payload == NULL || payload_len == 0){
printf("Invalid parameters for packet construction\n");
return -1;
}
struct rte_ether_hdr *ehdr = NULL;
struct rte_ipv4_hdr *iphdr = NULL;
struct rte_udp_hdr *udphdr = NULL;
uint8_t *udp_payload = NULL;
const uint32_t eth_hdr_len = sizeof(struct rte_ether_hdr);
const uint32_t ip_hdr_len = sizeof(struct rte_ipv4_hdr);
const uint32_t udp_hdr_len = sizeof(struct rte_udp_hdr);
const uint32_t total_pkt_len = eth_hdr_len + ip_hdr_len + udp_hdr_len + payload_len;
ehdr = rte_pktmbuf_mtod_offset(mbuf, struct rte_ether_hdr *, 0);
iphdr = rte_pktmbuf_mtod_offset(mbuf, struct rte_ipv4_hdr *, eth_hdr_len);
udphdr = rte_pktmbuf_mtod_offset(mbuf, struct rte_udp_hdr *, eth_hdr_len + ip_hdr_len);
udp_payload = rte_pktmbuf_mtod_offset(mbuf, uint8_t *, eth_hdr_len + ip_hdr_len + udp_hdr_len);
// 修正:强制类型转换为uint8_t*,直接访问rte_ether_addr的原始字节(适配19.08.2)
memcpy((uint8_t *)&ehdr->d_addr, dst_mac, RTE_ETHER_ADDR_LEN); // 目标MAC
memcpy((uint8_t *)&ehdr->s_addr, src_mac, RTE_ETHER_ADDR_LEN); // 源MAC
ehdr->ether_type = rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4);
iphdr->version_ihl = RTE_IPV4_VHL_DEF;
iphdr->type_of_service = 0;
iphdr->total_length = rte_cpu_to_be_16(ip_hdr_len + udp_hdr_len + payload_len);
iphdr->packet_id = 0;
iphdr->fragment_offset = 0;
iphdr->time_to_live = 64;
iphdr->next_proto_id = IPPROTO_UDP;
iphdr->src_addr = src_ip;
iphdr->dst_addr = dst_ip;
iphdr->hdr_checksum = 0;
iphdr->hdr_checksum = rte_ipv4_cksum(iphdr);
udphdr->src_port = rte_cpu_to_be_16(src_port);
udphdr->dst_port = rte_cpu_to_be_16(dst_port);
udphdr->dgram_len = rte_cpu_to_be_16(udp_hdr_len + payload_len);
udphdr->dgram_cksum = 0;
memcpy(udp_payload, payload, payload_len);
mbuf->pkt_len = total_pkt_len;
mbuf->data_len = total_pkt_len;
return 0;
}
static int send_udp_response(struct rte_mempool *mbuf_pool,
struct rte_ether_hdr *recv_ehdr,
struct rte_ipv4_hdr *recv_iphdr,
struct rte_udp_hdr *recv_udphdr){
struct rte_mbuf *tx_mbuf = rte_pktmbuf_alloc(mbuf_pool);
if(tx_mbuf == NULL){
printf("Failed to allocate mbuf for TX packet\n");
return -1;
}
uint8_t src_mac[RTE_ETHER_ADDR_LEN] = {0};
uint8_t dst_mac[RTE_ETHER_ADDR_LEN] = {0};
// 修正:强制类型转换为uint8_t*,直接访问rte_ether_addr的原始字节(适配19.08.2)
memcpy(src_mac, (uint8_t *)&recv_ehdr->d_addr, RTE_ETHER_ADDR_LEN);
memcpy(dst_mac, (uint8_t *)&recv_ehdr->s_addr, RTE_ETHER_ADDR_LEN);
uint32_t src_ip = recv_iphdr->dst_addr;
uint32_t dst_ip = recv_iphdr->src_addr;
uint16_t src_port = ntohs(recv_udphdr->dst_port);
uint16_t dst_port = ntohs(recv_udphdr->src_port);
const uint8_t *payload = (const uint8_t *)UDP_PAYLOAD;
uint32_t payload_len = strlen(UDP_PAYLOAD);
if(create_eth_ip_udp(tx_mbuf,dst_mac,src_mac,src_ip,dst_ip,src_port,dst_port,
payload,payload_len) < 0){
rte_pktmbuf_free(tx_mbuf);
return -1;
}
struct rte_mbuf *tx_mbufs[1] = {tx_mbuf};
uint16_t num_sent = rte_eth_tx_burst(gDpdkPortId,0,tx_mbufs,1);
if(num_sent < 1){
printf("Failed to send response packet, num_sent: %u\n", num_sent);
rte_pktmbuf_free(tx_mbuf);
return -1;
}
struct in_addr src_addr, dst_addr;
src_addr.s_addr = src_ip;
dst_addr.s_addr = dst_ip;
printf("Response sent: %s:%d -> %s:%d, payload: %s\n",
inet_ntoa(src_addr), src_port,
inet_ntoa(dst_addr), dst_port,
UDP_PAYLOAD);
return 0;
}
int main(int argc,char *argv[]){
int ret = rte_eal_init(argc,argv);
if(ret < 0){
fprintf(stderr, "DPDK EAL初始化失败!错误码:%d\n", ret);
rte_exit(EXIT_FAILURE,"EAL init error!");
}
argc -= ret;
argv += ret;
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,"mbuf pool create error!");
}
printf("mbuf pool created successfully\n");
ng_init_port(mbuf_pool);
while(1){
struct rte_mbuf *mbufs[BURST_SIZE];
unsigned num_recv = rte_eth_rx_burst(gDpdkPortId,0,mbufs,BURST_SIZE);
if(num_recv > BURST_SIZE){
rte_exit(EXIT_FAILURE,"Invalid number of received packets");
}
for(unsigned int i = 0;i < num_recv;i++){
int need_process = 1;
struct rte_ether_hdr *ehdr = NULL;
struct rte_ipv4_hdr *iphdr = NULL;
struct rte_udp_hdr *udphdr = NULL;
uint8_t *recv_payload = NULL; // 新增:接收负载指针
uint32_t recv_payload_len = 0; // 新增:接收负载长度
char payload_buf[MAX_PAYLOAD_PRINT] = {0}; // 新增:负载打印缓冲区
ehdr = rte_pktmbuf_mtod(mbufs[i],struct rte_ether_hdr *);
if(ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4)){
need_process = 0;
}
if(need_process){
iphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_ipv4_hdr *,
sizeof(struct rte_ether_hdr));
if(iphdr == NULL || iphdr->next_proto_id != IPPROTO_UDP){
need_process = 0;
}
}
if(need_process){
udphdr = rte_pktmbuf_mtod_offset(mbufs[i], struct rte_udp_hdr *,
sizeof(struct rte_ether_hdr) + sizeof(struct rte_ipv4_hdr));
if(udphdr == NULL){
need_process = 0;
} else {
// 新增:计算UDP负载起始地址和长度
const uint32_t hdr_total_len = sizeof(struct rte_ether_hdr) +
sizeof(struct rte_ipv4_hdr) +
sizeof(struct rte_udp_hdr);
// 1. 获取负载起始地址
recv_payload = rte_pktmbuf_mtod_offset(mbufs[i], uint8_t *, hdr_total_len);
// 2. 计算负载长度(网络字节序转主机字节序后,减去UDP头长度)
recv_payload_len = ntohs(udphdr->dgram_len) - sizeof(struct rte_udp_hdr);
// 3. 安全拷贝负载到打印缓冲区(防止超长负载溢出)
if(recv_payload_len > 0 && recv_payload != NULL){
uint32_t copy_len = (recv_payload_len > MAX_PAYLOAD_PRINT - 1) ?
(MAX_PAYLOAD_PRINT - 1) : recv_payload_len;
memcpy(payload_buf, recv_payload, copy_len);
payload_buf[copy_len] = '\0'; // 字符串结尾符,保证正常打印
}
// 新增:打印接收到的UDP负载
struct in_addr src_addr, dst_addr;
src_addr.s_addr = iphdr->src_addr;
dst_addr.s_addr = iphdr->dst_addr;
printf("Received UDP packet: %s:%d -> %s:%d\n",
inet_ntoa(src_addr), ntohs(udphdr->src_port),
inet_ntoa(dst_addr), ntohs(udphdr->dst_port));
printf("Received UDP payload length: %u bytes\n", recv_payload_len);
printf("Received UDP payload content: %s\n",
(recv_payload_len > 0) ? payload_buf : "Empty payload");
printf("----------------------------------------------------\n");
send_udp_response(mbuf_pool, ehdr, iphdr, udphdr);
}
}
rte_pktmbuf_free(mbufs[i]);
}
}
return 0;
}
2、Makefile:
从dpdk源码中copy一份,将源文件文件名和生成的文件名改为send_recv。
# SPDX-License-Identifier: BSD-3-Clause
# Copyright(c) 2010-2014 Intel Corporation
# binary name
APP = send_recv
# all source are stored in SRCS-y
SRCS-y := send_recv.c
# Build using pkg-config variables if possible
ifeq ($(shell pkg-config --exists libdpdk && echo 0),0)
all: shared
.PHONY: shared static
shared: build/$(APP)-shared
ln -sf $(APP)-shared build/$(APP)
static: build/$(APP)-static
ln -sf $(APP)-static build/$(APP)
PKGCONF=pkg-config --define-prefix
PC_FILE := $(shell $(PKGCONF) --path libdpdk)
CFLAGS += -O3 $(shell $(PKGCONF) --cflags libdpdk)
CFLAGS += -DALLOW_EXPERIMENTAL_API
LDFLAGS_SHARED = $(shell $(PKGCONF) --libs libdpdk)
LDFLAGS_STATIC = -Wl,-Bstatic $(shell $(PKGCONF) --static --libs libdpdk)
build/$(APP)-shared: $(SRCS-y) Makefile $(PC_FILE) | build
$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_SHARED)
build/$(APP)-static: $(SRCS-y) Makefile $(PC_FILE) | build
$(CC) $(CFLAGS) $(SRCS-y) -o $@ $(LDFLAGS) $(LDFLAGS_STATIC)
build:
@mkdir -p $@
.PHONY: clean
clean:
rm -f build/$(APP) build/$(APP)-static build/$(APP)-shared
test -d build && rmdir -p build || true
else # Build using legacy build system
ifeq ($(RTE_SDK),)
$(error "Please define RTE_SDK environment variable")
endif
# Default target, detect a build directory, by looking for a path with a .config
RTE_TARGET ?= $(notdir $(abspath $(dir $(firstword $(wildcard $(RTE_SDK)/*/.config)))))
include $(RTE_SDK)/mk/rte.vars.mk
ifneq ($(CONFIG_RTE_EXEC_ENV_LINUX),y)
$(error This application can only operate in a linux environment, \
please change the definition of the RTE_TARGET environment variable)
endif
CFLAGS += -O3
CFLAGS += -DALLOW_EXPERIMENTAL_API
CFLAGS += $(WERROR_FLAGS)
include $(RTE_SDK)/mk/rte.extapp.mk
endif
3. 代码讲解
这个代码基于 DPDK 实现的轻量级 UDP 回显服务程序:它初始化 DPDK 运行环境和网络端口,以轮询(Polling)方式批量接收网卡上的 IPv4 UDP 数据包,解析数据包的 MAC/IP/UDP 头和负载并打印,然后构造反向的 UDP 响应包(源 / 目地址 / 端口互换),最后将响应包发送回原客户端。
cpp
main() // 程序入口
├── rte_eal_init() // 1. 初始化DPDK EAL环境
├── rte_pktmbuf_pool_create()// 2. 创建mbuf内存池(存储数据包)
├── ng_init_port() // 3. 初始化网卡端口
│ ├── rte_eth_dev_count_avail() // 检测可用网卡数量
│ ├── rte_eth_dev_info_get() // 获取网卡设备信息
│ ├── rte_eth_dev_configure() // 配置网卡RX/TX队列数
│ ├── rte_eth_rx_queue_setup() // 初始化RX队列
│ ├── rte_eth_tx_queue_setup() // 初始化TX队列
│ ├── rte_eth_dev_start() // 启动网卡
│ └── rte_eth_promiscuous_enable() // 开启混杂模式
└── 无限循环(收包处理)
├── rte_eth_rx_burst() // 批量接收数据包
├── 数据包解析(eth/ip/udp)
├── send_udp_response() // 构造并发送响应包
│ ├── rte_pktmbuf_alloc() // 分配新的mbuf
│ ├── create_eth_ip_udp() // 构造UDP数据包
│ │ ├── rte_pktmbuf_mtod_offset() // 获取mbuf中指定偏移的指针
│ │ ├── rte_cpu_to_be_16() // 主机序转网络大端序
│ │ └── rte_ipv4_cksum() // 计算IPv4校验和
│ ├── rte_eth_tx_burst() // 批量发送响应包
│ └── rte_pktmbuf_free() // 释放mbuf(发送失败时)
└── rte_pktmbuf_free() // 释放接收的mbuf
- EAL 环境初始化 调用
rte_eal_init()初始化 DPDK 的环境抽象层(EAL),这是所有 DPDK 程序的第一步。EAL 会完成 CPU 核心绑定、内存区域划分、网卡设备探测、驱动加载等底层工作,返回值表示处理的命令行参数个数,剩余参数供应用层使用。- mbuf 内存池创建 调用
rte_pktmbuf_pool_create()创建专门存储数据包的 mbuf 内存池。mbuf 是 DPDK 中用于封装数据包的核心数据结构,内存池预分配固定数量的 mbuf(代码中为 4095 个),避免运行时动态分配内存的性能损耗。- 网卡端口初始化(ng_init_port 函数)
- 检测系统中可用的网卡数量,无可用网卡则直接退出;
- 获取网卡的设备信息(如默认 TX/RX 队列配置);
- 配置网卡的 RX/TX 队列数量(代码中各 1 个);
- 初始化 RX 队列:指定队列大小(512),并关联 mbuf 内存池(收包时直接从池里取 mbuf);
- 初始化 TX 队列:使用网卡默认的 TX 配置;
- 启动网卡设备,使其进入可收发包状态;
- 开启网卡混杂模式,确保能接收所有经过网卡的数据包(而非仅目标 MAC 为自身的包)。
阶段 2:运行阶段(无限循环收发包)
初始化完成后,程序进入无限循环,持续处理数据包:
- 批量收包 调用
rte_eth_rx_burst()以 "批量(Burst)" 方式从 RX 队列接收数据包,这是 DPDK 高性能的核心 ------ 批量操作减少系统调用和上下文切换,代码中每次最多接收 32 个包(BURST_SIZE),返回实际接收的包数量。- 逐个解析数据包 对每个接收的数据包,依次解析:
- 以太网头(rte_ether_hdr):检查是否为 IPv4 数据包(ether_type=0x0800);
- IPv4 头(rte_ipv4_hdr):检查上层协议是否为 UDP(next_proto_id=IPPROTO_UDP);
- UDP 头(rte_udp_hdr):计算 UDP 负载的起始地址和长度,安全拷贝负载到缓冲区并打印(源 / 目 IP、端口、负载内容)。
- 构造并发送响应包 调用
send_udp_response()构造反向响应包:
- 互换源 / 目 MAC(接收包的源 MAC→响应包的目的 MAC,反之亦然);
- 互换源 / 目 IP 和端口(接收包的源 IP / 端口→响应包的目的 IP / 端口,反之亦然);
- 调用
create_eth_ip_udp()填充以太网、IPv4、UDP 头,并写入自定义负载("Hello DPDK! This is a response packet.");- 调用
rte_eth_tx_burst()批量发送响应包(代码中每次发 1 个);- 发送失败则释放分配的 mbuf,避免内存泄漏。
- 释放 mbuf 无论是否处理成功,都调用
rte_pktmbuf_free()释放接收的 mbuf,将其归还到内存池,供后续收包复用。
4.编译运行

运行./build/send_recv

注意,现在由于开启了混杂模式,所以能够收到经过该网卡的数据包,但是此时如果使用宿主机的网络调试助手,向192.168.3.100(dpdk网卡的ip)发送数据包,dpdk无法收到。核心原因就是宿主机没有这个ip与dpdk网卡MAC地址的arp条目,即不知道该往哪发。因此使用netsh命令配置条目后可以就发成功(具体见上篇)。