第二部分 · 6 / 10
高层实战:动手写业务
上一节列了 rdmanet 的方法。这一节回答最实际的问题:
「我现在要写自己的程序,第一步该敲什么?」
我们从零搭一个请求/响应(RPC)服务,再给一张「想做 X 用哪个方法」的速查表。
第 0 步:起手式
每个 RDMA 程序都从「判断当前环境是否支持」开始——这样代码在 macOS 上也能编译运行(只是优雅退出),见 跨平台 stub。固定模板:
import (
"github.com/smallnest/gordma"
"github.com/smallnest/gordma/rdmanet"
)
if !gordma.Supported() {
log.Fatal("RDMA not supported here") // 非 Linux / 无硬件
}
opts := []rdmanet.Option{
rdmanet.WithDevice("mlx5_1"), // 选网卡
rdmanet.WithGIDIndex(3), // RoCE v2 常用 3
}
第 1 步:服务端循环
和标准库 net 几乎一样:Listen → Accept → 处理 → 关闭。RPC 服务端是一个
「收请求 → 处理 → 回响应」的循环,客户端断开时 RecvMsg 返回 io.EOF:
func serve(addr string, opts []rdmanet.Option) error {
ln, err := rdmanet.Listen(addr, opts...)
if err != nil { return err }
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil { return err }
go handle(conn) // 每个连接一个 goroutine
}
}
func handle(conn *rdmanet.Conn) {
defer conn.Close()
for {
req, err := conn.RecvMsg() // 一条完整请求
if err == io.EOF { return } // 客户端关闭,正常结束
if err != nil { log.Print(err); return }
resp := process(req) // 你的业务逻辑
if err := conn.SendMsg(resp); err != nil {
log.Print(err); return
}
}
}
第 2 步:客户端
func call(addr string, req []byte, opts []rdmanet.Option) ([]byte, error) {
conn, err := rdmanet.Dial(addr, opts...)
if err != nil { return nil, err }
defer conn.Close()
if err := conn.SendMsg(req); err != nil {
return nil, err
}
return conn.RecvMsg() // 阻塞等响应
}
就这些——没有 MR、没有 WR、没有 CQ 轮询、没有状态机。rdmanet 把
上一节那一大坨底层代码全收进了这四个方法里。
这个骨架就是 examples/rpc 的核心。
速查:想做 X,用哪个?
| 我想…… | 用 | 参考示例 |
|---|---|---|
| 收发有边界的「消息」 | SendMsg / RecvMsg | echo-msg, rpc |
把连接当字节流(配 io.Copy) | Read / Write | echo-stream, file-transfer |
| 一次发/收多条,提消息率 | SendBatch / RecvBatch | batch, bench-throughput |
| 从预注册内存零拷贝发 | AllocBuffer + SendBuffer | zerocopy |
| 发大消息(自动分片重组) | SendMsg(透明处理) | large-message |
| 无连接数据报 / 一对多 | PacketConn + WriteTo/ReadFrom | packet, ud-broadcast |
| 带外发现 UD 对端 | NewRegistry + LookupAddr | registry |
| 全双工并发收发 | 一读 goroutine + 一写 goroutine | chat |
| 服务多个客户端 | Accept 循环 + 每连接一 goroutine | multi-client |
| 不用 rdma_cm,走 TCP 握手 | WithHandshake() | handshake-dial, connection-styles |
| 榨干网卡的极限吞吐 | RawConn | (见下一节) |
| 单边 RDMA Write/Read | 下沉到底层 gordma 或 RawConn | cmd/go_write_bw |
几条务必记住的契约
- 消息边界保留:一次
SendMsg= 一次RecvMsg,无论多大。别假设它像 TCP 那样可以任意切分。 - 别混用
Read和RecvMsg:一个Conn只选一种接收风格(字节流适配器会缓存半条消息)。 - 并发契约:一个
Conn上「一个并发读 + 一个并发写」安全;不要多个 goroutine 同时SendMsg。 Close唤醒阻塞的RecvMsg(返回io.EOF),用它做优雅退出。- 两端设备要对齐:
WithDevice/WithGIDIndex两端必须指向同一张物理网络、同一 RoCE 版本(见 RoCE 与 GID)。
下一步怎么调优
骨架跑通后,性能不够再按需加 Option——不用一开始就堆:
- 吞吐不够?→ 试
SendBatch/ 调WithQueueDepth(见 性能选项)。 - 延迟敏感?→
WithPollMode(rdmanet.PollBusy)。 - 大消息频繁?→ 调大
WithBufferSize减少分片。 - 还要更快、能接受自己管内存?→ 上 RawConn。
gordma 教程