這部分基本和上一節(jié)一樣,不過上一節(jié)中 RPC 是通過 Named Pipe 調(diào)用的,這里我們再試一下 TCP 的方式。
代碼大部分都是相同的, IDL 接口不用變(無論是通過什么方式 RPC,接口都是與之無關(guān)的)。
服務(wù)端要換成 TCP 的方式:
---------------------------------
int main(int argc,char * argv[])
{
// 用TCP 方式作為RPC 的通道。綁定端口13521。
RpcServerUseProtseqEp(
(unsigned char *)"ncacn_ip_tcp",
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
(unsigned char *)"13521",
NULL);
// 注意:從Windows XP SP2 開始,增強了安全性的要求,如果用 RpcServerRegisterIf() 注冊
// 接口,客戶端調(diào)用時會出現(xiàn) RpcExceptionCode() == 5,即Access Denied 的錯誤. 因此,必
// 須用 RpcServerRegisterIfEx 帶 RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH 標(biāo)志允許客戶端直
// 接調(diào)用。
// RpcServerRegisterIf(HelloWorld_v1_0_s_ifspec, NULL, NULL);
RpcServerRegisterIfEx(
HelloWorld_v1_0_s_ifspec, // Interface to register.
NULL,
NULL, // Use the MIDL generated entry-point vector.
RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH,
0,
NULL);
// 后面都相同
...
return 0;
}
客戶端的調(diào)用方式也要換:
---------------------------------
int main(int argc, char * argv[])
{
// 前面都相同
...
// 用 TCP 方式作為 RPC 的通道。服務(wù)器端口 13521。第3個
// 參數(shù) NetworkAddr 如果取 NULL,那么就是連接本機服務(wù),
// 也可以取IP, 域名, servername 等
RpcStringBindingCompose(
NULL,
(unsigned char*)"ncacn_ip_tcp",
(unsigned char*)"localhost" /*NULL*/,
(unsigned char*)"13521",
NULL,
&pszStringBinding
);
// 后面都相同
...
}
別的地方都是一樣的。
我們在上一節(jié)的基礎(chǔ)上,討論如何實現(xiàn)異步的 RPC 調(diào)用。前兩節(jié)演示的函數(shù)調(diào)用都是同步的,即調(diào)用函數(shù) Hello() 時,
客戶端將阻塞住直到服務(wù)端的 Hello() 函數(shù)返回。如果服務(wù)端函數(shù)需要進(jìn)行一些費時的操作,例如復(fù)雜的計算、查詢,
客戶端只能一直阻塞在那里。這種情況下,我們可以使用異步的 RPC 提高客戶端的性能。
異步的RPC是通過配置文件(.acf)來啟用的:
--------------------------------------------
Hello.acf:
[
implicit_handle(handle_t HelloWorld_Binding)
]
interface HelloWorld
{
[async] Hello(); // 增加了 [async] 表明這是異步調(diào)用
}
原來的接口 HelloWorld 有兩個方法,Hello() 和 Shutdown(),Shutdown() 我們?nèi)匀蛔屗峭秸{(diào)用,所以在.acf文
件中不用列出。IDL 接口文件還是可以不用修改。
服務(wù)端的代碼 server.c 中的 Hello() 要改成下面的樣子:
------------------------------------------------------
void Hello(PRPC_ASYNC_STATE rpcAsyncHandle, const unsigned char * psz)
{
// 模擬一個長時間的操作
printf("Sleep 5 seconds.../n");
Sleep(5000);
printf("%s/n", psz);
// 表明調(diào)用已經(jīng)完成
RpcAsyncCompleteCall(rpcAsyncHandle, NULL);
}
服務(wù)端的其它代碼不用修改。
客戶端client.c中的調(diào)用方式也要換:
---------------------------------
int main(int argc, char * argv[])
{
// 前面都相同
...
// 下面是調(diào)用服務(wù)端的函數(shù)
RpcTryExcept
{
if ( _stricmp(argv[1], "SHUTDOWN") == 0 )
{
Shutdown();
}
else
{
// 初始化異步調(diào)用
RPC_ASYNC_STATE async;
RpcAsyncInitializeHandle( &async, sizeof(async) );
async.UserInfo = NULL;
async.NotificationType = RpcNotificationTypeNone;
// 本函數(shù)能立即返回
Hello( &async, (unsigned char*)argv[1]);
// 查詢調(diào)用的狀態(tài)
while ( RpcAsyncGetCallStatus(&async) == RPC_S_ASYNC_CALL_PENDING )
{
printf("Call Hello() pending, wait 1s.../n");
Sleep(1000);
}
// 通知調(diào)用已經(jīng)完成
RpcAsyncCompleteCall( &async, NULL );
}
}
RpcExcept(1)
{
printf( "RPC Exception %d/n", RpcExceptionCode() );
}
RpcEndExcept
// 后面都相同
...
}
這樣客戶端就實現(xiàn)了異步調(diào)用!
這節(jié)我們來談?wù)?Windows NT 下 RPC 的高性能模式 - LPC。
很多 Windows 編程入門的書里面講 Windows 的進(jìn)程間通信,都會講 WM_COPYDATA,講匿名管道,講命名管道,講共享內(nèi)存等等,
但是很少有講 RPC 的,為什么呢?因為 RPC 看名字,就叫“Remote Procedure Call”,一看就是給分布式系統(tǒng)通信用的,雖然
也可以作為本機進(jìn)程間通信用,但是性能上總是讓人懷疑。所以很多人設(shè)計的進(jìn)程間通信模型,都是用 WM_COPYDATA,或者管道,
或者干脆共享內(nèi)存,相當(dāng)于自己造輪子,一切從頭做起。但 RPC 確實好用啊,調(diào)用起來就像調(diào)用庫函數(shù)一樣,通信的細(xì)節(jié)全給你
封裝起來了。那 RPC 有沒有性能好一點的模式呢?這就是下面要講的 LPC 模式了。
LPC(Local Procedure Call)是 Windows NT 內(nèi)部的高性能的通信模式。它是在內(nèi)核中實現(xiàn)的,主要用于 Win32 子系統(tǒng)內(nèi)部的
通信,比如 csrss, lsass 都大量的用到了 LPC。在前面演示的代碼中,只需要改一行代碼,我們就可使用 LPC 了,其實 RPC 就
是內(nèi)部使用 LPC 來進(jìn)行通信,性能大大提高。
服務(wù)端代碼:
server.c
--------------
// 用LPC 方式通信
RpcServerUseProtseqEp(
(unsigned char *)"ncalrpc",
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
(unsigned char *)"AppName",
NULL);
客戶端代碼:
client.c
--------------
// 用LPC 方式通信
// 第3 個參數(shù)NetworkAddr 只能取NULL
RpcStringBindingCompose(
NULL,
(unsigned char*)"ncalrpc",
NULL, (unsigned char*)"AppName",
NULL,
&pszStringBinding );