最近对DPDK技术颇感兴趣,工作之余想要系统性学习一下。借此平台记录下学习心得,方便以后回顾。
DPDK环境搭建就不赘述了,学习采用DPDK官方代码的1908版本。
今天记录下第一天的收获。
#include<rte_eal.h>
#include<rte_ethdev.h>
#include<rte_mbuf.h>
#include<stdio.h>
#include<arpa/inet.h>
int gDpdkPortId = 0;//eth0
#define NUM_MBUFS (4096-1)
#define BURST_SIZE 32
static const struct rte_eth_conf port_conf_default = {
.rxmode = {.max_rx_pkt_len = RTE_ETHER_MAX_LEN} //RTE_ETHER_MAX_LEN 以太网数据中长度,一般为1518
}
static void ng_init_port(struct rte_mempool *mbuf_pool)
{
//查询系统中可用的以太网设备数量,比如eth0,eth1等
uint16_t nb_sys_ports = rte_eth_dev_count_avail();
if(nb_sys_ports == 0)
{
rte_exit(EXIT,"no eth dev is availble\n");
}
struct rte_eth_dev_info dev_info;
//查询以太网接口属性,此处的id = 0,代表查询eth0
rte_eth_dev_info_get(gDpdkPortId,&dev_info);
const int num_rx_queues = 1;//设置接受队列大小,通常每个队列与一个独立CPU关联
const int num_tx_queues = 0;
struct rte_eth_conf port_conf = port_conf_default;
//配置eth0相关属性,用于后面接收发送数据包
rte_eth_dev_configure(gDpdkPortId,num_rx_queues,num_tx_queues,&port_conf);
//用于配置以太网设备的接收队列
if(rte_eth_rx_queue_setup(gDpdkPortId,0,128,
rte_eth_dev_socket_id(gDpdkPortId),NULL,mbuf_pool) < 0)
{
rte_exit(EXIT,"could not setup RX queue\n");
}
//启动指定的网卡,使其能够接收和发送数据包
//初始化指定的以太网设备,配置接收队列和设备属性,并启动该网卡,以便进行数据包的收发和处理操作
if(rte_eth_dev_start(gDpdkPortId) < 0)
{
rte_exit(EXIT,"can not start\n");
}
}
int main(int argc,char *argv[])
{
//初始化EAL环境
if(rte_eal_init(argc,argv) < 0 )
{
rte_exit(EXIT,"Error with EAL init\n");
}
//创建内存池
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,"Could not create mbuf pool\n");
}
//mbuf_pool 是一个预先创建好的内存池,它将被用于接收队列来存储数据包的缓冲区
ng_init_port(mbuf_pool);
while(1)
{
struct rte_mbuf *mbufs[BURST_SIZE];
//mbufs用于存储数据包的缓冲区结构体
//BURST_SIZE表示每次从网卡接收数据包的最大数量
unsigned num_recvd = rte_eth_rx_burst(gDpdkPortId,0,mbufs,BURST_SIZE);
if(num_recvd > BURST_SIZE)
{
rte_exit(EXIT,"Error receiving from eth\n");
}
unsigned i = 0;
for(i = 0;i < num_recvd;i++)
{
//rte_ether_hdr是DPDK 中用于表示以太网数据包头部的结构体
//rte_pktmbuf_mtod用于将数据包缓冲区中的数据指针转换为特定类型的指针,以方便对数据包头部进行解析
struct rte_ether_hdr ehdr = rte_pktmbuf_mtod(mbufs[i],struct rte_ether_hdr *);
//rte_cpu_to_be_16用于将 16 位的数据从主机字节序(CPU 字节序)转换为网络字节序(大端字节序)
if(ehdr->ether_type != rte_cpu_to_be_16(RTE_ETHER_TYPE_IPV4))
{
continue;
}
//rte_pktmbuf_mtod_offset来获取数据包缓冲区中 IPv4 头部的指针
//将数据包偏移以太网数据包头部大小后,就是IPV4头部信息,再转换为struct rte_ipv4_hdr *
struct rte_ipv4_hdr * iphdr = rte_pktmbuf_mtod_offset(mbufs[i],struct rte_ipv4_hdr *,
sizeof(struct rte_ether_hdr));
if(iphdr->next_proto_id == IPPROTO_UDP)
{
//(iphdr + 1) +1指的是偏移rte_ipv4_hdr大小
struct rte_udp_hdr *udphdr = (struct rte_udp_hdr *)(iphdr + 1);
uint16_t length = ntohs(udphdr->dgram_len);
*((char *)udphdr + length) = '\0';
struct in_addr addr;
addr.s_addr = iphdr->src_addr;
printf("src: %s:%d\n",inet_ntoa(addr),udphdr->src_port);
addr.s_addr = iphdr->dst_addr;
printf("dst: %s:%d data:%s\n",inet_ntoa(addr),udphdr->dst_port,(char *)(udphdr+1));
rte_pktmbuf_free(mbufs[i]);
}
}
}
}
接下来在配置好的DPDK环境中编译运行程序,通过网络调试助手发送数据。
最后可以看到程序将得到的UDP数据打印出来,至此,自己写的第一个关于DPDK的收包程序完毕,可以睡个好觉了!!明天再冲