我的新浪微博:http://weibo.com/freshairbrucewoo。
歡迎大家相互交流,共同提高技術。
第二節、rpc客戶端實現原理及代碼分析
rpc客戶端主要發起一個rpc請求,執行完rpc請求以後就退出rpc,下面分析客戶端rpc請求建立的整個過程。Rpc客戶端請求建立的第一步是執行cli_rpc_init函數,主要實現代碼如下:
this = THIS;//取得本線程的xlator列表
cli_rpc_prog = &cli_prog;//設置rpc調用過程集合(許多函數)
options = dict_new ();//新建一個字典數據結構用於存放選項信息
ret = dict_set_str (options, "remote-host", state->remote_host);//設置host
if (state->remote_port)
port = state->remote_port;
ret = dict_set_int32 (options, "remote-port", port);//設置端口號
ret = dict_set_str (options, "transport.address-family", "inet");//設置協議族爲inet
rpc = rpc_clnt_new (options, this->ctx, this->name);//新建一個rpc客戶端對象
ret = rpc_clnt_register_notify (rpc, cli_rpc_notify, this);//註冊rpc請求通知函數
rpc_clnt_start (rpc);//開始rpc
這段代碼其實是glusterfs客戶端程序啓動時建立rpc請求的初始化過程函數,真正獨立開始建立一個rpc請求的過程是從函數rpc_clnt_new開始的,下面就分析這個函數的功能,先看主要代碼:
rpc = GF_CALLOC (1, sizeof (*rpc), gf_common_mt_rpcclnt_t);//爲客戶端rpc對象分配內存
pthread_mutex_init (&rpc->lock, NULL);//初始化鎖
rpc->ctx = ctx;//屬於哪一個ctx
//新建請求內存池
rpc->reqpool = mem_pool_new (struct rpc_req, RPC_CLNT_DEFAULT_REQUEST_COUNT);
//保存幀數據的內存池
rpc->saved_frames_pool = mem_pool_new (struct saved_frame,
RPC_CLNT_DEFAULT_REQUEST_COUNT);
ret = rpc_clnt_connection_init (rpc, ctx, options, name);//初始化rpc請求連接
rpc = rpc_clnt_ref (rpc);//客戶端對象引用計數加1
INIT_LIST_HEAD (&rpc->programs);//初始化程序集的鏈表
rpc客戶端發送請求也需要裝載相應的協議庫,它主要使用協議庫裏面的初始化函數和鏈接函數,上面代碼中rpc_clnt_connection_init函數主要完成協議庫裝載功能和鏈接選項的一些初始化功能,具體實現的主要代碼如下:
pthread_mutex_init (&clnt->conn.lock, NULL);//初始化鎖
conn->trans = rpc_transport_load (ctx, options, name);//裝載協議庫並執行初始化init
rpc_transport_ref (conn->trans);//增加傳輸層的引用計數
conn->rpc_clnt = clnt;//連接對象屬於哪一個客戶端對象
ret = rpc_transport_register_notify (conn->trans, rpc_clnt_notify, conn);//註冊通知函數
conn->saved_frames = saved_frames_new ();//新建一個保存幀數據的對象
通過上面代碼執行以後基本的初始化工作已經完畢,下一步就是建立於rpc服務的鏈接,此功能在函數rpc_clnt_start中實現,也就是在所有初始化工作完成以後調用此函數發起鏈接,主要代碼如下:
rpc_clnt_reconnect (conn->trans);
繼續跟蹤函數rpc_clnt_reconnect:
pthread_mutex_lock (&conn->lock);//初始化鎖
{
if (conn->reconnect)//如果重新鏈接正在進行
gf_timer_call_cancel (clnt->ctx, conn->reconnect);//取消正在的鏈接
conn->reconnect = 0;//初始化爲0
if (conn->connected == 0) {//還沒有完成鏈接
tv.tv_sec = 3;//時間三秒
//發起傳輸層的鏈接
ret = rpc_transport_connect (trans, conn->config.remote_port);
//設置重鏈接對象
conn->reconnect = gf_timer_call_after (clnt->ctx, tv,
rpc_clnt_reconnect, trans);
}
}
pthread_mutex_unlock (&conn->lock);//解鎖
if ((ret == -1) && (errno != EINPROGRESS) && (clnt->notifyfn)) {
//建立鏈接失敗就通知rpc客戶端對象(調用通知函數)
clnt->notifyfn (clnt, clnt->mydata, RPC_CLNT_DISCONNECT, NULL);
}
真正的鏈接是在具體的協議中的鏈接函數中執行,下面以tcp爲例,看看它的鏈接函數的實現,主要代碼如下:
//得到客戶端協議族相關信息
ret = socket_client_get_remote_sockaddr (this, SA (&sockaddr), &sockaddr_len, &sa_family)
if (port > 0)
((struct sockaddr_in *) (&sockaddr))->sin_port = htons (port);//端口字節序轉換
pthread_mutex_lock (&priv->lock);
{
memcpy (&this->peerinfo.sockaddr, &sockaddr, sockaddr_len);//賦值sockaddr信息
this->peerinfo.sockaddr_len = sockaddr_len;//地址長度保存
priv->sock = socket (sa_family, SOCK_STREAM, 0);//創建socket
setsockopt (priv->sock, SOL_SOCKET, SO_RCVBUF, &priv->windowsize,
sizeof (priv->windowsize)) < 0) ;//設置接收的系統緩衝區
setsockopt (priv->sock, SOL_SOCKET, SO_SNDBUF, &priv->windowsize,
sizeof (priv->windowsize)) < 0);//發送緩衝區設置
if (priv->nodelay) {
ret = __socket_nodelay (priv->sock);//設置是否延遲發送(等待一個完整的)
}
if (!priv->bio) {
ret = __socket_nonblock (priv->sock);//設置非阻塞
}
if (priv->keepalive) {
ret = __socket_keepalive (priv->sock, priv->keepaliveintvl,
priv->keepaliveidle);//保存鏈接活動
}
SA (&this->myinfo.sockaddr)->sa_family =
SA (&this->peerinfo.sockaddr)->sa_family;//保存協議族信息
ret = client_bind (this, SA (&this->myinfo.sockaddr),//根據協議族做適當的綁定
&this->myinfo.sockaddr_len, priv->sock);
ret = connect (priv->sock, SA (&this->peerinfo.sockaddr),//發起鏈接請求
this->peerinfo.sockaddr_len);
priv->connected = 0;
rpc_transport_ref (this);//傳輸對象引用加1
priv->idx = event_register (ctx->event_pool, priv->sock,//註冊epoll讀寫事件
socket_event_handler, this, 1, 1);
}
pthread_mutex_unlock (&priv->lock);//解鎖
整個鏈接過程的建立需要注意一點的就是會根據協議族的類型做適當的綁定,當發起鏈接以後就開始註冊各種讀寫事件。處理這些事件發生的函數是socket_event_handler,主要代碼如下:
THIS = this->xl;//取得xlator
priv = this->private;//取得私有數據
pthread_mutex_lock (&priv->lock);//加鎖
{
priv->idx = idx;//取得索引下表(對應的socket)
}
pthread_mutex_unlock (&priv->lock);
if (!priv->connected) {//如果鏈接還沒有建立先完成鏈接的建立
ret = socket_connect_finish (this);//完成鏈接建立
}
if (!ret && poll_out) {//可寫事件處理
ret = socket_event_poll_out (this);
}
if (!ret && poll_in) {//可讀事件處理
ret = socket_event_poll_in (this);
}
到此客戶端rpc請求過程完全建立,當真正的發送一個rpc請求的時候就會響應相應的epoll的寫事件,把包裝好的數據幀發送到rpc服務器端,rpc客戶端也會通過可讀事件來接收rpc服務器端的響應信息。
總結:同rpc服務器端一眼,rpc客戶端的建立也是比較複雜的過程,還是通過流程圖加描述來展示一下整個rpc客戶端的初始化過程,圖如下: