dpdk的接口全部都是C语言实现的,它的makefile模版也很简单,只需改变其宏就可以在生成可执行文件、静态库、动态库之间切换。本篇博文主要给出如果使用dpdk封装成静态库后,在C++应用程序中编译使用的方法。
一、封装dpdk抓包接口
源代码实现的功能:
1、可通过配置文件进行配置多个队列收取数据包,并且保证数据包的同源同宿(或者负载均衡,二者取其一,具体方法须修改网卡驱动程序);
2、实时显示收到总包数、Gbps、pps。
jz_dpdk_api.h
/********************************************************************
Copyright (C), 2001-2017, XXX Co., Ltd.
File Name : jz_dpdk_api.h
Version : Initial Draft
Author :
Created : 2017.06.19
Last Modified: 2017.06.19
Description : Api of capture packets by dpdk,with max speed 10Gbps.
*********************************************************************/
#ifndef JZ_DPDK_H
#define JZ_DPDK_H
#include
#include
#include
#include
#ifdef __cplusplus
extern "C" {
#endif
/*
* rx_number:从配置文件中读取的线程数,用于调用者绑定cpu以及开启多线程
*/
extern int rx_number;
/*
* 描述: 初始化函数,对端口、队列、大页内存等配置。
* 参数: 无。
* 返回值: 初始化成功返回0,失败返回-1。
*/
int jz_dpdk_init(int argc,char **argv);
/*
* 描述: 接收包。
* 参数: uint8_t : 端口号
uint16_t: 队列号
struct rte_mbuf * :存储接收到包的数组指针,一次性返回多个数据包。
* uint16_t : 一次接收包的个数,需要根据包长以及配置的单个页大小来设定。
* 如:页大小为2M,设定接收包大概是1500字节,那么设置32.
* 返回值:返回收到包的个数,如果为-1,则说明收包失败。
*/
int jz_dpdk_recv_pkts(uint8_t port_id,uint16_t queue_id,struct rte_mbuf *pkts[],const uint16_t nb_pkts);
/*
* 描述: 释放dpdk收包内存,注意:此接口必须在收包成功后调用。
* 参数:struct rte_mbuf * :存储接收到包的数组指针。
* uint16_t :收到包的个数,与接口 jz_dpdk_recv_pkt 配合使用。
* 返回值: 无。
*/
void jz_dpdk_free(struct rte_mbuf *pkts[], uint16_t nb_pkts);
/*
* 描述: 返回当前收包状态,如pps,总包数。
* 参数: 无。
* 返回值: 无。
*/
void jz_dpdk_current_stat(void);
#ifdef __cplusplus
};
#endif
#endif
#include "jz_dpdk_api.h"
#include "ini/iniparser.h"
/**************************宏********************************/
#define NUM_MBUFS_ 8191
#define MBUF_CACHE_SIZE_ 512
#define RX_RING_SIZE_ 128
#define TX_RING_SIZE_ 512
/**************************全局变量**************************/
int rx_number = 0;
uint64_t total_pkts[512] = {0};
uint64_t last_total_pkts[512] = {0};
uint64_t total_pkts_bytes[512] = {0};
uint64_t last_total_pkts_bytes[512] = {0};
uint64_t last_time = 0;
static const struct rte_eth_conf port_conf_default =
{
.rxmode = {
.max_rx_pkt_len = ETHER_MAX_LEN,
.mq_mode = ETH_MQ_RX_RSS
},
.rx_adv_conf = {
.rss_conf = {
.rss_key = NULL,
.rss_hf = ETH_RSS_IP,
}
}
};
/**************************函数声明*************************/
static int config_init(const char *filename);
static int port_init(int port,struct rte_mempool *mbuf_pool);
static void recv_pkt_init(void);
/**************************接口实现*************************/
int jz_dpdk_init(int argc,char **argv)
{
const char *filename = "conf.ini";
// 0.配置文件读取
rx_number = config_init(filename);
if( rx_number < 1)
{
printf("错误:读取配置文件[conf.ini] 失败!\n");
return -1;
}
// 1. DPDK EAL初始化
if( rte_eal_init(argc,argv) < 0 )
{
printf("错误:EAL初始化失败!\n");
return -1;
}
else
{
printf("EAL初始化成功。\n");
}
// 2. 检测至少有一个端口(即网口)
if( rte_eth_dev_count() != 1)
{
printf("错误:只能有一个端口(网口)。\n");
return -1;
}
// 3. 内存池的创建
struct rte_mempool *mbuf_pool = rte_pktmbuf_pool_create("JZ_MBUF_POOL",
NUM_MBUFS_ * rte_eth_dev_count(), MBUF_CACHE_SIZE_, 0,
RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id());
if( NULL == mbuf_pool )
{
printf("错误:创建内存池失败。\n");
return -1;
}
else
{
printf("创建内存池成功。\n");
}
// 4. 对端口进行初始化
int port = 0;
for (; port < rte_eth_dev_count(); port++)
{
if( port_init(port, mbuf_pool) < 0)
{
printf("错误:端口初始化失败。\n");
return -1;
}
else
{
printf("端口初始化成功。\n");
}
}
recv_pkt_init();
printf("\n恭喜:jz_dpdk 初始化成功!\n");
return 0;
}
int jz_dpdk_recv_pkts(uint8_t port_id,uint16_t queue_id,struct rte_mbuf *pkts[],const uint16_t nb_pkts)
{
uint16_t nb_rx = rte_eth_rx_burst(port_id, queue_id, pkts, nb_pkts);
if( nb_rx > 0)
{
total_pkts[queue_id] += nb_rx;
uint16_t i;
for ( i = 0; i < nb_rx; i++)
{
total_pkts_bytes[queue_id] += pkts[i]->pkt_len + 24;
}
}
return nb_rx;
}
void jz_dpdk_free(struct rte_mbuf *pkts[], uint16_t nb_pkts)
{
uint16_t i;
for ( i = 0; i < nb_pkts; i++)
{
rte_pktmbuf_free(pkts[i]);
}
}
void jz_dpdk_current_stat(void)
{
uint64_t now = time(NULL);
uint64_t total_pps = 0;
uint64_t total_pkts_all = 0;
float total_gbps = 0.0;
uint16_t i = 0;
printf("---------------------------------------------------------\n");
for(; i < rx_number; i++)
{
uint64_t pps = (total_pkts[i] - last_total_pkts[i]) / (now - last_time);
float gbps = (float)((total_pkts_bytes[i] - last_total_pkts_bytes[i]) * 8) / ((now - last_time) * 1000000000);
last_total_pkts_bytes[i] = total_pkts_bytes[i];
last_total_pkts[i] = total_pkts[i];
total_pps += pps;
total_gbps += gbps;
total_pkts_all += total_pkts[i];
printf("Rx[%d] rate: [current %lu pps/%0.3f Gbps][total: %lu pkts]\n",i,pps,gbps,total_pkts[i]);
}
printf("Rx[All] rate: [current %lu pps/%0.3f Gbps][total: %lu pkts]\n",total_pps,total_gbps,total_pkts_all);
last_time = now;
}
int config_init(const char *filename)
{
int thread_number = 0;
dictionary *ini = iniparser_load(filename);
if( NULL == ini)
{
return thread_number;
}
thread_number = iniparser_getint(ini, "config:thread_number",-1);
printf("成功配置了 %d 个线程用于接收数据包.\n",thread_number);
return thread_number;
}
int port_init(int port,struct rte_mempool *mbuf_pool)
{
struct rte_eth_conf port_conf = port_conf_default;
const uint16_t rx_rings = rx_number, tx_rings = rx_number;
int retval = 0;
uint16_t q = 0;
struct rte_eth_link link;
retval = rte_eth_dev_configure(port, rx_rings, tx_rings, &port_conf);
if (retval != 0)
{
printf("ERROR: %d , %s ,%d ",retval,__FUNCTION__,__LINE__);
return retval;
}
/* 初始化队列. */
for (q = 0; q < rx_rings; q++)
{
retval = rte_eth_rx_queue_setup(port, q, RX_RING_SIZE_,
rte_eth_dev_socket_id(port), NULL, mbuf_pool);
if (retval < 0)
{
printf("ERROR: %d , %s ,%d ",retval,__FUNCTION__,__LINE__);
return retval;
}
}
for (q = 0; q < tx_rings; q++)
{
retval = rte_eth_tx_queue_setup(port, q, TX_RING_SIZE_,
rte_eth_dev_socket_id(port), NULL);
if (retval < 0)
{
printf("ERROR: %d , %s ,%d ",retval,__FUNCTION__,__LINE__);
return retval;
}
}
/* 启动端口 */
retval = rte_eth_dev_start(port);
if (retval < 0)
{
printf("ERROR: %d , %s ,%d ",retval,__FUNCTION__,__LINE__);
return retval;
}
/* 开启混杂模式 */
rte_eth_promiscuous_enable(port);
/* 查看端口状态 */
rte_eth_link_get(port, &link);
if (link.link_status)
{
printf("Port %d Link Up - speed %u Mbps - %s\n",port,
(uint32_t) link.link_speed,
(link.link_duplex == ETH_LINK_FULL_DUPLEX) ?
("full-duplex") : ("half-duplex\n"));
}
else
{
printf("Port %d Link Down\n",port);
}
return 0;
}
void recv_pkt_init(void)
{
uint8_t port;
for (port = 0; port < rte_eth_dev_count(); port++)
{
if (rte_eth_dev_socket_id(port) > 0 && rte_eth_dev_socket_id(port) != (int)rte_socket_id())
{
printf("WARNING, port %u is on remote NUMA node to "
"polling thread.\n\tPerformance will "
"not be optimal.\n", port);
}
}
printf("Core %u forwarding packets. [Ctrl+C to quit]\n",rte_lcore_id());
}
二、通过dpdk的模版makefile生成 libdpdk_demo.a
ifeq ($(RTE_SDK),)
$(error "Please define RTE_SDK environment variable")
endif
# Default target, can be overriden by command line or environment
RTE_TARGET ?= x86_64-native-linuxapp-gcc
include $(RTE_SDK)/mk/rte.vars.mk
# binary name
#APP = main
LIB = libdpdk_demo.so
#SHARED = libdpdk_demo.so
# all source are stored in SRCS-y
SRCS-y := jz_dpdk_api.c ini/dictionary.c ini/iniparser.c # main.c
CFLAGS += -O0 -g
CFLAGS += $(WERROR_FLAGS)
#include $(RTE_SDK)/mk/rte.extapp.mk
include $(RTE_SDK)/mk/rte.extlib.mk
#include $(RTE_SDK)/mk/rte.extshared.mk
上面这个makefile,完全就是dpdk给出的模版。很强大很简单,不需要做太多的配置。
二、将上面编译生成的库引用到C++项目中:
main.cpp
#include
#include "jz_dpdk_api.h"
int main(int argc,char **argv)
{
jz_dpdk_init(argc,argv);
std::cout << "hello,world." << std::endl;
return 0;
}
CC := g++
TARGET := main
CFLAGS := -O0 -g -Wall -march=native
# 这里直接粗暴的将dpdk所有的库都链接进来,以达到对所有网卡都支持
DPDK_LIBS:= dpdk_demo rte_ethdev rte_mbuf rte_mempool rte_eal rte_acl rte_kni rte_pmd_af_packet rte_pmd_kni rte_pmd_vhost \
rte_bitratestats rte_kvargs rte_pmd_ark rte_pmd_lio rte_pmd_virtio \
rte_cfgfile rte_latencystats rte_pmd_avp rte_pmd_nfp rte_pmd_vmxnet3_uio \
rte_cmdline rte_lpm rte_pmd_bnxt rte_pmd_null rte_port \
rte_cryptodev rte_pmd_bond rte_pmd_null_crypto rte_power \
rte_distributor rte_pmd_crypto_scheduler rte_pmd_octeontx_ssovf rte_reorder \
rte_mempool_ring rte_pmd_cxgbe rte_pmd_qede rte_ring \
rte_efd rte_mempool_stack rte_pmd_e1000 rte_pmd_ring rte_sched \
rte_meter rte_pmd_ena rte_pmd_sfc_efx rte_table \
rte_eventdev rte_metrics rte_pmd_enic rte_pmd_skeleton_event rte_timer \
rte_hash rte_net rte_pmd_fm10k rte_pmd_sw_event rte_vhost \
rte_ip_frag rte_pdump rte_pmd_i40e rte_pmd_tap \
rte_jobstats rte_pipeline rte_pmd_ixgbe rte_pmd_thunderx_nicvf
LIBS:= pthread
LIBPATH=/usr/local/lib
INCLUDE=/usr/local/include/dpdk
SRC := $(wildcard *.cpp)
OBJ := $(patsubst %cpp,%o,$(SRC))
all:$(TARGET)
%.o:%.cpp
$(CC) $(CFLAGS) -c $< -I$(INCLUDE) $(addprefix -L,$(LIBPATH)) $(addprefix -l,$(LIBS))
$(TARGET):$(OBJ)
$(CC) $(CFLAGS) -o $@ $^ $(addprefix -L,$(LIBPATH)) -Wl,--whole-archive $(addprefix -l,$(DPDK_LIBS)) -Wl,--no-whole-archive $(addprefix -l,$(LIBS)) -ldl -lrt
.PHONY:clean
clean:
-rm -f $(TARGET) $(OBJ)
其中最重要的2处:
1、需要-march=native ,不然会出现指令错误。
2、需要 -Wl,--whole-archive $(addprefix -l,$(DPDK_LIBS)) -Wl,--no-whole-archive,不然程序运行会找不到网卡。