RDMA编程1 建立侦听

#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服务器的代码流程如下:

  1. rdma_create_event_channel
    这一步是创建一个event channel,event channel是RDMA设备在操作完成后,或者有连接请求等事件发生时,用来通知应用程序的通道。其内部就是一个file descriptor, 因此可以进行poll等操作。

  2. rdma_create_id
    这一步创建一个rdma_cm_id, 概念上等价与socket编程时的listen socket。

  3. rdma_bind_addr
    和socket编程一样,也要先绑定一个本地的地址和端口,以进行listen操作。

  4. rdma_listen
    开始侦听客户端的连接请求

  5. rdma_get_cm_event
    这个调用就是作用在第一步创建的event channel上面,要从event channel中获取一个事件。这是个阻塞调用,只有有事件时才会返回。在一切正常的情况下,函数返回时会得到一个 RDMA_CM_EVENT_CONNECT_REQUEST事件,也就是说,有客户端发起连接了。
    在事件的参数里面,会有一个新的rdma_cm_id传入。这点和socket是不同的,socket只有在accept后才有新的socket fd创建。

  6. ibv_alloc_pd
    创建一个protection domain。protection domain可以看作是一个内存保护单位,在内存区域和队列直接建立一个关联关系,防止未授权的访问。

  7. ibv_create_comp_channel
    和之前创建的event channel类似,这也是一个event channel,但只用来报告完成队列里面的事件。当完成队列里有新的任务完成时,就通过这个channel向应用程序报告。

  8. ibv_create_cq
    创建完成队列,创建时就指定使用第6步的channel。

  9. rdma_create_qp
    创建一个queue pair, 一个queue pair包括一个发送queue和一个接收queue. 指定使用前面创建的cq作为完成队列。该qp创建时就指定关联到第6步创建的pd上。

  10. ibv_reg_mr
    注册内存区域。RDMA使用的内存,必须事先进行注册。这个是可以理解的,DMA的内存在边界对齐,能否被swap等方面,都有要求。

  11. rdma_accept
    至此,做好了全部的准备工作,可以调用accept接受客户端的这个请求了。 --:)长出一口气 ~~ 且慢,

  12. rdma_ack_cm_event
    对于每个从event channel得到的事件,都要调用ack函数,否则会产生内存泄漏。这一步的ack是对应第5步的get。每一次get调用,都要有对应的ack调用。

  13. rdma_get_cm_event
    继续调用rdma_get_cm_event, 一切正常的话我们此时应该得到 RDMA_CM_EVENT_ESTABLISHED 事件,表示连接已经建立起来。不需要做额外的处理,直接rdma_ack_cm_event就行了

终于可以开始进行数据传输了 ==== (如何传输下篇再说)

##4. 关闭连接

  1. 断开连接
    rdma_get_cm_event返回RDMA_CM_EVENT_DISCONNECTED事件时,表示客户端断开了连接,server端要进行对应的清理。此时可以调用rdma_ack_cm_event释放事件资源。然后依次调用下面的函数,释放连接资源,内存资源,队列资源。

  2. rdma_disconnect

  3. rdma_destroy_qp

  4. ibv_dereg_mr

  5. rdma_destroy_id
    释放同客户端连接的rdma_cm_id

  6. rdma_destroy_id
    释放用于侦听的rdma_cm_id

  7. 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;
}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值