封装dpdk接口成静(动)态库(同源同宿、多通道队列收包),然后在C++应用中使用方法

  • Post author:
  • Post category:其他



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 <stdio.h>
#include <stdint.h>

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

#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


jz_dpdk_api.c


#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 <iostream>
#include "jz_dpdk_api.h"

int main(int argc,char **argv)
{
	jz_dpdk_init(argc,argv);
	std::cout << "hello,world." << std::endl;
	
	return 0;
}

Makefile:

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,不然程序运行会找不到网卡。



版权声明:本文为oceanperfect原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。