【RDMA项目】如何使用rdma-core进行调用开发一个实战项目

news2024/9/21 0:45:23

RDMA (Remote Direct Memory Access) 是一种网络协议,可以在计算节点之间实现高效的内存数据传输,而无需CPU的干预。rdma-core 是 RDMA 的一个用户空间库,提供了一些简单易用的接口来使用 RDMA 功能。

开发了一套高级 RDMA(远程直接内存访问)连接和数据传输系统,使用 rdma-core 库(包括 rdma_cm 和 ibverbs)编写,适用于高性能计算和实时数据处理。该项目分为服务器和客户端两部分,能够在实际网络环境中实现 RDMA 连接和数据传输。通过零拷贝技术和高效的网络编程,系统显著提高了数据传输速度和减少了延迟。项目成功验证了 RDMA 在高性能和低延迟场景中的应用价值,为需要高效数据传输的应用场景提供了可靠的解决方案。

在这个项目中,rdma_cm(RDMA Connection Manager)用于管理RDMA连接的创建、绑定、监听、接受和断开。rdma_cm 提供了一套API,使得RDMA编程变得更为简便,抽象了底层复杂的连接管理过程。

在这个项目中,ibverbs(Infiniband Verbs API)用于管理低级别的Infiniband操作,如保护域、内存区域、完成队列和队列对(QP)的分配和管理。​​​​​​​

基础知识参考博文:

一文带你了解什么是RDMA

RDMA 高性能架构基本原理与设计方案

 RDMA之RoCE & Soft-RoCE_software roce

如何使用rdma-core来实现RDMA操作

教你在虚拟机上把普通网卡配置成softroce设备来运行rdma-core中的示例程序-CSDN博客

目录:

目录:

一、环境准备:

 1.1 安装依赖

1.2 安装 rdma-core

1.3 支持RDMA的机器

二、使用 RDMA(主要用于测试RDMA 环境配置是否正确)

2.1 编写 RDMA 程序

2.2 编译和运行程序

三、进一步的 RDMA-core 操作示例

3.1 服务器代码

3.1.1 初始化和准备工作

3.1.2 设置和监听

3.1.3 接受连接

3.1.4 资源分配和QP创建

3.1.5 内存注册和接收操作

3.1.6 轮询和处理完成队列

3.1.7 清理资源

3.1.8 主函数

3.2 客户端代码

3.2.1 初始化和准备工作

3.2.2 创建和解析连接

3.2.3 路由解析

3.2.4 资源分配和QP创建

3.2.5 内存注册与连接

3.2.6 数据发送

3.2.7 清理资源

3.2.8  主函数:

3.3 编译和运行

 3.5 结果

四、rdma-core 实现更复杂的 RDMA 连接和数据传输操作

4.1 RDMA 操作基础

4.2 设备发现与上下文创建

4.1.1 变量定义

4.1.2 获取设备列表 :

4.1.3 选择设备:

4.1.4 获取设备上下文

4.1.5 打印设备名称并且清理资源

4.1.6 编译运行:

4.1.7 示例输出:

4.3 内存注册与队列对创建

4.3.1 获取InfiniBand设备列表并打开设备:(此过程就是4.2介绍的过程)

4.3.2 创建保护域(Protection Domain,PD):

4.3.3 注册内存区域:

4.3.4 创建完成队列(Completion Queue,CQ): 

4.3.5  设置队列对属性并创建队列对:

4.3.6 清理资源:

4.3.7 演示效果

4.4 建立 RDMA 连接

 1. 服务器端代码(与3.1代码略有不同)

2. 客户端代码(与3.2代码相同)

4.5 编译和运行

 4.6 结果

五、验证RDMA 在高性能和低延迟场景中的应用价值

5.1 性能测试方法

5.1.2 延迟测试:

5.2.3 零拷贝效应:

5.2.4 资源使用率分析:

5.2  实验设计

5.2.1  测试环境搭建:

5.2.2 基准测试:

5.2.3 压力测试:

5.3 测试指标

5.3.1 吞吐量(Throughput):

5.3.2 延迟(Latency):

5.3.3 资源使用率(Resource Utilization):

5.4 实验结果分析

5.5 实验示例


一、环境准备:

 1.1 安装依赖

在安装 rdma-core 之前,确保你的系统已经安装了相关依赖库。对于基于 Debian 的系统,可以使用以下命令:

sudo apt-get update
sudo apt-get install build-essential cmake libnuma-dev libibverbs-dev

对于基于 Red Hat 的系统,可以使用以下命令:

sudo yum groupinstall "Development Tools"
sudo yum install cmake libibverbs-devel numactl-devel

1.2 安装 rdma-core

你可以从源码编译并安装 rdma-core。首先克隆 rdma-core 的 GitHub 仓库:

git clone https://github.com/linux-rdma/rdma-core.git
cd rdma-core

然后使用 CMake 进行编译和安装:

mkdir build
cd build
cmake ..
make
sudo make install

1.3 支持RDMA的机器

 准备俩台支持RDMA的设备机器进行测试调试

二、使用 RDMA(主要用于测试RDMA 环境配置是否正确

2.1 编写 RDMA 程序

rdma-core 提供了多个库,例如 libibverbslibrdmacm,用于不同的 RDMA 操作。以下是一个简单的例子,展示了如何使用 libibverbs 库进行基本的 RDMA 操作。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <infiniband/verbs.h>

int main() {
    struct ibv_device **dev_list;  // 用于存储设备列表的指针数组
    struct ibv_device *ib_dev;     // 用于存储选择的设备指针
    struct ibv_context *ctx;       // 用于存储设备上下文的指针

    // 获取设备列表
    dev_list = ibv_get_device_list(NULL);
    if (!dev_list) {  // 检查设备列表是否获取成功
        perror("Failed to get devices list");
        return EXIT_FAILURE;
    }

    // 选择第一个设备
    ib_dev = dev_list[0];
    if (!ib_dev) {  // 检查是否找到至少一个设备
        fprintf(stderr, "No IB devices found\n");
        ibv_free_device_list(dev_list);  // 释放设备列表
        return EXIT_FAILURE;
    }

    // 获取设备的上下文
    ctx = ibv_open_device(ib_dev);
    if (!ctx) {  // 检查是否成功打开设备上下文
        perror("Failed to open device");
        ibv_free_device_list(dev_list);  // 释放设备列表
        return EXIT_FAILURE;
    }

    printf("Device %s opened\n", ib_dev->name);  // 输出设备名称表示成功打开

    // 清理
    ibv_close_device(ctx);  // 关闭设备上下文
    ibv_free_device_list(dev_list);  // 释放设备列表

    return EXIT_SUCCESS;  // 程序成功执行完毕
}

2.2 编译和运行程序

将上述代码保存到一个文件中,例如 rdma_example.c,然后使用以下命令进行编译和运行:

gcc -o rdma_example rdma_example.c -libverbs
./rdma_example

恭喜你成功打开了 RDMA 设备 rxe0这说明你的 RDMA 环境已经配置正确,能够正常工作。接下来,可以尝试更多复杂的 RDMA 操作,如建立连接、数据传输等。以下是一个更复杂的示例,展示如何进行基本的 RDMA 数据传输。

三、进一步的 RDMA-core 操作示例

我们将展示一个简单的 RDMA 客户端-服务器程序,其中客户端向服务器发送数据,服务器接收数据。这只是一个简单的示例,实际应用中可能需要更多的错误处理和资源管理。

3.1 服务器代码

保存以下代码为 rdma_server.c

实现了一种使用RDMA(远程直接数据存取)技术的简单服务器程序。RDMA允许在计算机之间进行高速数据传输,而无需操作系统干预。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <rdma/rdma_cma.h>
#include <infiniband/verbs.h>

#define BUFFER_SIZE 1024

void run_server() {
    struct rdma_event_channel *ec = rdma_create_event_channel(); // 创建RDMA事件通道
    struct rdma_cm_id *listener = NULL, *conn = NULL; // 定义监听和连接ID
    struct rdma_addrinfo hints, *res; // 定义地址信息结构体
    struct ibv_pd *pd = NULL; // 定义保护域
    struct ibv_mr *mr = NULL; // 定义内存注册区域
    struct ibv_comp_channel *comp_chan = NULL; // 定义完成通道
    struct ibv_cq *cq = NULL; // 定义完成队列
    struct ibv_qp_init_attr qp_attr; // 定义QP初始化属性
    char buf[BUFFER_SIZE]; // 定义缓冲区
    int ret; // 定义返回值变量

    memset(&hints, 0, sizeof(hints)); // 将hints结构体清零
    hints.ai_flags = RAI_PASSIVE; // 设置hints为被动模式
    hints.ai_port_space = RDMA_PS_TCP; // 设置地址空间为TCP

    ret = rdma_getaddrinfo(NULL, "20079", &hints, &res); // 获取地址信息
    if (ret) {
        perror("rdma_getaddrinfo"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    ret = rdma_create_id(ec, &listener, NULL, RDMA_PS_TCP); // 创建RDMA标识符
    if (ret) {
        perror("rdma_create_id"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    ret = rdma_bind_addr(listener, res->ai_src_addr); // 绑定地址
    if (ret) {
        perror("rdma_bind_addr"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    ret = rdma_listen(listener, 0); // 开始监听
    if (ret) {
        perror("rdma_listen"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    printf("Server is listening on port 20079...\n"); // 打印服务器正在监听的消息

    struct rdma_cm_event *event; // 定义RDMA事件
    ret = rdma_get_cm_event(ec, &event); // 获取一个连接管理事件
    if (ret) {
        perror("rdma_get_cm_event"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    if (event->event == RDMA_CM_EVENT_CONNECT_REQUEST) { // 检查事件类型是否为连接请求
        conn = event->id; // 获取连接ID
        rdma_ack_cm_event(event); // 确认事件
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event); // 如果事件类型不是预期的,输出错误信息并退出
        rdma_ack_cm_event(event); // 确认事件
        exit(EXIT_FAILURE);
    }

    pd = ibv_alloc_pd(conn->verbs); // 分配保护域
    if (!pd) {
        perror("ibv_alloc_pd"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    comp_chan = ibv_create_comp_channel(conn->verbs); // 创建完成通道
    if (!comp_chan) {
        perror("ibv_create_comp_channel"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    cq = ibv_create_cq(conn->verbs, 10, NULL, comp_chan, 0); // 创建完成队列
    if (!cq) {
        perror("ibv_create_cq"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    memset(&qp_attr, 0, sizeof(qp_attr)); // 清零QP初始化属性结构体
    qp_attr.cap.max_send_wr = 1; // 设置最大发送工作请求数
    qp_attr.cap.max_recv_wr = 1; // 设置最大接收工作请求数
    qp_attr.cap.max_send_sge = 1; // 设置最大发送SGE数
    qp_attr.cap.max_recv_sge = 1; // 设置最大接收SGE数
    qp_attr.send_cq = cq; // 关联发送完成队列
    qp_attr.recv_cq = cq; // 关联接收完成队列
    qp_attr.qp_type = IBV_QPT_RC; // 设置QP类型为RC

    ret = rdma_create_qp(conn, pd, &qp_attr); // 创建QP
    if (ret) {
        perror("rdma_create_qp"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE); // 注册内存区域
    if (!mr) {
        perror("ibv_reg_mr"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    struct ibv_recv_wr wr, *bad_wr = NULL; // 定义接收工作请求和错误工作请求指针
    struct ibv_sge sge; // 定义SGE

    sge.addr = (uintptr_t)buf; // 设置SGE地址为缓冲区地址
    sge.length = BUFFER_SIZE; // 设置SGE长度为缓冲区大小
    sge.lkey = mr->lkey; // 设置SGE的本地密钥为内存区域的密钥

    wr.wr_id = 0; // 设置工作请求ID为0
    wr.next = NULL; // 设置下一个工作请求为空
    wr.sg_list = &sge; // 设置工作请求的SGE列表
    wr.num_sge = 1; // 设置SGE数量为1

    ret = ibv_post_recv(conn->qp, &wr, &bad_wr); // 发起接收工作请求
    if (ret) {
        perror("ibv_post_recv"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    struct rdma_conn_param cm_params; // 定义连接参数
    memset(&cm_params, 0, sizeof(cm_params)); // 清零连接参数结构体
    ret = rdma_accept(conn, &cm_params); // 接受连接请求
    if (ret) {
        perror("rdma_accept"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    struct ibv_wc wc; // 定义工作完成结构体
    while ((ret = ibv_poll_cq(cq, 1, &wc)) == 0); // 轮询完成队列直到有工作完成
    if (ret < 0) {
        perror("ibv_poll_cq"); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    if (wc.status != IBV_WC_SUCCESS) { // 检查工作完成状态是否成功
        fprintf(stderr, "Failed status %s (%d) for wr_id %d\n",
                ibv_wc_status_str(wc.status), wc.status, (int) wc.wr_id); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

    printf("Received message: %s\n", buf); // 打印接收到的消息

    rdma_disconnect(conn); // 断开连接
    rdma_destroy_qp(conn); // 销毁QP
    ibv_dereg_mr(mr); // 解注册内存区域
    ibv_destroy_cq(cq); // 销毁完成队列
    ibv_destroy_comp_channel(comp_chan); // 销毁完成通道
    ibv_dealloc_pd(pd); // 释放保护域
    rdma_destroy_id(conn); // 销毁连接ID
    rdma_destroy_id(listener); // 销毁监听ID
    rdma_destroy_event_channel(ec); // 销毁事件通道
    rdma_freeaddrinfo(res); // 释放地址信息
}

int main() {
    run_server(); // 运行服务器
    return 0; // 返回0表示正常退出
}

3.1.1 初始化和准备工作

1. 创建并初始化RDMA事件通道

struct rdma_event_channel *ec = rdma_create_event_channel(); // 创建RDMA事件通道

2. 定义变量

    struct rdma_cm_id *listener = NULL, *conn = NULL; // 定义监听和连接ID
    struct rdma_addrinfo hints, *res; // 定义地址信息结构体
    struct ibv_pd *pd = NULL; // 定义保护域
    struct ibv_mr *mr = NULL; // 定义内存注册区域
    struct ibv_comp_channel *comp_chan = NULL; // 定义完成通道
    struct ibv_cq *cq = NULL; // 定义完成队列
    struct ibv_qp_init_attr qp_attr; // 定义QP初始化属性
    char buf[BUFFER_SIZE]; // 定义缓冲区
    int ret; // 定义返回值变量
  • listenerconn用于监听和连接的RDMA标识符。
  • hintsres用于存储地址信息。
  • pdmrcomp_chancq分别代表保护域、内存注册区、完成通道和完成队列。
  • qp_attr用于配置QP的初始化属性。
  • buf用于存储接收到的数据。
  • ret用于存储各个操作的返回值。

3. 配置地址信息

    memset(&hints, 0, sizeof(hints)); // 将hints结构体清零
    hints.ai_flags = RAI_PASSIVE; // 设置hints为被动模式
    hints.ai_port_space = RDMA_PS_TCP; // 设置地址空间为TCP

4. 获取RDMA地址

    ret = rdma_getaddrinfo(NULL, "20079", &hints, &res); // 获取地址信息

3.1.2 设置和监听

1. 创建RDMA标识符:

    ret = rdma_create_id(ec, &listener, NULL, RDMA_PS_TCP); // 创建RDMA标识符

2. 绑定地址并开始监听:

    ret = rdma_bind_addr(listener, res->ai_src_addr);//绑定地址
    ret = rdma_listen(listener, 0);//开始监听

3.1.3 接受连接

1. 等待并接受连接请求:

    struct rdma_cm_event *event;//定义RDMA事件
    ret = rdma_get_cm_event(ec, &event);//获取一个连接管理事件

2.. 处理连接请求

    if (event->event == RDMA_CM_EVENT_CONNECT_REQUEST) { // 检查事件类型是否为连接请求
        conn = event->id; // 获取连接ID
        rdma_ack_cm_event(event); // 确认事件
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event); // 如果事件类型不是预期的,输出错误信息并退出
        rdma_ack_cm_event(event); // 确认事件
        exit(EXIT_FAILURE);
    }

3.1.4 资源分配和QP创建

1. 分配保护域、创建完成通道和完成队列

    pd = ibv_alloc_pd(conn->verbs); // 分配保护域
    comp_chan = ibv_create_comp_channel(conn->verbs); // 创建完成通道
    cq = ibv_create_cq(conn->verbs, 10, NULL, comp_chan, 0); // 创建完成队列

2. 配置并创建QP

    memset(&qp_attr, 0, sizeof(qp_attr)); // 清零QP初始化属性结构体
    qp_attr.cap.max_send_wr = 1; // 设置最大发送工作请求数
    qp_attr.cap.max_recv_wr = 1; // 设置最大接收工作请求数
    qp_attr.cap.max_send_sge = 1; // 设置最大发送SGE数
    qp_attr.cap.max_recv_sge = 1; // 设置最大接收SGE数
    qp_attr.send_cq = cq; // 关联发送完成队列
    qp_attr.recv_cq = cq; // 关联接收完成队列
    qp_attr.qp_type = IBV_QPT_RC; // 设置QP类型为RC

    ret = rdma_create_qp(conn, pd, &qp_attr); // 创建QP

3.1.5 内存注册和接收操作

1. 注册内存区域

    mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);//注册内存区域

2. 准备接收工作请求

    struct ibv_recv_wr wr, *bad_wr = NULL; // 定义接收工作请求和错误工作请求指针
    struct ibv_sge sge; // 定义SGE

    sge.addr = (uintptr_t)buf; // 设置SGE地址为缓冲区地址
    sge.length = BUFFER_SIZE; // 设置SGE长度为缓冲区大小
    sge.lkey = mr->lkey; // 设置SGE的本地密钥为内存区域的密钥

    wr.wr_id = 0; // 设置工作请求ID为0
    wr.next = NULL; // 设置下一个工作请求为空
    wr.sg_list = &sge; // 设置工作请求的SGE列表
    wr.num_sge = 1; // 设置SGE数量为1

    ret = ibv_post_recv(conn->qp, &wr, &bad_wr); // 发起接收工作请求

3. 接受连接

    struct rdma_conn_param cm_params; // 定义连接参数
    memset(&cm_params, 0, sizeof(cm_params)); // 清零连接参数结构体
    ret = rdma_accept(conn, &cm_params); // 接受连接请求

3.1.6 轮询和处理完成队列

1. 轮询完成队列,等待数据接收

    struct ibv_wc wc; // 定义工作完成结构体
    while ((ret = ibv_poll_cq(cq, 1, &wc)) == 0); // 轮询完成队列直到有工作完成

2. 检查完成状态并处理数据

    if (wc.status != IBV_WC_SUCCESS) { // 检查工作完成状态是否成功
        fprintf(stderr, "Failed status %s (%d) for wr_id %d\n",
                ibv_wc_status_str(wc.status), wc.status, (int) wc.wr_id); // 如果失败,输出错误信息并退出
        exit(EXIT_FAILURE);
    }

3.1.7 清理资源

断开连接并释放资源

    rdma_disconnect(conn); // 断开连接
    rdma_destroy_qp(conn); // 销毁QP
    ibv_dereg_mr(mr); // 解注册内存区域
    ibv_destroy_cq(cq); // 销毁完成队列
    ibv_destroy_comp_channel(comp_chan); // 销毁完成通道
    ibv_dealloc_pd(pd); // 释放保护域
    rdma_destroy_id(conn); // 销毁连接ID
    rdma_destroy_id(listener); // 销毁监听ID
    rdma_destroy_event_channel(ec); // 销毁事件通道
    rdma_freeaddrinfo(res); // 释放地址信息

3.1.8 主函数

运行服务器:

int main() {
    run_server();
    return 0;
}

3.2 客户端代码

保存以下代码为 rdma_client.c

实现了一个简单的RDMA(Remote Direct Memory Access)客户端,连接到指定的服务器并发送一条消息。RDMA允许计算机通过高效的网络通信方式直接访问彼此的内存,而无需操作系统干预,从而减少延迟并提高数据传输速度。代码使用了RDMA的通信管理API和Infiniband verbs API来实现这个功能。

#include <stdio.h>  // 包含标准输入输出库
#include <stdlib.h>  // 包含标准库
#include <string.h>  // 包含字符串处理库
#include <rdma/rdma_cma.h>  // 包含RDMA连接管理API
#include <infiniband/verbs.h>  // 包含Infiniband verbs API

#define BUFFER_SIZE 1024  // 定义缓冲区大小

// 客户端主函数,接收服务器地址作为参数
void run_client(const char *server) {
    // 声明和初始化变量
    struct rdma_event_channel *ec = rdma_create_event_channel();  // 创建RDMA事件通道
    struct rdma_cm_id *conn = NULL;  // RDMA连接标识符
    struct rdma_addrinfo hints, *res;  // RDMA地址信息结构和指针
    struct ibv_pd *pd = NULL;  // 保护域
    struct ibv_mr *mr = NULL;  // 内存注册区域
    struct ibv_comp_channel *comp_chan = NULL;  // 事件通道
    struct ibv_cq *cq = NULL;  // 完成队列
    struct ibv_qp_init_attr qp_attr;  // 队列对初始化属性
    char buf[BUFFER_SIZE];  // 数据缓冲区
    int ret;  // 返回值变量

    // 初始化hints结构体
    memset(&hints, 0, sizeof(hints));  // 清空结构体
    hints.ai_port_space = RDMA_PS_TCP;  // 设置端口空间为TCP

    // 获取地址信息
    ret = rdma_getaddrinfo(server, "20079", &hints, &res);
    if (ret) {
        perror("rdma_getaddrinfo");  // 打印错误信息
        exit(EXIT_FAILURE);  // 退出程序
    }

    // 创建RDMA标识符
    ret = rdma_create_id(ec, &conn, NULL, RDMA_PS_TCP);
    if (ret) {
        perror("rdma_create_id");
        exit(EXIT_FAILURE);
    }

    // 解析地址
    ret = rdma_resolve_addr(conn, NULL, res->ai_dst_addr, 2000);
    if (ret) {
        perror("rdma_resolve_addr");
        exit(EXIT_FAILURE);
    }

    // 等待地址解析事件
    struct rdma_cm_event *event;
    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        exit(EXIT_FAILURE);
    }

    // 检查事件类型
    if (event->event == RDMA_CM_EVENT_ADDR_RESOLVED) {
        rdma_ack_cm_event(event);  // 确认事件
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);
        exit(EXIT_FAILURE);
    }

    // 解析路由
    ret = rdma_resolve_route(conn, 2000);
    if (ret) {
        perror("rdma_resolve_route");
        exit(EXIT_FAILURE);
    }

    // 等待路由解析事件
    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        exit(EXIT_FAILURE);
    }

    // 检查事件类型
    if (event->event == RDMA_CM_EVENT_ROUTE_RESOLVED) {
        rdma_ack_cm_event(event);  // 确认事件
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);
        exit(EXIT_FAILURE);
    }

    // 分配保护域
    pd = ibv_alloc_pd(conn->verbs);
    if (!pd) {
        perror("ibv_alloc_pd");
        exit(EXIT_FAILURE);
    }

    // 创建完成通道
    comp_chan = ibv_create_comp_channel(conn->verbs);
    if (!comp_chan) {
        perror("ibv_create_comp_channel");
        exit(EXIT_FAILURE);
    }

    // 创建完成队列
    cq = ibv_create_cq(conn->verbs, 10, NULL, comp_chan, 0);
    if (!cq) {
        perror("ibv_create_cq");
        exit(EXIT_FAILURE);
    }

    // 初始化队列对属性
    memset(&qp_attr, 0, sizeof(qp_attr));
    qp_attr.cap.max_send_wr = 1;  // 最大发送请求数
    qp_attr.cap.max_recv_wr = 1;  // 最大接收请求数
    qp_attr.cap.max_send_sge = 1;  // 最大发送SGE数
    qp_attr.cap.max_recv_sge = 1;  // 最大接收SGE数
    qp_attr.send_cq = cq;  // 发送完成队列
    qp_attr.recv_cq = cq;  // 接收完成队列
    qp_attr.qp_type = IBV_QPT_RC;  // 队列对类型

    // 创建队列对
    ret = rdma_create_qp(conn, pd, &qp_attr);
    if (ret) {
        perror("rdma_create_qp");
        exit(EXIT_FAILURE);
    }

    // 注册内存区域
    mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);
    if (!mr) {
        perror("ibv_reg_mr");
        exit(EXIT_FAILURE);
    }

    // 设置连接参数
    struct rdma_conn_param cm_params;
    memset(&cm_params, 0, sizeof(cm_params));
    
    // 发起连接请求
    ret = rdma_connect(conn, &cm_params);
    if (ret) {
        perror("rdma_connect");
        exit(EXIT_FAILURE);
    }

    // 等待连接建立事件
    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        exit(EXIT_FAILURE);
    }

    // 检查事件类型
    if (event->event == RDMA_CM_EVENT_ESTABLISHED) {
        rdma_ack_cm_event(event);  // 确认事件
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);
        exit(EXIT_FAILURE);
    }

    // 准备发送数据
    strcpy(buf, "Hello, RDMA!");
    struct ibv_send_wr wr, *bad_wr = NULL;  // 发送请求和错误请求指针
    struct ibv_sge sge;  // 单次数据描述符
    sge.addr = (uintptr_t)buf;  // 数据缓冲区地址
    sge.length = BUFFER_SIZE;  // 数据长度
    sge.lkey = mr->lkey;  // 本地密钥

    // 初始化发送请求
    wr.wr_id = 0;
    wr.next = NULL;
    wr.sg_list = &sge;  // 数据描述符列表
    wr.num_sge = 1;  // 数据描述符数量
    wr.opcode = IBV_WR_SEND;  // 操作码为发送
    wr.send_flags = IBV_SEND_SIGNALED;  // 发送标志

    // 发送数据
    ret = ibv_post_send(conn->qp, &wr, &bad_wr);
    if (ret) {
        perror("ibv_post_send");
        exit(EXIT_FAILURE);
    }

    // 等待完成队列事件
    struct ibv_wc wc;
    while ((ret = ibv_poll_cq(cq, 1, &wc)) == 0);
    if (ret < 0) {
        perror("ibv_poll_cq");
        exit(EXIT_FAILURE);
    }

    // 检查完成状态
    if (wc.status != IBV_WC_SUCCESS) {
        fprintf(stderr, "Failed status %s (%d) for wr_id %d\n",
                ibv_wc_status_str(wc.status), wc.status, (int) wc.wr_id);
        exit(EXIT_FAILURE);
    }

    // 打印发送消息
    printf("Message sent: %s\n", buf);

    // 断开连接并释放资源
    rdma_disconnect(conn);  // 断开连接
    rdma_destroy_qp(conn);  // 销毁队列对
    ibv_dereg_mr(mr);  // 解除内存注册
    ibv_destroy_cq(cq);  // 销毁完成队列
    ibv_destroy_comp_channel(comp_chan);  // 销毁完成通道
    ibv_dealloc_pd(pd);  // 释放保护域
    rdma_destroy_id(conn);  // 销毁连接标识符
    rdma_destroy_event_channel(ec);  // 销毁事件通道
    rdma_freeaddrinfo(res);  // 释放地址信息
}

// 主函数
int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <server-address>\n", argv[0]);  // 打印用法信息
        return EXIT_FAILURE;  // 返回失败状态
    }

    run_client(argv[1]);  // 运行客户端
    return 0;  // 返回成功状态
}

3.2.1 初始化和准备工作

1. 创建并初始化RDMA事件通道

struct rdma_event_channel *ec = rdma_create_event_channel();// 创建RDMA事件通道

 2. 定义变量

    struct rdma_cm_id *conn = NULL;  // RDMA连接标识符
    struct rdma_addrinfo hints, *res;  // RDMA地址信息结构和指针
    struct ibv_pd *pd = NULL;  // 保护域
    struct ibv_mr *mr = NULL;  // 内存注册区域
    struct ibv_comp_channel *comp_chan = NULL;  // 事件通道
    struct ibv_cq *cq = NULL;  // 完成队列
    struct ibv_qp_init_attr qp_attr;  // 队列对初始化属性
    char buf[BUFFER_SIZE];  // 数据缓冲区
    int ret;  // 返回值变量
  • conn:RDMA连接标识符。
  • hintsres:用于存储地址信息。
  • pdmrcomp_chancq:分别代表保护域、内存注册区、完成通道和完成队列。
  • qp_attr:用于配置QP的初始化属性。
  • buf:用于存储要发送的数据。
  • ret:用于存储各个操作的返回值。

3. 配置地址信息

    // 初始化hints结构体
    memset(&hints, 0, sizeof(hints));  // 清空结构体
    hints.ai_port_space = RDMA_PS_TCP;  // 设置端口空间为TCP

4. 获取RDMA地址

    // 获取地址信息
    ret = rdma_getaddrinfo(server, "20079", &hints, &res);
    if (ret) {
        perror("rdma_getaddrinfo");  // 打印错误信息
        exit(EXIT_FAILURE);  // 退出程序
    }

3.2.2 创建和解析连接

1. 创建RDMA标识符

    // 创建RDMA标识符
    ret = rdma_create_id(ec, &conn, NULL, RDMA_PS_TCP);
    if (ret) {
        perror("rdma_create_id");
        exit(EXIT_FAILURE);
    }

2. 解析服务器地址

    // 解析地址
    ret = rdma_resolve_addr(conn, NULL, res->ai_dst_addr, 2000);
    if (ret) {
        perror("rdma_resolve_addr");
        exit(EXIT_FAILURE);
    }

    // 等待地址解析事件
    struct rdma_cm_event *event;
    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        exit(EXIT_FAILURE);
    }

    // 检查事件类型
    if (event->event == RDMA_CM_EVENT_ADDR_RESOLVED) {
        rdma_ack_cm_event(event);  // 确认事件
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);
        exit(EXIT_FAILURE);
    }

3.2.3 路由解析

    // 解析路由
    ret = rdma_resolve_route(conn, 2000);
    if (ret) {
        perror("rdma_resolve_route");
        exit(EXIT_FAILURE);
    }

    // 等待路由解析事件
    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        exit(EXIT_FAILURE);
    }

    // 检查事件类型
    if (event->event == RDMA_CM_EVENT_ROUTE_RESOLVED) {
        rdma_ack_cm_event(event);  // 确认事件
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);
        exit(EXIT_FAILURE);
    }

3.2.4 资源分配和QP创建

1. 分配保护域、创建完成通道和完成队列

    // 分配保护域
    pd = ibv_alloc_pd(conn->verbs);
    if (!pd) {
        perror("ibv_alloc_pd");
        exit(EXIT_FAILURE);
    }

    // 创建完成通道
    comp_chan = ibv_create_comp_channel(conn->verbs);
    if (!comp_chan) {
        perror("ibv_create_comp_channel");
        exit(EXIT_FAILURE);
    }

    // 创建完成队列
    cq = ibv_create_cq(conn->verbs, 10, NULL, comp_chan, 0);
    if (!cq) {
        perror("ibv_create_cq");
        exit(EXIT_FAILURE);
    }

2. 初始化并创建QP

    // 初始化队列对属性
    memset(&qp_attr, 0, sizeof(qp_attr));
    qp_attr.cap.max_send_wr = 1;  // 最大发送请求数
    qp_attr.cap.max_recv_wr = 1;  // 最大接收请求数
    qp_attr.cap.max_send_sge = 1;  // 最大发送SGE数
    qp_attr.cap.max_recv_sge = 1;  // 最大接收SGE数
    qp_attr.send_cq = cq;  // 发送完成队列
    qp_attr.recv_cq = cq;  // 接收完成队列
    qp_attr.qp_type = IBV_QPT_RC;  // 队列对类型

    // 创建队列对
    ret = rdma_create_qp(conn, pd, &qp_attr);
    if (ret) {
        perror("rdma_create_qp");
        exit(EXIT_FAILURE);
    }

3.2.5 内存注册与连接

1. 注册内存区域

    // 注册内存区域
    mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);
    if (!mr) {
        perror("ibv_reg_mr");
        exit(EXIT_FAILURE);
    }

2. 发起连接请求

    // 设置连接参数
    struct rdma_conn_param cm_params;
    memset(&cm_params, 0, sizeof(cm_params));
    
    // 发起连接请求
    ret = rdma_connect(conn, &cm_params);
    if (ret) {
        perror("rdma_connect");
        exit(EXIT_FAILURE);
    }

    // 等待连接建立事件
    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        exit(EXIT_FAILURE);
    }

    // 检查事件类型
    if (event->event == RDMA_CM_EVENT_ESTABLISHED) {
        rdma_ack_cm_event(event);  // 确认事件
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);
        exit(EXIT_FAILURE);
    }

3.2.6 数据发送

准备并发送数据

    // 准备发送数据
    strcpy(buf, "Hello, RDMA!");
    struct ibv_send_wr wr, *bad_wr = NULL;  // 发送请求和错误请求指针
    struct ibv_sge sge;  // 单次数据描述符
    sge.addr = (uintptr_t)buf;  // 数据缓冲区地址
    sge.length = BUFFER_SIZE;  // 数据长度
    sge.lkey = mr->lkey;  // 本地密钥

    // 初始化发送请求
    wr.wr_id = 0;
    wr.next = NULL;
    wr.sg_list = &sge;  // 数据描述符列表
    wr.num_sge = 1;  // 数据描述符数量
    wr.opcode = IBV_WR_SEND;  // 操作码为发送
    wr.send_flags = IBV_SEND_SIGNALED;  // 发送标志

    // 发送数据
    ret = ibv_post_send(conn->qp, &wr, &bad_wr);
    if (ret) {
        perror("ibv_post_send");
        exit(EXIT_FAILURE);
    }

    // 等待完成队列事件
    struct ibv_wc wc;
    while ((ret = ibv_poll_cq(cq, 1, &wc)) == 0);
    if (ret < 0) {
        perror("ibv_poll_cq");
        exit(EXIT_FAILURE);
    }

    // 检查完成状态
    if (wc.status != IBV_WC_SUCCESS) {
        fprintf(stderr, "Failed status %s (%d) for wr_id %d\n",
                ibv_wc_status_str(wc.status), wc.status, (int) wc.wr_id);
        exit(EXIT_FAILURE);
    }

    // 打印发送消息
    printf("Message sent: %s\n", buf);

3.2.7 清理资源

断开连接并释放资源

    // 断开连接并释放资源
    rdma_disconnect(conn);  // 断开连接
    rdma_destroy_qp(conn);  // 销毁队列对
    ibv_dereg_mr(mr);  // 解除内存注册
    ibv_destroy_cq(cq);  // 销毁完成队列
    ibv_destroy_comp_channel(comp_chan);  // 销毁完成通道
    ibv_dealloc_pd(pd);  // 释放保护域
    rdma_destroy_id(conn);  // 销毁连接标识符
    rdma_destroy_event_channel(ec);  // 销毁事件通道
    rdma_freeaddrinfo(res);  // 释放地址信息

3.2.8  主函数:

运行客户端

// 主函数
int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <server-address>\n", argv[0]);  // 打印用法信息
        return EXIT_FAILURE;  // 返回失败状态
    }

    run_client(argv[1]);  // 运行客户端
    return 0;  // 返回成功状态
}

3.3 编译和运行

确保先编译服务器和客户端代码:

gcc -o rdma_server rdma_server.c -lrdmacm -libverbs
gcc -o rdma_client rdma_client.c -lrdmacm -libverbs

注意:

如果遇到此错误,因为链接器找不到 ibv_reg_mr_iova2 函数的定义。这个函数可能是你所使用的库版本中不存在的,或者它属于另一个库。

解决方案:

1.确保你安装的库版本是正确的,且包含 ibv_reg_mr_iova2 函数。检查所使用的库版本以及文档,确认该函数确实存在。

2.确保链接器能够找到正确的库。确保你已经正确安装并链接了 rdma-core 包。如果库路径不在默认的搜索路径中,可能需要手动指定库路径。例如:

gcc -o rdma_client rdma_client.c -L/usr/local/lib -lrdmacm -libverbs

 3.5 结果

服务器将打印收到的消息:

客户端将打印发送的消息:

可以了解到如何使用 rdma-core 进行基本的 RDMA 连接和数据传输。

四、rdma-core 实现更复杂的 RDMA 连接和数据传输操作

为了进一步利用 rdma-core 实现更复杂的 RDMA 连接和数据传输操作,你需要掌握一些基本概念和操作步骤。

4.1 RDMA 操作基础

RDMA 编程涉及多个步骤,包括设备发现、上下文创建、连接建立、数据传输等。

4.2 设备发现与上下文创建

你需要先获取 RDMA 设备列表,并选择一个设备进行上下文创建。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <infiniband/verbs.h>

// 主函数
int main() {
    struct ibv_device **dev_list;     // 存储设备列表的指针数组
    struct ibv_device *ib_dev;        // 存储选定设备的指针
    struct ibv_context *ctx;          // 存储设备上下文的指针

    // 获取设备列表
    dev_list = ibv_get_device_list(NULL);
    if (!dev_list) {
        // 如果获取设备列表失败,打印错误信息并返回失败状态
        perror("Failed to get devices list");
        return EXIT_FAILURE;
    }

    // 选择第一个设备
    ib_dev = dev_list[0];
    if (!ib_dev) {
        // 如果没有找到任何IB设备,打印错误信息并返回失败状态
        fprintf(stderr, "No IB devices found\n");
        return EXIT_FAILURE;
    }

    // 获取设备的上下文
    ctx = ibv_open_device(ib_dev);
    if (!ctx) {
        // 如果打开设备失败,打印错误信息并返回失败状态
        perror("Failed to open device");
        return EXIT_FAILURE;
    }

    // 打印出成功打开的设备名称
    printf("Device %s opened\n", ib_dev->name);

    // 清理分配的资源
    ibv_close_device(ctx);            // 关闭设备上下文
    ibv_free_device_list(dev_list);   // 释放设备列表

    return EXIT_SUCCESS;              // 返回成功状态
}

 展示了如何使用Infiniband Verbs API获取并打开Infiniband设备,适用于需要与Infiniband设备进行低级别交互的应用程序

4.1.1 变量定义

struct ibv_device **dev_list;     // 存储设备列表的指针数组
struct ibv_device *ib_dev;        // 存储选定设备的指针
struct ibv_context *ctx;          // 存储设备上下文的指针

声明了三个指针变量:

  • dev_list:用于存储设备列表。
  • ib_dev:用于存储选定的设备。
  • ctx:用于存储设备的上下文信息。

4.1.2 获取设备列表 :

dev_list = ibv_get_device_list(NULL);
if (!dev_list) {
     perror("Failed to get devices list");
     return EXIT_FAILURE;
}

调用ibv_get_device_list函数获取设备列表,如果获取失败,打印错误信息并返回失败状态。 

4.1.3 选择设备:

ib_dev = dev_list[0];
if (!ib_dev) {
    fprintf(stderr, "No IB devices found\n");
    return EXIT_FAILURE;
}

选择设备列表中的第一个设备如果设备列表为空,打印错误信息并返回失败状态。 

4.1.4 获取设备上下文

ctx = ibv_open_device(ib_dev);
if (!ctx) {
    perror("Failed to open device");
    return EXIT_FAILURE;
}

调用ibv_open_device函数获取选定设备的上下文。如果获取失败,打印错误信息并返回失败状态。 

4.1.5 打印设备名称并且清理资源

printf("Device %s opened\n", ib_dev->name);
ibv_close_device(ctx);            // 关闭设备上下文
ibv_free_device_list(dev_list);   // 释放设备列表

打印出成功打开的设备名称,关闭设备上下文并释放设备列表,清理资源。

4.1.6 编译运行:

将提供的代码保存到一个文件中,例如ibv_example.c

使用GCC编译器来编译代码,并链接Infiniband库:

gcc -o ibv_example ibv_example.c -libverbs

运行编译生成的可执行文件:

./ibv_example

4.1.7 示例输出:

假设系统中有可用的Infiniband设备,并且代码运行成功,输出可能如下:

Device rxe0 opened

如果系统中没有Infiniband设备,输出可能如下:

No IB devices found

 如果在获取设备列表或打开设备时发生错误,输出可能如下:

Failed to get devices list: <error_description>
Failed to open device: <error_description>

此时我们输出则显示有可用的Infiniband设备:

4.3 内存注册与队列对创建

为了传输数据,你需要注册内存区域并创建发送和接收队列。

#include <stdio.h>                       // 包含标准输入输出头文件
#include <stdlib.h>                      // 包含标准库头文件
#include <string.h>                      // 包含字符串操作头文件
#include <infiniband/verbs.h>            // 包含InfiniBand verbs库头文件

#define BUFFER_SIZE 1024                 // 定义缓冲区大小为1024字节

int main() {
    struct ibv_device **dev_list;        // 定义设备列表指针,指向InfiniBand设备数组
    struct ibv_device *ib_dev;           // 定义设备指针,指向单个InfiniBand设备
    struct ibv_context *ctx;             // 定义上下文指针,表示设备上下文
    struct ibv_pd *pd;                   // 定义保护域指针
    struct ibv_mr *mr;                   // 定义内存注册区域指针
    struct ibv_cq *cq;                   // 定义完成队列指针
    struct ibv_qp *qp;                   // 定义队列对指针
    struct ibv_qp_init_attr qp_attr;     // 定义队列对初始化属性结构
    char buf[BUFFER_SIZE];               // 定义缓冲区

    // 获取设备列表并打开设备
    dev_list = ibv_get_device_list(NULL);                  // 获取所有InfiniBand设备列表
    ib_dev = dev_list[0];                                  // 选择第一个设备
    ctx = ibv_open_device(ib_dev);                         // 打开设备并获取设备上下文

    // 创建保护域
    pd = ibv_alloc_pd(ctx);                                // 为设备上下文分配保护域
    if (!pd) {                                             // 检查保护域是否分配成功
        perror("ibv_alloc_pd");                            // 如果失败,打印错误信息
        return EXIT_FAILURE;                               // 返回失败状态
    }

    // 注册内存区域
    mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);  // 注册内存区域
    if (!mr) {                                             // 检查内存区域是否注册成功
        perror("ibv_reg_mr");                              // 如果失败,打印错误信息
        return EXIT_FAILURE;                               // 返回失败状态
    }

    // 创建完成队列
    cq = ibv_create_cq(ctx, 10, NULL, NULL, 0);            // 创建一个完成队列,最多包含10个完成元素
    if (!cq) {                                             // 检查完成队列是否创建成功
        perror("ibv_create_cq");                           // 如果失败,打印错误信息
        return EXIT_FAILURE;                               // 返回失败状态
    }

    // 设置队列对属性
    memset(&qp_attr, 0, sizeof(qp_attr));                  // 将队列对初始化属性结构清零
    qp_attr.cap.max_send_wr = 1;                           // 设置最大发送请求数为1
    qp_attr.cap.max_recv_wr = 1;                           // 设置最大接收请求数为1
    qp_attr.cap.max_send_sge = 1;                          // 设置单个发送请求最大散播聚合元素数为1
    qp_attr.cap.max_recv_sge = 1;                          // 设置单个接收请求最大散播聚合元素数为1
    qp_attr.send_cq = cq;                                  // 关联发送完成队列
    qp_attr.recv_cq = cq;                                  // 关联接收完成队列
    qp_attr.qp_type = IBV_QPT_RC;                          // 设置队列对类型为可靠连接

    // 创建队列对
    qp = ibv_create_qp(pd, &qp_attr);                      // 创建队列对
    if (!qp) {                                             // 检查队列对是否创建成功
        perror("ibv_create_qp");                           // 如果失败,打印错误信息
        return EXIT_FAILURE;                               // 返回失败状态
    }

    printf("Queue Pair created successfully\n");           // 打印成功创建队列对的消息

    // 清理
    ibv_destroy_qp(qp);                                    // 销毁队列对
    ibv_destroy_cq(cq);                                    // 销毁完成队列
    ibv_dereg_mr(mr);                                      // 取消注册内存区域
    ibv_dealloc_pd(pd);                                    // 释放保护域
    ibv_close_device(ctx);                                 // 关闭设备
    ibv_free_device_list(dev_list);                        // 释放设备列表

    return EXIT_SUCCESS;                                   // 返回成功状态
}

演示如何使用libibverbs库创建并初始化一个InfiniBand队列对(Queue Pair,QP)。

4.3.1 获取InfiniBand设备列表并打开设备:(此过程就是4.2介绍的过程)

dev_list = ibv_get_device_list(NULL);
ib_dev = dev_list[0];
ctx = ibv_open_device(ib_dev);

4.3.2 创建保护域(Protection Domain,PD):

pd = ibv_alloc_pd(ctx);

4.3.3 注册内存区域:

mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);

4.3.4 创建完成队列(Completion Queue,CQ)

cq = ibv_create_cq(ctx, 10, NULL, NULL, 0);

4.3.5  设置队列对属性并创建队列对:

memset(&qp_attr, 0, sizeof(qp_attr));
qp_attr.cap.max_send_wr = 1;
qp_attr.cap.max_recv_wr = 1;
qp_attr.cap.max_send_sge = 1;
qp_attr.cap.max_recv_sge = 1;
qp_attr.send_cq = cq;
qp_attr.recv_cq = cq;
qp_attr.qp_type = IBV_QPT_RC;

qp = ibv_create_qp(pd, &qp_attr);

4.3.6 清理资源:

程序结束前清理分配的资源。 

ibv_destroy_qp(qp);
ibv_destroy_cq(cq);
ibv_dereg_mr(mr);
ibv_dealloc_pd(pd);
ibv_close_device(ctx);
ibv_free_device_list(dev_list);

4.3.7 演示效果

这表示队列对(Queue Pair)成功创建。程序会在执行后清理所有资源,确保没有资源泄漏。

4.4 建立 RDMA 连接

你需要使用 rdma_cm 库来建立 RDMA 连接。这包括服务器和客户端之间的连接建立。

 1. 服务器端代码(与3.1代码略有不同)

与3.1最大差异体现在资源释放更加全面,确保在出错时也能释放所有已分配的资源。

#include <stdio.h>       // 包含标准输入输出库
#include <stdlib.h>      // 包含标准库
#include <string.h>      // 包含字符串处理库
#include <rdma/rdma_cma.h>  // 包含RDMA连接管理API

#define BUFFER_SIZE 1024  // 定义缓冲区大小

void run_server() {
    // 创建RDMA事件通道,用于接收RDMA事件
    struct rdma_event_channel *ec = rdma_create_event_channel();
    struct rdma_cm_id *listener = NULL, *conn = NULL;  // RDMA连接标识符
    struct rdma_addrinfo hints, *res;  // RDMA地址信息结构和指针
    struct ibv_pd *pd = NULL;  // 保护域
    struct ibv_mr *mr = NULL;  // 内存注册区域
    struct ibv_comp_channel *comp_chan = NULL;  // 完成事件通道
    struct ibv_cq *cq = NULL;  // 完成队列
    struct ibv_qp_init_attr qp_attr;  // 队列对初始化属性
    char buf[BUFFER_SIZE];  // 数据缓冲区
    int ret;  // 返回值变量

    // 配置地址信息
    memset(&hints, 0, sizeof(hints));  // 清空结构体
    hints.ai_flags = RAI_PASSIVE;  // 设置为被动模式
    hints.ai_port_space = RDMA_PS_TCP;  // 设置端口空间为TCP

    // 获取地址信息
    ret = rdma_getaddrinfo(NULL, "20079", &hints, &res);
    if (ret) {
        perror("rdma_getaddrinfo");  // 打印错误信息
        exit(EXIT_FAILURE);  // 退出程序
    }

    // 创建RDMA标识符
    ret = rdma_create_id(ec, &listener, NULL, RDMA_PS_TCP);
    if (ret) {
        perror("rdma_create_id");
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 绑定地址
    ret = rdma_bind_addr(listener, res->ai_src_addr);
    if (ret) {
        perror("rdma_bind_addr");
        rdma_destroy_id(listener);  // 销毁RDMA标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 监听连接请求
    ret = rdma_listen(listener, 0);
    if (ret) {
        perror("rdma_listen");
        rdma_destroy_id(listener);  // 销毁RDMA标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    printf("Server is listening on port 20079...\n");

    // 等待连接请求事件
    struct rdma_cm_event *event;
    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        rdma_destroy_id(listener);  // 销毁RDMA标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 处理连接请求事件
    if (event->event == RDMA_CM_EVENT_CONNECT_REQUEST) {
        conn = event->id;  // 获取连接标识符
        rdma_ack_cm_event(event);  // 确认事件
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);  // 确认事件
        rdma_destroy_id(listener);  // 销毁RDMA标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 分配保护域
    pd = ibv_alloc_pd(conn->verbs);
    if (!pd) {
        perror("ibv_alloc_pd");
        rdma_destroy_id(conn);  // 销毁连接标识符
        rdma_destroy_id(listener);  // 销毁监听标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 创建完成事件通道
    comp_chan = ibv_create_comp_channel(conn->verbs);
    if (!comp_chan) {
        perror("ibv_create_comp_channel");
        ibv_dealloc_pd(pd);  // 释放保护域
        rdma_destroy_id(conn);  // 销毁连接标识符
        rdma_destroy_id(listener);  // 销毁监听标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 创建完成队列
    cq = ibv_create_cq(conn->verbs, 10, NULL, comp_chan, 0);
    if (!cq) {
        perror("ibv_create_cq");
        ibv_destroy_comp_channel(comp_chan);  // 销毁完成事件通道
        ibv_dealloc_pd(pd);  // 释放保护域
        rdma_destroy_id(conn);  // 销毁连接标识符
        rdma_destroy_id(listener);  // 销毁监听标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 配置队列对属性
    memset(&qp_attr, 0, sizeof(qp_attr));
    qp_attr.cap.max_send_wr = 1;  // 最大发送请求数
    qp_attr.cap.max_recv_wr = 1;  // 最大接收请求数
    qp_attr.cap.max_send_sge = 1;  // 最大发送SGE数
    qp_attr.cap.max_recv_sge = 1;  // 最大接收SGE数
    qp_attr.send_cq = cq;  // 发送完成队列
    qp_attr.recv_cq = cq;  // 接收完成队列
    qp_attr.qp_type = IBV_QPT_RC;  // 队列对类型

    // 创建队列对
    ret = rdma_create_qp(conn, pd, &qp_attr);
    if (ret) {
        perror("rdma_create_qp");
        ibv_destroy_cq(cq);  // 销毁完成队列
        ibv_destroy_comp_channel(comp_chan);  // 销毁完成事件通道
        ibv_dealloc_pd(pd);  // 释放保护域
        rdma_destroy_id(conn);  // 销毁连接标识符
        rdma_destroy_id(listener);  // 销毁监听标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 注册内存区域
    mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);
    if (!mr) {
        perror("ibv_reg_mr");
        rdma_destroy_qp(conn);  // 销毁队列对
        ibv_destroy_cq(cq);  // 销毁完成队列
        ibv_destroy_comp_channel(comp_chan);  // 销毁完成事件通道
        ibv_dealloc_pd(pd);  // 释放保护域
        rdma_destroy_id(conn);  // 销毁连接标识符
        rdma_destroy_id(listener);  // 销毁监听标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 设置接收请求
    struct ibv_recv_wr wr, *bad_wr = NULL;
    struct ibv_sge sge;
    sge.addr = (uintptr_t)buf;
    sge.length = BUFFER_SIZE;
    sge.lkey = mr->lkey;

    wr.wr_id = 0;
    wr.next = NULL;
    wr.sg_list = &sge;
    wr.num_sge = 1;

    // 发起接收请求
    ret = ibv_post_recv(conn->qp, &wr, &bad_wr);
    if (ret) {
        perror("ibv_post_recv");
        ibv_dereg_mr(mr);  // 解除内存注册
        rdma_destroy_qp(conn);  // 销毁队列对
        ibv_destroy_cq(cq);  // 销毁完成队列
        ibv_destroy_comp_channel(comp_chan);  // 销毁完成事件通道
        ibv_dealloc_pd(pd);  // 释放保护域
        rdma_destroy_id(conn);  // 销毁连接标识符
        rdma_destroy_id(listener);  // 销毁监听标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 接受连接请求
    struct rdma_conn_param cm_params;
    memset(&cm_params, 0, sizeof(cm_params));
    ret = rdma_accept(conn, &cm_params);
    if (ret) {
        perror("rdma_accept");
        rdma_disconnect(conn);  // 断开连接
        ibv_dereg_mr(mr);  // 解除内存注册
        rdma_destroy_qp(conn);  // 销毁队列对
        ibv_destroy_cq(cq);  // 销毁完成队列
        ibv_destroy_comp_channel(comp_chan);  // 销毁完成事件通道
        ibv_dealloc_pd(pd);  // 释放保护域
        rdma_destroy_id(conn);  // 销毁连接标识符
        rdma_destroy_id(listener);  // 销毁监听标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 轮询完成队列,等待数据接收
    struct ibv_wc wc;
    while ((ret = ibv_poll_cq(cq, 1, &wc)) == 0);
    if (ret < 0) {
        perror("ibv_poll_cq");
        rdma_disconnect(conn);  // 断开连接
        ibv_dereg_mr(mr);  // 解除内存注册
        rdma_destroy_qp(conn);  // 销毁队列对
        ibv_destroy_cq(cq);  // 销毁完成队列
        ibv_destroy_comp_channel(comp_chan);  // 销毁完成事件通道
        ibv_dealloc_pd(pd);  // 释放保护域
        rdma_destroy_id(conn);  // 销毁连接标识符
        rdma_destroy_id(listener);  // 销毁监听标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 检查完成状态
    if (wc.status != IBV_WC_SUCCESS) {
        fprintf(stderr, "Failed status %s (%d) for wr_id %d\n",
                ibv_wc_status_str(wc.status), wc.status, (int) wc.wr_id);
        rdma_disconnect(conn);  // 断开连接
        ibv_dereg_mr(mr);  // 解除内存注册
        rdma_destroy_qp(conn);  // 销毁队列对
        ibv_destroy_cq(cq);  // 销毁完成队列
        ibv_destroy_comp_channel(comp_chan);  // 销毁完成事件通道
        ibv_dealloc_pd(pd);  // 释放保护域
        rdma_destroy_id(conn);  // 销毁连接标识符
        rdma_destroy_id(listener);  // 销毁监听标识符
        rdma_freeaddrinfo(res);  // 释放地址信息
        exit(EXIT_FAILURE);
    }

    // 打印接收到的消息
    printf("Received message: %s\n", buf);

    // 清理资源
    rdma_disconnect(conn);  // 断开连接
    rdma_destroy_qp(conn);  // 销毁队列对
    ibv_dereg_mr(mr);  // 解除内存注册
    ibv_destroy_cq(cq);  // 销毁完成队列
    ibv_destroy_comp_channel(comp_chan);  // 销毁完成事件通道
    ibv_dealloc_pd(pd);  // 释放保护域
    rdma_destroy_id(conn);  // 销毁连接标识符
    rdma_destroy_id(listener);  // 销毁监听标识符
    rdma_destroy_event_channel(ec);  // 销毁事件通道
    rdma_freeaddrinfo(res);  // 释放地址信息
}

int main() {
    run_server();  // 运行服务器
    return 0;
}

2. 客户端代码(与3.2代码相同)

保存以下代码为 rdma_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <rdma/rdma_cma.h>
#include <infiniband/verbs.h>

#define BUFFER_SIZE 1024

void run_client(const char *server) {
    struct rdma_event_channel *ec = rdma_create_event_channel();
    struct rdma_cm_id *conn = NULL;
    struct rdma_addrinfo hints, *res;
    struct ibv_pd *pd = NULL;
    struct ibv_mr *mr = NULL;
    struct ibv_comp_channel *comp_chan = NULL;
    struct ibv_cq *cq = NULL;
    struct ibv_qp_init_attr qp_attr;
    char buf[BUFFER_SIZE];
    int ret;

    // 配置地址信息
    memset(&hints, 0, sizeof(hints));
    hints.ai_port_space = RDMA_PS_TCP;

    // 获取地址信息
    ret = rdma_getaddrinfo(server, "20079", &hints, &res);
    if (ret) {
        perror("rdma_getaddrinfo");
        exit(EXIT_FAILURE);
    }

    // 创建 RDMA 连接标识符
    ret = rdma_create_id(ec, &conn, NULL, RDMA_PS_TCP);
    if (ret) {
        perror("rdma_create_id");
        exit(EXIT_FAILURE);
    }

    // 解析地址
    ret = rdma_resolve_addr(conn, NULL, res->ai_dst_addr, 2000);
    if (ret) {
        perror("rdma_resolve_addr");
        exit(EXIT_FAILURE);
    }

    struct rdma_cm_event *event;
    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        exit(EXIT_FAILURE);
    }

    if (event->event == RDMA_CM_EVENT_ADDR_RESOLVED) {
        rdma_ack_cm_event(event);
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);
        exit(EXIT_FAILURE);
    }

    // 解析路由
    ret = rdma_resolve_route(conn, 2000);
    if (ret) {
        perror("rdma_resolve_route");
        exit(EXIT_FAILURE);
    }

    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        exit(EXIT_FAILURE);
    }

    if (event->event == RDMA_CM_EVENT_ROUTE_RESOLVED) {
        rdma_ack_cm_event(event);
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);
        exit(EXIT_FAILURE);
    }

    // 分配保护域
    pd = ibv_alloc_pd(conn->verbs);
    if (!pd) {
        perror("ibv_alloc_pd");
        exit(EXIT_FAILURE);
    }

    // 创建完成事件通道
    comp_chan = ibv_create_comp_channel(conn->verbs);
    if (!comp_chan) {
        perror("ibv_create_comp_channel");
        exit(EXIT_FAILURE);
    }

    // 创建完成队列
    cq = ibv_create_cq(conn->verbs, 10, NULL, comp_chan, 0);
    if (!cq) {
        perror("ibv_create_cq");
        exit(EXIT_FAILURE);
    }

    // 配置队列对属性
    memset(&qp_attr, 0, sizeof(qp_attr));
    qp_attr.cap.max_send_wr = 1;
    qp_attr.cap.max_recv_wr = 1;
    qp_attr.cap.max_send_sge = 1;
    qp_attr.cap.max_recv_sge = 1;
    qp_attr.send_cq = cq;
    qp_attr.recv_cq = cq;
    qp_attr.qp_type = IBV_QPT_RC;

    // 创建队列对
    ret = rdma_create_qp(conn, pd, &qp_attr);
    if (ret) {
        perror("rdma_create_qp");
        exit(EXIT_FAILURE);
    }

    // 注册内存区域
    mr = ibv_reg_mr(pd, buf, BUFFER_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);
    if (!mr) {
        perror("ibv_reg_mr");
        exit(EXIT_FAILURE);
    }

    // 连接到服务器
    struct rdma_conn_param cm_params;
    memset(&cm_params, 0, sizeof(cm_params));
    ret = rdma_connect(conn, &cm_params);
    if (ret) {
        perror("rdma_connect");
        exit(EXIT_FAILURE);
    }

    // 等待连接建立事件
    ret = rdma_get_cm_event(ec, &event);
    if (ret) {
        perror("rdma_get_cm_event");
        exit(EXIT_FAILURE);
    }

    if (event->event == RDMA_CM_EVENT_ESTABLISHED) {
        rdma_ack_cm_event(event);
    } else {
        fprintf(stderr, "Unexpected event: %d\n", event->event);
        rdma_ack_cm_event(event);
        exit(EXIT_FAILURE);
    }

    // 准备发送数据
    strcpy(buf, "Hello, RDMA!");
    struct ibv_send_wr wr, *bad_wr = NULL;
    struct ibv_sge sge;
    sge.addr = (uintptr_t)buf;
    sge.length = BUFFER_SIZE;
    sge.lkey = mr->lkey;

    wr.wr_id = 0;
    wr.next = NULL;
    wr.sg_list = &sge;
    wr.num_sge = 1;
    wr.opcode = IBV_WR_SEND;
    wr.send_flags = IBV_SEND_SIGNALED;

    // 发送数据
    ret = ibv_post_send(conn->qp, &wr, &bad_wr);
    if (ret) {
        perror("ibv_post_send");
        exit(EXIT_FAILURE);
    }

    // 轮询完成队列
    struct ibv_wc wc;
    while ((ret = ibv_poll_cq(cq, 1, &wc)) == 0);
    if (ret < 0) {
        perror("ibv_poll_cq");
        exit(EXIT_FAILURE);
    }

    if (wc.status != IBV_WC_SUCCESS) {
        fprintf(stderr, "Failed status %s (%d) for wr_id %d\n",
                ibv_wc_status_str(wc.status), wc.status, (int) wc.wr_id);
        exit(EXIT_FAILURE);
    }

    printf("Message sent: %s\n", buf);

    // 清理资源
    rdma_disconnect(conn);
    rdma_destroy_qp(conn);
    ibv_dereg_mr(mr);
    ibv_destroy_cq(cq);
    ibv_destroy_comp_channel(comp_chan);
    ibv_dealloc_pd(pd);
    rdma_destroy_id(conn);
    rdma_destroy_event_channel(ec);
    rdma_freeaddrinfo(res);
}

int main(int argc, char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <server-address>\n", argv[0]);
        return EXIT_FAILURE;
    }

    run_client(argv[1]);
    return 0;
}

4.5 编译和运行

确保先编译服务器和客户端代码:

gcc -o rdma_server rdma_server.c -lrdmacm -libverbs
gcc -o rdma_client rdma_client.c -lrdmacm -libverbs

 注意:

如果遇到此错误,因为链接器找不到 ibv_reg_mr_iova2 函数的定义。这个函数可能是你所使用的库版本中不存在的,或者它属于另一个库。

解决方案:

1.确保你安装的库版本是正确的,且包含 ibv_reg_mr_iova2 函数。检查所使用的库版本以及文档,确认该函数确实存在。

2.确保链接器能够找到正确的库。确保你已经正确安装并链接了 rdma-core 包。如果库路径不在默认的搜索路径中,可能需要手动指定库路径。例如:

gcc -o rdma_client rdma_client.c -L/usr/local/lib -lrdmacm -libverbs

首先启动服务器:

./rdma_server

然后在另一个终端中启动客户端,替换 <server-address> 为服务器的实际 IP 地址:

./rdma_client <server-address>

 4.6 结果

服务器将打印收到的消息:

客户端将打印发送的消息:

可以了解到如何使用 rdma-core 进行基本的 RDMA 连接和数据传输。

五、验证RDMA 在高性能和低延迟场景中的应用价值

验证 RDMA(远程直接内存访问)在高性能和低延迟场景中的应用价值,可以通过一系列实验和测试来进行。下面是一些常见的方法和指标,用于验证和评估 RDMA 系统的性能和延迟。

5.1 性能测试方法

5.1.1 吞吐量测试

目的:评估系统的数据传输速度。

方法:在一段时间内持续传输数据,统计传输的数据量(如每秒传输的字节数)。

工具:可以使用 Iperf 等网络性能测试工具,或编写自定义测试脚本。

5.1.2 延迟测试

目的:评估系统的数据传输延迟。

方法:测量发送方发送数据包到接收方接收到数据包之间的时间。

工具:可以使用 ping、qperf 等工具,或通过编写自定义代码测量 RTT(往返时间)。

5.2.3 零拷贝效应

目的:评估零拷贝技术对系统性能的提升。

方法:比较启用和禁用零拷贝技术情况下的吞吐量和延迟。

工具:自定义测试代码和性能分析工具。

5.2.4 资源使用率分析

目的:评估系统的资源消耗情况,包括 CPU 使用率、内存占用等。

方法:监控系统在数据传输过程中的资源使用情况。

工具:top、htop、vmstat 等系统监控工具。

5.2  实验设计

5.2.1  测试环境搭建:

搭建一个包含服务器和客户端的测试环境,可以使用物理机或虚拟机。

配置网络,使其支持 RDMA 通信。

5.2.2 基准测试:

选择传统的 TCP/IP 网络通信作为对比基准。

运行相同的测试用例,对比 RDMA 和 TCP/IP 在吞吐量和延迟上的性能差异。

5.2.3 压力测试:

通过增加并发连接数和数据包大小,测试 RDMA 系统在高负载下的表现。

观察系统在高负载下的稳定性和性能变化。

5.3 测试指标

5.3.1 吞吐量(Throughput):

单位:Gbps 或 MB/s。

高吞吐量表明系统能够在单位时间内传输大量数据。

5.3.2 延迟(Latency):

单位:微秒(µs)或毫秒(ms)。

低延迟表明系统能够快速传输数据,适用于实时应用。

5.3.3 资源使用率(Resource Utilization):

包括 CPU 使用率、内存占用、网络带宽利用率等。

低资源使用率表明系统在高效运行。

5.4 实验结果分析

通过上述方法和指标,可以得到如下结论:

1.高吞吐量:如果 RDMA 系统在吞吐量测试中表现优异,传输速率显著高于传统 TCP/IP 网络,则表明 RDMA 系统能够在单位时间内传输更多的数据。

2. 低延迟:如果 RDMA 系统在延迟测试中表现出显著低于传统网络的延迟,则表明 RDMA 系统能够较快地传输数据,适用于对时间敏感的应用场景。

3. 低资源消耗:如果 RDMA 系统在资源使用率分析中表现出较低的 CPU 和内存消耗,则表明 RDMA 系统在处理数据传输时较为高效。

5.5 实验示例

  • 环境:两台配置相同的服务器,连接在同一 RDMA 支持的网络中。
  • 基准测试
    • 使用 TCP/IP 进行数据传输,记录吞吐量和延迟。
    • 使用 RDMA 进行相同的数据传输,记录吞吐量和延迟。
  • 结果
    • TCP/IP 吞吐量:5 Gbps;延迟:500 微秒。
    • RDMA 吞吐量:20 Gbps;延迟:50 微秒。
  • 资源使用率
    • TCP/IP CPU 使用率:80%;内存占用:1 GB。
    • RDMA CPU 使用率:20%;内存占用:500 MB。

通过对比,可以得出结论:RDMA 系统在吞吐量上有显著提升,延迟大幅降低,同时资源消耗也更低。这些结果验证了 RDMA 在高性能和低延迟场景中的应用价值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1977518.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅谈Trie树算法(c++)

文章目录 于是他错误的点名开始了题目背景题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示思路AC代码 01Trie求n个数两两异或的最大值AC代码 Nikitosh 和异或思路AC代码 The XOR-longest Path思路AC代码 又称字典树&#xff0c;用边来代表字母&#xff0c;而从根结…

《最终幻想14》手游版已获准在中国发行

上个月&#xff0c;有传言称史克威尔和腾讯正在合作开发前者大获成功的MMORPG《最终幻想14》的手机版。Niko Partners分析师丹尼尔艾哈迈德在推特上提到&#xff0c;中国国家新闻出版署已批准发行新一批进口游戏&#xff0c;其中包括《最终幻想14》的手机版&#xff0c;名为《最…

AI智能名片小程序:匹配法则下的粉丝经济新探索

摘要&#xff1a;在数字化时代&#xff0c;企业与消费者之间的互动方式正经历着前所未有的变革。AI智能名片小程序作为这一变革中的新兴产物&#xff0c;不仅重塑了传统商务交流的场景&#xff0c;更在匹配法则的指导下&#xff0c;深刻影响着品牌与粉丝关系的构建与维护。本文…

网络安全数字化转型

1. 背景介绍 在当今数字化浪潮席卷全球的背景下&#xff0c;推行数字化网络安全建设显得尤为迫切与重要&#xff0c;这主要根植于两大核心驱动力&#xff1a;实战挑战的严峻性与行业发展的迫切需求。 1.1. 实战难题的迫切应对 随着信息技术的飞速发展&#xff0c;网络…

全球汽车用粉末涂料市场规划预测:2030年市场规模将接近199亿元,未来六年CAGR为4.3%

一、引言 随着全球汽车行业的持续发展&#xff0c;汽车用粉末涂料作为车辆涂装的重要材料&#xff0c;其市场重要性日益凸显。本文旨在探索汽车用粉末涂料行业的发展趋势、潜在商机及其未来展望。 二、市场趋势 全球汽车用粉末涂料市场的增长主要受全球汽车产量增加、消费者对…

OpenCV||超详细的几何变换

2D图像几何变换的33矩阵&#xff1a; 图像常见的几何变换&#xff1a; 图像来源&#xff1a;《OpenCV 4.5计算机视觉开发实战&#xff1a;基于Python》作者&#xff1a;朱文伟 李建英&#xff1b; 1. 平移&#xff08;Translation&#xff09; 在OpenCV中&#xff0c;平移不是…

如果你感到焦虑、精神内耗,那就跑步去吧!

点击上方△腾阳 关注 转载请联系授权 你好&#xff0c;我是腾阳。 深夜里&#xff0c;你是否辗转反侧、思绪万千却难以入眠&#xff1f; 面对工作截止日期压力山大、心力交瘁&#xff0c;但总提不起神&#xff0c;工作效率低下&#xff1f; 人际交往中&#xff0c;被误解和…

windows 下使用MSYS2编译ffmpeg

1. 下载ffmpeg ,最新源码下载FFmpeghttps://ffmpeg.org/ 2.下载MSYS2,并安装(正常操作步骤) MSYS2Software Distribution and Building Platform for Windowshttps://www.msys2.org/3. 安装好MSYS2后,配置编译环境 打开mingw64.e

Python 进度条:告别枯燥等待,让你的程序动感十足!

在日常编程中&#xff0c;我们经常会遇到需要处理耗时任务的情况&#xff0c;例如文件下载、数据处理等等。看着程序运行&#xff0c;却只能干巴巴地等待&#xff0c;实在令人心焦。 别担心&#xff01;今天就来教你如何使用 Python 创建炫酷的进度条&#xff0c;告别枯燥等待…

董明珠:格力正在开发“不要电”的空调!

8月2日&#xff0c;格力电器在河北召开“格力冰洗生活电器战略发布会”。格力电器董事长兼总裁董明珠在现场发表讲话&#xff0c;透露格力正在开发一个“不要电”的空调。据悉&#xff0c;2012年格力就开始开发这个技术。 董明珠表示&#xff0c;现在的光伏发电需要通过逆变器…

横屏无水印风景视频素材去哪里找啊?横屏无水印素材库分享

在进行视频创作和编辑时&#xff0c;拥有高质量的横屏无水印风景视频素材非常关键&#xff0c;尤其是当你打造旅行记录、自然纪录片或是背景视频等内容时。这类素材不仅能显著提升你的作品的视觉效果&#xff0c;还能加强其感染力。然而&#xff0c;许多创作者在寻找合适的风景…

每日学术速递8.4

1.Sparse vs Contiguous Adversarial Pixel Perturbations in Multimodal 标题&#xff1a; Models: An Empirical Analysis 多模态模型中的稀疏与连续对抗性像素扰动&#xff1a;实证分析 作者&#xff1a; Cristian-Alexandru Botocan, Raphael Meier, Ljiljana Dolamic 文…

eslint配置忽略目录和文件

本部分选自《基于vite构建vue3开发环境——eslint整合》。 默认配置下&#xff0c;咱们引入第三方的svg库js文件也会被eslint检查&#xff1a; 在eslint.config.js中增加配置&#xff1a; 注意 这里不能写成./src/assets&#xff0c;除了忽略整个目录&#xff0c;也可以通过通配…

golang命名异常 error var Xxx should have name of the form ErrFoo (ST1012) 解决方法

异常原因分析 这个提示通常发生在我们使用var 定义一个变量来接收 error异常信息时的变量名称不规范所致。 其实这里的异常信息“ error var Xxx should have name of the form ErrFoo (ST1012)” 这个提示里面也已经提醒了" 变量 Xxx 的名称格式应该为 ErrFoo ", …

网络编程相关

关于ipv4和v6 ipv4小细节-------公网和私有地址 端口 InetAddress 协议 UDP、TCP UDP通信程序 发送&#xff08;单播&#xff09;&#xff1a; 接收&#xff08;单播&#xff09;&#xff1a; UDP三种通信方式 单播和广播代码几乎相同&#xff0c;就是将&#xff1a; InetAddr…

第17课 Scratch入门篇:时钟

时钟 故事背景&#xff1a; 在一个遥远的科技星球上&#xff0c;时间对于居民们来说无比珍贵。这个星球上的居民们都是技术高手&#xff0c;他们使用先进的编程技术来管理自己的生活。然而&#xff0c;星球上的时间系统最近出现了故障&#xff0c;导致时间的流逝变得不稳定。为…

【Material-UI 组件】 Autocomplete中的 Free Solo 模式详解

文章目录 一、组件概述1.1 Free Solo 的定义1.2 适用场景 二、基础用法2.1 实现 Free Solo 模式2.2 注意事项 三、高级配置3.1 增强用户体验的设置3.2 可创建项 四、最佳实践4.1 性能优化4.2 可访问性4.3 处理非字符串选项 五、总结 Free Solo 模式允许用户输入任意值&#xff…

将本地微服务发布到docker镜像

描述 将本地springboot微服务发布到docker镜像中并启动容器 第一步 先本地idea创建一个简单的springboot服务&#xff0c;不需要连接数据库相关操作&#xff0c;只包含简单的接口功能做验证。 相关测试代码如下所示 package com.itwopqq.booting;import org.springframewor…

手写高斯牛顿求解非线性最小二乘问题

容易写错的地方&#xff1a; 注意你的residual定义是 z-h(x)&#xff0c; 还是 h(x) - z&#xff0c;这会影响到Hxb的符号。&#xff08;自己推一遍就知道了&#xff09;&#xff0c;我的习惯性定义是z-h(x) class GaussianNewtonOptimizer {// Observation: [x, y]// y std…