#RDMA编程 建立连接
##1. RDMA的学习环境搭建
RDMA需要专门的RDMA网卡或者InfiniBand卡才能使用,学习RDMA而又没有这些硬件设备,可以使用一个软件RDMA模拟环境,softiwarp ,
- 这是加载地址:https://github.com/zrlio/softiwarp
- 这是安装教程:http://www.reflectionsofthevoid.com/2011/03/how-to-install-soft-iwarp-on-ubuntu.html
这里也有一个RDMA编程的入门示例,
- https://github.com/tarickb/the-geek-in-the-corner
需要注意的是,这个例子里面缺省用的是IPv6连接,如果希望在IPv4环境下测试,需要先改代码用IPv4地址。
##2. RDMA与socket的类比
和Socket连接类似,RDMA连接也分为可靠连接和不可靠连接。然而也不完全相同,Socket的可靠连接就是TCP连接,是流式的;不可靠连接也就是UDP,是消息式的。对于RDMA来说,无论是可靠连接和不可靠连接,都是消息式的。
编程角度看,RDMA代码也分为Server端,Client端,也有bind, listen, connect, accept,等动作,然而细节上仍有不少区别。
##3. 在Server端,一个RDMA服务器的代码流程如下:
-
rdma_create_event_channel
这一步是创建一个event channel,event channel是RDMA设备在操作完成后,或者有连接请求等事件发生时,用来通知应用程序的通道。其内部就是一个file descriptor, 因此可以进行poll等操作。 -
rdma_create_id
这一步创建一个rdma_cm_id, 概念上等价与socket编程时的listen socket。 -
rdma_bind_addr
和socket编程一样,也要先绑定一个本地的地址和端口,以进行listen操作。 -
rdma_listen
开始侦听客户端的连接请求 -
rdma_get_cm_event
这个调用就是作用在第一步创建的event channel上面,要从event channel中获取一个事件。这是个阻塞调用,只有有事件时才会返回。在一切正常的情况下,函数返回时会得到一个 RDMA_CM_EVENT_CONNECT_REQUEST事件,也就是说,有客户端发起连接了。
在事件的参数里面,会有一个新的rdma_cm_id传入。这点和socket是不同的,socket只有在accept后才有新的socket fd创建。 -
ibv_alloc_pd
创建一个protection domain。protection domain可以看作是一个内存保护单位,在内存区域和队列直接建立一个关联关系,防止未授权的访问。 -
ibv_create_comp_channel
和之前创建的event channel类似,这也是一个event channel,但只用来报告完成队列里面的事件。当完成队列里有新的任务完成时,就通过这个channel向应用程序报告。 -
ibv_create_cq
创建完成队列,创建时就指定使用第6步的channel。 -
rdma_create_qp
创建一个queue pair, 一个queue pair包括一个发送queue和一个接收queue. 指定使用前面创建的cq作为完成队列。该qp创建时就指定关联到第6步创建的pd上。 -
ibv_reg_mr
注册内存区域。RDMA使用的内存,必须事先进行注册。这个是可以理解的,DMA的内存在边界对齐,能否被swap等方面,都有要求。 -
rdma_accept
至此,做好了全部的准备工作,可以调用accept接受客户端的这个请求了。 --:)长出一口气 ~~ 且慢, -
rdma_ack_cm_event
对于每个从event channel得到的事件,都要调用ack函数,否则会产生内存泄漏。这一步的ack是对应第5步的get。每一次get调用,都要有对应的ack调用。 -
rdma_get_cm_event
继续调用rdma_get_cm_event
, 一切正常的话我们此时应该得到 RDMA_CM_EVENT_ESTABLISHED 事件,表示连接已经建立起来。不需要做额外的处理,直接rdma_ack_cm_event
就行了
终于可以开始进行数据传输了 ==== (如何传输下篇再说)
##4. 关闭连接
-
断开连接
当rdma_get_cm_event
返回RDMA_CM_EVENT_DISCONNECTED事件时,表示客户端断开了连接,server端要进行对应的清理。此时可以调用rdma_ack_cm_event
释放事件资源。然后依次调用下面的函数,释放连接资源,内存资源,队列资源。 -
rdma_disconnect
-
rdma_destroy_qp
-
ibv_dereg_mr
-
rdma_destroy_id
释放同客户端连接的rdma_cm_id -
rdma_destroy_id
释放用于侦听的rdma_cm_id -
rdma_destroy_event_channel
释放 event channel
最后来show一下code吧, 这是server端的代码,client端的代码在本人的另外一篇:
//compile : gcc rdma-srv.cpp -libverbs -lrdmacm
#define _ISOC11_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <infiniband/verbs.h>
#include <rdma/rdma_cma.h>
struct rdma_event_channel* ec;
struct rdma_cm_id* cm_id;
struct ibv_pd* pd;
struct ibv_context* rdma_cm_context;
void* recv_buf;
#define RECV_BUF_SIZE 4096
struct ibv_mr* recv_mr;
int on_connect_request(struct rdma_cm_event* evt)
{
int rc = 0;
struct rdma_conn_param cm_params;
struct rdma_cm_id* id = evt->id;
//conn = new PfRdmaConnection();
//conn->dev_ctx = build_context(id->verbs);
struct ibv_context* rdma_context = id->verbs;
pd = ibv_alloc_pd(rdma_context);
recv_buf = aligned_alloc(4096, RECV_BUF_SIZE);
recv_mr = ibv_reg_mr(pd, recv_buf, RECV_BUF_SIZE, IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ);
struct ibv_comp_channel* comp_channel = ibv_create_comp_channel(rdma_context);
struct ibv_cq* cq = ibv_create_cq(rdma_context, 512, NULL, comp_channel, 0);
ibv_req_notify_cq(cq, 0);
struct ibv_qp_init_attr qp_attr={0};
qp_attr.send_cq = cq;
qp_attr.recv_cq = cq;
qp_attr.qp_type = IBV_QPT_RC;
qp_attr.cap.max_send_wr = 512;
qp_attr.cap.max_recv_wr = 512;
qp_attr.cap.max_send_sge = 1;
qp_attr.cap.max_recv_sge = 1;
rc = rdma_create_qp(id, pd, &qp_attr);
if(rc)
{
perror("rdma_create_qp, errno:%d");
return rc;
}
//id->context = conn;
struct ibv_recv_wr wr, *bad_wr = NULL;
struct ibv_sge sge;
wr.wr_id = (uint64_t)1;
wr.next = NULL;
wr.sg_list = &sge;
wr.num_sge = 1;
sge.addr = (uint64_t)recv_buf;
sge.length = RECV_BUF_SIZE;
sge.lkey = recv_mr->lkey;
rc = ibv_post_recv(id->qp, &wr, &bad_wr);
if (rc != 0)
{
perror("ibv_post_recv failed, ");
return rc;
}
memset(&cm_params, 0, sizeof(cm_params));
cm_params.responder_resources = (uint8_t)16;
cm_params.initiator_depth = (uint8_t)16;
cm_params.retry_count = 7;
cm_params.rnr_retry_count = 7;
rc = rdma_accept(id, &cm_params);
if (rc)
{
perror("rdma_accept failed, ");
return rc;
}
struct sockaddr * peer_addr = rdma_get_peer_addr(id);
char ip_str[64];
inet_ntop(AF_INET, &(((struct sockaddr_in *)peer_addr)->sin_addr),ip_str, sizeof(ip_str));
printf("rdma_accepted succeed, from peer:%s \n", ip_str);
struct ibv_wc wc[8];
void *cq_ctx;
int n;
ibv_get_cq_event(comp_channel, &cq, &cq_ctx);
ibv_ack_cq_events(cq, 1);
ibv_req_notify_cq(cq, 0);
printf("Begin polling completion queue ...\n");
while((n = ibv_poll_cq(cq, 8, wc)))
{
for(int i=0; i<n; i++)
{
long user_data = wc[i].wr_id;
assert(user_data == 1);
if(wc[i].status != IBV_WC_SUCCESS){
fprintf(stderr, "wc[%d].status != IBV_WC_SUCCESS, wc.status:%d, %s", i, wc[i].status, ibv_wc_status_str(wc[i].status));
return 1;
}
printf("Receive:%d bytes, '%.*s'\n", wc[i].byte_len, wc[i].byte_len, recv_buf);
}
}
return 0;
release0:
rdma_destroy_qp(id);
return rc;
}
int main(int argc, char** argv)
{
int rc;
int port=10121;
struct sockaddr_in listen_addr;
memset(&listen_addr, 0, sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons((uint16_t)port);
ec = rdma_create_event_channel();
if(ec == NULL){
perror("Failed create event channel");
return errno;
}
rc = rdma_create_id(ec, &cm_id, NULL, RDMA_PS_TCP);
if(rc)
{
perror("rdma_create_id");
return rc;
}
rdma_cm_context = cm_id->verbs;
rc = rdma_bind_addr(cm_id, (struct sockaddr*)&listen_addr);
if(rc)
{
perror("rdma_bind_addr");
return rc;
}
rc = rdma_listen(cm_id, 10);
if(rc)
{
perror("rdma_listen");
return rc;
}
struct rdma_cm_event* event = NULL;
printf("Waiting cm evnet ...\n");
while(rdma_get_cm_event(ec, &event)==0)
{
struct rdma_cm_event event_copy;
memcpy(&event_copy, event, sizeof(*event));
printf("Get cm evnet %d(%s)...\n", event_copy.event, rdma_event_str (event_copy.event));
rdma_ack_cm_event(event);
if(event_copy.event == RDMA_CM_EVENT_CONNECT_REQUEST)
{
on_connect_request(&event_copy);
}
}
return 0;
}