RoCE 的 UC SEND/RECEIVE 的完整交互过程分析

1. UC SEND/RECEIVE 的完整交互过程图示

下面用左右两纵列 画一个 UC SEND/RECEIVE 的完整交互图。

左列:发送方 Client/Sender;右列:接收方 Server/Receiver。

│ 中间箭头表示交互;带外 TCP 用于交换 QP 信息;RoCEv2 数据通道 用于真正的 SEND 报文。

text 复制代码
发送方 (Client/Sender)                                     接收方 (Server/Receiver)
────────────────────────────────────────────────────────────────────────────────────
【0】本地资源创建(双方独立,无交互)
ibv_get_device_list / ibv_open_device                      ibv_get_device_list / ibv_open_device
ibv_alloc_pd                                               ibv_alloc_pd
ibv_reg_mr(buf_S) -> lkey_S                                ibv_reg_mr(buf_R) -> lkey_R
ibv_create_cq()                                            ibv_create_cq()
ibv_create_qp(qp_type=IBV_QPT_UC) -> qp_S                  ibv_create_qp(qp_type=IBV_QPT_UC) -> qp_R

【1】带外 TCP 控制通道:交换连接信息
        │  发送: QPN_S, GID_S, PSN_S, MTU                          │
        │ ───────────────────────────────────────────────────────> │
        │  回复: QPN_R, GID_R, PSN_R, MTU                          │
        │ <─────────────────────────────────────────────────────── │
        │                                                          │

【2】QP 状态迁移:INIT -> RTR -> RTS
发送方 qp_S:                                               接收方 qp_R:
INIT -> RTR:                                               INIT -> RTR:
  dest_qp_num = QPN_R                                        dest_qp_num = QPN_S
  ah_attr.grh.dgid = GID_R                                   ah_attr.grh.dgid = GID_S
  path_mtu = 交换的 MTU                                      path_mtu = 交换的 MTU
  rq_psn = PSN_R                                             rq_psn = PSN_S
RTR -> RTS:                                                RTR -> RTS:
  sq_psn = PSN_S                                             sq_psn = PSN_R
  (可发送 SEND)                                              (可接收/也可发送)

【3】接收方必须先张贴 RECEIVE,否则报文会被静默丢弃
                                                           ibv_post_recv(qp_R, &recv_wr)
                                                           recv_wr.wr_id = RECV_WRID
                                                           recv_wr.sg_list -> buf_R
                                                           recv_wr.sg_list[0].lkey = lkey_R

【4】发送方张贴 SEND
ibv_post_send(qp_S, &send_wr)
send_wr.opcode = IBV_WR_SEND
send_wr.wr_id = SEND_WRID
send_wr.sg_list -> data_S
send_wr.sg_list[0].lkey = lkey_S
send_wr.send_flags = IBV_SEND_SIGNALED

【5】RoCEv2 数据通道:发送 SEND 报文
        │  UDP/IP/Eth + BTH: opcode = SEND_ONLY / SEND_LAST        │
        │  Dest QPN = QPN_R, PSN = ..., payload = data_S           │
        │ ───────────────────────────────────────────────────────> │
        │                                                          │
        │                                      RNIC 查 RQ:
        │                                      若匹配到 recv_wr:
        │                                        DMA 写入 buf_R
        │                                        生成 CQE: IBV_WC_RECV
        │                                      若未匹配:
        │                                        静默丢弃
        │                                        UC 无 ACK、无重传

【6】完成队列 CQ 通知
ibv_poll_cq(cq_S) -> CQE                                   ibv_poll_cq(cq_R) -> CQE
  opcode = IBV_WC_SEND                                       opcode = IBV_WC_RECV
  wr_id  = SEND_WRID                                         wr_id  = RECV_WRID
  ⚠ 仅表示本地 RNIC 已发出,                                处理 buf_R 中的数据
    不保证接收方已收到

【7】可选:应用层确认 / 超时重传
        │ <─────────────────────────────────────────────────────── │
        │   应用层 ACK:TCP 或另一次 SEND                           │
        │                                                          │

【8】资源释放
ibv_destroy_qp / ibv_destroy_cq / ibv_dereg_mr             同样按顺序释放
ibv_dealloc_pd / ibv_close_device

图中关键点

  1. UC 是面向连接的 :必须先通过带外 TCP 交换 QPN / GID / PSN / MTU,再把 QP 从 INIT -> RTR -> RTS。
  2. SEND/RECEIVE 是双边操作 :接收方必须先 ibv_post_recv(),发送方的 SEND 才有地方落数据。
  3. SEND 不需要交换 rkey :数据写到接收方哪个缓冲区,由接收方的 recv_wr.sg_list 决定,不是发送方指定远端地址。
  4. UC 不可靠 :没有 ACK/NACK、没有重传;如果接收方 RQ 中没有 RECV WR,或者网络丢包,报文会被丢弃。
  5. 发送方 IBV_WC_SEND 不等于对端收到:它只表示本地 RNIC 已经把报文发出。要确认对端处理成功,需要应用层自己做 ACK、超时和重传。

2. UC SEND/RECEIVE 的完整交互过程分析

UC(Unreliable Connection,不可靠连接)的 SEND/RECEIVE 是一套双边操作 。与单边操作(如 RDMA WRITE)不同,双边的 SEND/RECEIVE 要求接收方必须预先在接收队列(RQ)中张贴 RECEIVE 请求,为即将到达的数据准备好"落脚点"。

一个完整的交互流程可以分为以下五个阶段。

🧱 阶段一:资源创建与初始化(两端各自独立完成)

在建立连接之前,通信双方(Client 和 Server)都需要在本地完成一系列资源分配与初始化工作,这些操作是独立进行的。

  1. 获取设备列表 :通过 ibv_get_device_list() 获取可用的 RDMA 设备(如 mlx5_0)。
  2. 打开设备 :使用 ibv_open_device() 打开选定的设备,获取设备上下文 ibv_context。
  3. 分配保护域(PD) :调用 ibv_alloc_pd() 分配一个保护域。PD 是内存区域(MR)和队列对(QP)的容器,用于实现内存保护。
  4. 注册内存区域(MR) :使用 ibv_reg_mr() 注册将要用于发送和接收的内存缓冲区。注册后,网卡才能直接读写这些内存。返回的 ibv_mr 结构体中包含了后续操作所需的 lkey(本地密钥) 和 rkey(远程密钥)。
  5. 创建完成队列(CQ) :调用 ibv_create_cq() 创建完成队列。当发送或接收请求完成后,相应的完成事件(CQE)会被放入 CQ 中,供应用程序查询。
  6. 创建队列对(QP) :调用 ibv_create_qp() 创建 QP。关键是在 ibv_qp_init_attr 结构体中指定 qp_type = IBV_QPT_UC,并关联之前创建的发送 CQ 和接收 CQ。

🔗 阶段二:连接建立与状态迁移(双方协作)

UC 是面向连接的,一个 QP 必须与远端的另一个 QP 建立点对点连接。此阶段双方需要通过带外通道(如 TCP socket) 交换信息,并驱动本地 QP 完成状态迁移。

信息交换:双方通过 TCP 等可靠通道交换各自的 QP 连接信息,核心包括:

  • QPN(Queue Pair Number):本地 QP 的编号。
  • GID(Global Identifier):RoCEv2 中的全局标识符,用于寻址。
  • PSN(Packet Sequence Number):初始包序列号,用于检测丢包和乱序。

QP 状态迁移:RDMA 规范要求 QP 必须按顺序经过一系列状态才能进行通信。对于 UC,主要涉及以下两个关键状态:

  1. INIT → RTR(Ready to Receive) :调用 ibv_modify_qp() 将 QP 从 INIT 状态迁移到 RTR 状态。此时需要配置远端信息,包括 dest_qp_num(远端 QPN) 、ah_attr(包含远端 GID 的地址句柄) 、path_mtu 等。进入 RTR 后,QP 就可以接收 来自远端的 SEND 报文了。
  2. RTR → RTS(Ready to Send) :再次调用 ibv_modify_qp(),将 QP 从 RTR 状态迁移到 RTS 状态。此阶段需要设置 sq_psn(发送队列的初始 PSN) 。进入 RTS 后,QP 就可以发送 SEND 报文了。

注意 :由于 UC 是不可靠的,状态迁移中不需要设置 timeout、retry_cnt 等 RC 所必需的重传参数,这是它与 RC 在配置上的显著区别。

📨 阶段三:数据交换(SEND/RECEIVE 核心流程)

连接建立后,真正的数据交换才开始。由于是双边操作,接收方必须"先发制人",抢先张贴 RECEIVE 请求。

接收方:张贴 RECEIVE 请求(ibv_post_recv)

接收方应用程序调用 ibv_post_recv(),将一个接收工作请求(Receive WR)放入 QP 的接收队列(RQ)中。这个请求告诉网卡:"如果收到一个 SEND 消息,请把它的数据放到我指定的这个内存缓冲区里。"

c 复制代码
// 构建接收请求 (Receive WR)
struct ibv_recv_wr recv_wr = {
    .wr_id = RECV_WRID,           // 用户自定义的请求ID
    .sg_list = &recv_sge,         // 指向 scatter/gather 列表
    .num_sge = 1,
    .next = NULL
};
// 张贴到接收队列
ibv_post_recv(qp, &recv_wr, &bad_wr);
发送方:张贴 SEND 请求(ibv_post_send)

发送方应用程序调用 ibv_post_send(),将一个发送工作请求(Send WR)放入 QP 的发送队列(SQ)中。关键是要将 opcode 设置为 IBV_WR_SEND。

c 复制代码
// 构建发送请求 (Send WR)
struct ibv_send_wr send_wr = {
    .wr_id = SEND_WRID,
    .sg_list = &send_sge,         // 指向要发送的数据
    .num_sge = 1,
    .opcode = IBV_WR_SEND,        // ** 关键:指定为 SEND 操作 **
    .send_flags = IBV_SEND_SIGNALED, // 请求发送完成通知
    .next = NULL
};
// 张贴到发送队列
ibv_post_send(qp, &send_wr, &bad_wr);
硬件交互与不可靠性

发送方的网卡(RNIC)从 SQ 中取出 SEND 请求,将数据打包成 RoCEv2 报文(UDP 封装),通过网络发送出去。

接收方的网卡收到报文后,会查找 RQ 中是否有已张贴的 RECEIVE 请求:

  • 如果找到 :网卡直接将数据负载写入 RECEIVE 请求所指定的内存缓冲区,并生成一个完成事件(CQE)放入接收 CQ。
  • 如果找不到 :由于 UC 的不可靠 特性,该报文会被静默丢弃 。接收方不会收到任何通知,发送方也不会收到任何确认或重传,数据就永久丢失了。这是 UC 与 RC 最本质的区别------UC 将错误处理的职责完全交给了应用程序。

✅ 阶段四:完成通知(Completion Notification)

应用程序通过轮询(busy-polling)或等待事件(waiting for events)的方式检查完成队列(CQ),以获知操作是否完成。

  • 接收方 :当 RECEIVE 操作完成(数据已写入内存)后,CQ 中会出现一个 CQE。其 opcode 为 IBV_WC_RECV。
  • 发送方 :如果 SEND 请求的 send_flags 中设置了 IBV_SEND_SIGNALED,当发送操作被网卡处理后,CQ 中也会出现一个 CQE。其 opcode 为 IBV_WC_SEND。

应用程序通过 ibv_poll_cq() 取出 CQE,并根据 wr_id 区分是哪个请求完成了。

重要提示 :在 UC 中,发送方收到 IBV_WC_SEND 完成事件仅表示本地网卡已成功将报文发出 ,并不保证接收方已收到。接收方是否收到,只能通过应用程序层的逻辑(如接收方回复一个确认消息)来间接判断。

🧹 阶段五:资源释放

通信结束后,应用程序需要按顺序释放所有资源:

  1. ibv_destroy_qp():销毁 QP。
  2. ibv_destroy_cq():销毁 CQ。
  3. ibv_dereg_mr():注销内存区域。
  4. ibv_dealloc_pd():释放保护域。
  5. ibv_close_device():关闭设备。

💎 总结

UC SEND/RECEIVE 的完整交互过程,其核心逻辑可以概括为:

接收方必须"先下车"(post_recv),发送方才能"投递"(post_send);而一旦"投递"失败(丢包),由于 UC 没有"回执"机制,发送方无从知晓,接收方也静默丢弃,一切错误处理都依赖于上层应用自身的超时与重传逻辑。

这与 RC 形成了鲜明对比:RC 的 SEND/RECEIVE 由硬件保证按序可靠送达,而 UC 则是一个"尽力而为"的双边通信原语,用可靠性换取了更低的协议开销和更少的 QP 资源占用。

相关推荐
花间相见1 小时前
【计算基础|网络10】TCP(中):可靠传输
网络·网络协议·tcp/ip
谢亮_vipxieliang1 小时前
多智能体系统的“TCP/IP时刻”:MCP与A2A协议如何定义Agent时代
网络·tcp/ip·ai·php
草莓熊Lotso2 小时前
【C++ 基于微服务的即时通讯系统】基建三件套:gflags + gtest + spdlog 源码编译安装与实战入门
linux·网络·数据库·c++·微服务·log4j
通信大模型10 小时前
IEEE TCCN | 面向低空经济网络的Agentic AI驱动多无人机轨迹优化
网络·人工智能·无人机
wdfk_prog11 小时前
LWIP教程 09:从 `tcp_slowtmr()` 到 Fast Retransmit——TCP 超时重传与重复 ACK
服务器·网络·数据库·网络协议·tcp/ip
15Moonlight11 小时前
Linux网络(02):Socket 编程 UDP
linux·网络·udp
盛世宏博智慧档案11 小时前
一文读懂 PoE+Modbus TCP 温湿度传感器布线规范与故障排查
网络·网络协议·tcp/ip
liulilittle12 小时前
Linux 下 select 测试函数
linux·服务器·网络·数据库·c++·select·c