DPDK技术问询:物理端口绑定多data_sender及rte_flow配置方法
DPDK 问题解答
1. 多data_sender绑定单个物理端口的可行性
可以。DPDK支持多线程/多进程共享同一物理网卡端口,核心思路是给端口配置多接收/发送队列,每个data_sender绑定独立队列并运行在不同CPU核心上,以此利用多核算力突破单核心的吞吐量瓶颈。你当前单个sender只能跑7Gb/s,大概率是单核心性能耗尽,通过多sender+多队列+多核扩展,能显著提升整体转发能力。
2. 网卡内部流量均衡到同一IP不同端口的套接字
可以实现,但需明确:网卡负责将流量分发到不同硬件队列,每个队列对应一个独立的data_sender进程/线程,由sender监听不同端口(7001/7002)并处理流量。常用实现方式有两种:
- RSS(接收端缩放):基于五元组(源IP、目的IP、源端口、目的端口、协议)哈希,将流量均匀分到多个队列,每个队列对应一个sender;
- 流分类(如rte_flow):精准匹配流量特征,把指定流量定向到对应队列。
3. rte_flow是否支持上述需求
完全支持。rte_flow提供了比RSS更灵活的流量匹配与分发能力,你可以基于目的端口(或其他字段)配置规则,把发往7001的流量定向到队列0,发往7002的流量定向到队列1,再让两个sender分别绑定队列0和队列1处理。
4. 简单配置示例
步骤1:配置网卡多队列
首先初始化DPDK并配置网卡多队列:
// 端口配置结构体 struct rte_eth_conf port_conf = RTE_ETH_CONF_DEFAULT; port_conf.rxmode.mq_mode = ETH_MQ_RX_RSS; // 开启RSS port_conf.rx_adv_conf.rss_conf.rss_key = NULL; port_conf.rx_adv_conf.rss_conf.rss_hf = ETH_RSS_IP | ETH_RSS_TCP; // 基于IP+TCP哈希 // 配置端口为2接收队列、2发送队列 int ret = rte_eth_dev_configure(port_id, 2, 2, &port_conf); if (ret < 0) { rte_exit(EXIT_FAILURE, "Failed to configure port %d\n", port_id); } // 设置队列所属CPU核心 ret = rte_eth_rx_queue_setup(port_id, 0, 1024, rte_lcore_to_socket_id(0), NULL, NULL); ret = rte_eth_rx_queue_setup(port_id, 1, 1024, rte_lcore_to_socket_id(1), NULL, NULL); ret = rte_eth_tx_queue_setup(port_id, 0, 1024, rte_lcore_to_socket_id(0), NULL); ret = rte_eth_tx_queue_setup(port_id, 1, 1024, rte_lcore_to_socket_id(1), NULL); // 启动端口 ret = rte_eth_dev_start(port_id);
步骤2:用rte_flow配置流量定向规则
以下示例将目的端口为7001的TCP流量定向到队列0,7002的定向到队列1:
struct rte_flow_attr attr = { .group = 0, .priority = 1, }; // 匹配目的端口7001的规则 struct rte_flow_item pattern_7001[] = { { // 匹配IPv4 .type = RTE_FLOW_ITEM_TYPE_IPV4, }, { // 匹配TCP .type = RTE_FLOW_ITEM_TYPE_TCP, .spec = &(struct rte_flow_item_tcp){ .dst_port = htons(7001), }, .mask = &(struct rte_flow_item_tcp){ .dst_port = 0xffff, // 完全匹配目的端口 }, }, { // 结束标记 .type = RTE_FLOW_ITEM_TYPE_END, }, }; // 动作:将流量定向到队列0 struct rte_flow_action action_7001[] = { { .type = RTE_FLOW_ACTION_TYPE_QUEUE, .conf = &(struct rte_flow_action_queue){ .index = 0, }, }, { .type = RTE_FLOW_ACTION_TYPE_END, }, }; // 创建规则 struct rte_flow *flow_7001 = rte_flow_create(port_id, &attr, pattern_7001, action_7001, NULL); // 同理创建7002的规则,定向到队列1 struct rte_flow_item pattern_7002[] = { { .type = RTE_FLOW_ITEM_TYPE_IPV4, }, { .type = RTE_FLOW_ITEM_TYPE_TCP, .spec = &(struct rte_flow_item_tcp){ .dst_port = htons(7002), }, .mask = &(struct rte_flow_item_tcp){ .dst_port = 0xffff, }, }, { .type = RTE_FLOW_ITEM_TYPE_END, }, }; struct rte_flow_action action_7002[] = { { .type = RTE_FLOW_ACTION_TYPE_QUEUE, .conf = &(struct rte_flow_action_queue){ .index = 1, }, }, { .type = RTE_FLOW_ACTION_TYPE_END, }, }; struct rte_flow *flow_7002 = rte_flow_create(port_id, &attr, pattern_7002, action_7002, NULL);
步骤3:多sender绑定队列处理
每个sender运行在不同核心,从对应队列取包并转发:
// Sender 1(核心0,绑定队列0) static int sender_lcore_0(void *arg) { int port_id = *(int *)arg; struct rte_mbuf *bufs[32]; while (1) { // 从队列0接收包 int nb_rx = rte_eth_rx_burst(port_id, 0, bufs, 32); if (nb_rx == 0) continue; // 处理并转发到远端服务器(目的端口7001) // ... 此处添加包处理逻辑 ... // 发送包 rte_eth_tx_burst(port_id, 0, bufs, nb_rx); } return 0; } // Sender 2(核心1,绑定队列1) static int sender_lcore_1(void *arg) { int port_id = *(int *)arg; struct rte_mbuf *bufs[32]; while (1) { int nb_rx = rte_eth_rx_burst(port_id, 1, bufs, 32); if (nb_rx == 0) continue; // 处理并转发到远端服务器(目的端口7002) // ... 此处添加包处理逻辑 ... rte_eth_tx_burst(port_id, 1, bufs, nb_rx); } return 0; } // 启动sender线程 rte_eal_remote_launch(sender_lcore_0, &port_id, 0); rte_eal_remote_launch(sender_lcore_1, &port_id, 1);
内容的提问来源于stack exchange,提问作者Максим
相关产品推荐
相关产品推荐

