RCF教程
1. Getting started
1.1 Hello World
遵循编程语言的传统,让我们从一个“Hello World”示例开始。我们将编写一个向server
发送字符串的client
,以及一个将字符串打印到标准输出的server
。
这是server
:
#include <RCF/RCF.hpp>
#include <iostream>
RCF_BEGIN(I_HelloWorld, “I_HelloWorld”)
RCF_METHOD_V1(void, Print, const std::string &)
RCF_END(I_HelloWorld)
class HelloWorldImpl{
public:
void Print(const std::string & s){
std::cout << "I_HelloWorld service: " << s << std::endl;
}
};
int main(){
RCF::RcfInitDeinit rcfInit;
HelloWorldImpl helloWorld;
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
server.bind<I_HelloWorld>(helloWorld);
server.start();
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"Press Enter to exit..."</span> <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
std<span class="token operator">::</span>cin<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
和client
:
#include <iostream>
#include <RCF/RCF.hpp>
RCF_BEGIN(I_HelloWorld, “I_HelloWorld”)
RCF_METHOD_V1(void, Print, const std::string &)
RCF_END(I_HelloWorld)
int main(){
RCF::RcfInitDeinit rcfInit;
std::cout << “Calling the I_HelloWorld Print() method.” << std::endl;
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
client.Print(“Hello World”);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
使用的transport
是一个TCP连接,server
监听本地主机(127.0.0.1)接口的50001端口。
在运行这段代码之前,我们需要构建它。要构建RCF,只需将src/RCF/RCF.cpp
源文件编译到应用程序中。RCF要求Boost库是可用的,因此你需要下载Boost
,1.35.0以后的任何版本都可以。您不需要构建任何Boost库,因为RCF在其默认配置中只使用来自Boost的头文件。
下面是如何使用两个常见的编译器工具集来构建上面的server
代码。
1.2 How to build - Visual Studio 2010
- 在Visual Studio IDE中,选择
New Project -> Visual c++ -> Win32 -> Win32 Console Application
; - 在
Application Settings
对话框中,选中Empty Project
复选框; - 在新建Project的
Project Properties
对话框中,选择C/C++ -> General -> Additional Include Directories
; - 为Boost和RCF添加include路径,例如
C:\boost_1_49_0
和C:\RCF\include
; - 向Project中添加一个Server.cpp文件,并复制粘贴上面的代码;
- 将
RCF\src\RCF\RCF.cpp
添加到Project中; - 选择
Build -> Build Solution
。
1.3 How to build - gcc
- 创建一个Server.cpp文件并将上面的代码复制粘贴到其中;
- 在同一目录下,运行以下命令:
g++ Server.cpp /path/to/RCF/src/RCF/RCF.cpp -I/path/to/boost_1_49_0 -I/path/to/RCF/include -lpthread -ldl -oServer
- 1
/path/to/boost_1_49_0
和/path/to/RCF
。
1.4 Running the server and client
让我们启动Sever:
c:\Projects\RcfSample\Debug>Server.exe
Press Enter to exit...
- 1
- 2
然后启动Client:
c:\Projects\RcfSample\Debug>Client.exe
Calling the I_HelloWorld Print() method.
- 1
- 2
在服务器窗口,你现在应该可以看到:
c:\Projects\RcfSample\Debug>Server.exe
Press Enter to exit...
I_HelloWorld service: Hello World
- 1
- 2
- 3
为了简化本教程的其余部分,我们将重写Hello World示例,以便客户机和服务器在单个进程中运行:
#include <iostream>
#include <RCF/RCF.hpp>
RCF_BEGIN(I_HelloWorld, “I_HelloWorld”)
RCF_METHOD_V1(void, Print, const std::string &)
RCF_END(I_HelloWorld)
class HelloWorldImpl{
public:
void Print(const std::string & s){
std::cout << "I_HelloWorld service: " << s << std::endl;
}
};
int main(){
RCF::RcfInitDeinit rcfInit;
HelloWorldImpl helloWorld<span class="token punctuation">;</span>
RCF<span class="token operator">::</span>RcfServer <span class="token function">server</span><span class="token punctuation">(</span> RCF<span class="token operator">::</span><span class="token function">TcpEndpoint</span><span class="token punctuation">(</span><span class="token number">50001</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span>bind<span class="token operator"><</span>I_HelloWorld<span class="token operator">></span><span class="token punctuation">(</span>helloWorld<span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"Calling the I_HelloWorld Print() method."</span> <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
RcfClient<span class="token operator"><</span>I_HelloWorld<span class="token operator">></span> <span class="token function">client</span><span class="token punctuation">(</span> RCF<span class="token operator">::</span><span class="token function">TcpEndpoint</span><span class="token punctuation">(</span><span class="token number">50001</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
client<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span><span class="token string">"Hello World"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
运行这个程序产生的输出:
Calling the I_HelloWorld Print() method.
I_HelloWorld service: Hello World
- 1
- 2
本教程的其余部分将以这个示例为基础,来演示RCF的一些基本特性。
2. Interfaces and implementations
RCF允许您直接在代码中定义远程接口。在上面的例子中,我们定义了I_HelloWorld
接口:
RCF_BEGIN(I_HelloWorld, "I_HelloWorld")
RCF_METHOD_V1(void, Print, const std::string &)
RCF_END(I_HelloWorld)
- 1
- 2
- 3
RCF_BEGIN()
、RCF_METHOD_xx()
和RCF_END()
是宏,用于为远程调用生成client和server存根。
RCF_BEGIN()
开始接口定义,并定义接口的编译时标识符(I_HelloWorld
)和接口的运行时标识符(“I_HelloWorld”
)。RCF_METHOD_xx()
——RCF_METHOD_xx()
宏定义远程方法。RCF_METHOD_V1()
定义了一个远程方法,它接受一个参数(在本例中是const std::string &
),并返回void
类型。RCF_METHOD_R2()
定义了一个带有两个参数和一个非void返回值的远程方法,以此类推。RCF_METHOD_xx()
宏是为接受最多15个参数的void和非void远程调用定义的。RCF_END()
结束接口定义。
在server上,我们将接口绑定到一个服务对象上:
server.bind<I_HelloWorld>(helloWorld);
- 1
让我们向I_HelloWorld
接口添加更多的远程方法。我们将添加打印一个字符串列表的方法,并返回打印的字符数:
// Serialization code for std::vector<>.
#include <SF/vector.hpp>
RCF_BEGIN(I_HelloWorld, “I_HelloWorld”)
RCF_METHOD_V1(void, Print, const std::string &)
RCF_METHOD_R1(int, Print, const std::vector<std::string> &)
RCF_METHOD_V2(void, Print, const std::vector<std::string> &, int &)
RCF_END(I_HelloWorld)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
远程调用可以通过一个返回值(与第二个Print()
方法一样)返回参数,也可以通过非const引用
参数(与第三个Print()
方法一样)返回参数。
将这些方法添加到I_HelloWorld
接口之后,我们还需要在HelloWorldImpl
服务对象中实现它们:
class HelloWorldImpl{ public: void Print(const std::string & s){ std::cout << "I_HelloWorld service: " << s << std::endl; }
<span class="token keyword">int</span> <span class="token function">Print</span><span class="token punctuation">(</span><span class="token keyword">const</span> std<span class="token operator">::</span>vector<span class="token operator"><</span>std<span class="token operator">::</span>string<span class="token operator">></span> <span class="token operator">&</span> v<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">int</span> howManyChars <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>std<span class="token operator">::</span>size_t i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator"><</span>v<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span><span class="token punctuation">{</span> std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"I_HelloWorld service: "</span> <span class="token operator"><<</span> v<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span> howManyChars <span class="token operator">+</span><span class="token operator">=</span> v<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> howManyChars<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">void</span> <span class="token function">Print</span><span class="token punctuation">(</span><span class="token keyword">const</span> std<span class="token operator">::</span>vector<span class="token operator"><</span>std<span class="token operator">::</span>string<span class="token operator">></span> <span class="token operator">&</span> v<span class="token punctuation">,</span> <span class="token keyword">int</span> <span class="token operator">&</span> howManyChars<span class="token punctuation">)</span><span class="token punctuation">{</span> howManyChars <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span>std<span class="token operator">::</span>size_t i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator"><</span>v<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span><span class="token punctuation">{</span> std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"I_HelloWorld service: "</span> <span class="token operator"><<</span> v<span class="token punctuation">[</span>i<span class="token punctuation">]</span> <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span> howManyChars <span class="token operator">+</span><span class="token operator">=</span> v<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span>
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
注意,HelloWorldImpl
不是派生类。代替使用虚函数,RCF使用基于模板的静态多态性绑定接口到实现上。
下面是调用新的Print()
方法的示例client代码:
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
std::vector<std::string> stringsToPrint;
stringsToPrint.push_back(“AAA”);
stringsToPrint.push_back(“BBB”);
stringsToPrint.push_back(“CCC”);
// 远程调用通过返回值返回参数。
int howManyChars = client.Print(stringsToPrint);
// 远程调用通过非const引用参数返回参数。
client.Print(stringsToPrint, howManyChars);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
输出为:
I_HelloWorld service: AAA
I_HelloWorld service: BBB
I_HelloWorld service: CCC
I_HelloWorld service: AAA
I_HelloWorld service: BBB
I_HelloWorld service: CCC
- 1
- 2
- 3
- 4
- 5
- 6
3. Error handling
如果远程调用没有完成,RCF将抛出一个异常,其中包含描述错误条件的错误消息。让我们用try/catch包装远程调用:
try
{
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
client.Print("Hello World");
}
catch(const RCF::Exception & e)
{
std::cout << "Error: " << e.getErrorString() << std::endl;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
我们可以通过注释掉RcfServer::start()调用来模拟服务器宕机:
HelloWorldImpl helloWorld;
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
server.bind<I_HelloWorld>(helloWorld);
//server.start();
- 1
- 2
- 3
- 4
运行客户端,我们现在得到:
Error: Client connection to 127.0.0.1:50001 timed out after 2000 ms (server not started?).
- 1
RCF抛出的所有异常都派生自RCF::Exception。Exception包含关于错误的各种描述和上下文信息。。您可以调用RCF::Exception::getErrorId()来检索错误代码,调用RCF::Exception::getErrorString()来检索错误的英文翻译,包括任何与错误相关的参数。
如果远程调用的服务器实现抛出异常,则RcfServer将捕获该异常并将其返回给客户机,并将其作为RCF::RemoteException抛出。例如,如果我们用以下服务实现启动服务器:
class HelloWorldImpl
{
public:
void Print(const std::string & s)
{
throw std::runtime_error("Print() service is unavailable at this time.");
std::cout << "I_HelloWorld service: " << s << std::endl;
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
,此时客户端将输出:
Error: Server-side user exception. Exception type: class std::runtime_error. Exception message: "Print() service is unavailable at this time.".
- 1
4. Client stubs
远程调用总是通过客户机存根(RCF::ClientStub)进行的。您可以通过调用RcfClient<>::getClientStub()来访问RcfClient<>的客户端存根。客户端存根包含许多影响远程调用执行方式的设置。
两个最重要的设置是连接超时和远程调用超时。连接超时决定RCF在尝试建立到服务器的网络连接时将等待多长时间。远程调用超时决定RCF等待远程调用响应从服务器返回的时间。
要更改这些设置,请调用ClientStub::setConnectionTimeoutMs()和ClientStub::setRemoteCallTimeoutMs()函数:
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
// 5 second timeout when establishing network connection.
client.getClientStub().setConnectTimeoutMs(5*1000);
// 60 second timeout when waiting for remote call response from the server.
client.getClientStub().setRemoteCallTimeoutMs(60*1000);
client.Print(“Hello World”);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
有关客户端存根的更多信息,请参见远程调用-客户端(Remote calls - Client-side)。
5. Server sessions
在服务器端,RCF为每个到server的client连接维护一个会话(RCF::RcfSession
)。一个client连接的RcfSession可通过RCF::getCurrentSession()
提供给服务器端代码。您可以使用RcfSession
来维护特定于一个特定client连接的应用程序数据。可以通过调用RcfSession::createSessionObject<>()
或RcfSession::getSessionObject<>()
将任意C++对象存储在一个会话中。
例如,要将一个HelloWorldSession
对象与每个client连接关联到I_HelloWorld
接口,该接口跟踪在连接上发出的调用的次数:
class HelloWorldSession{ public: HelloWorldSession() : mCallCount(0){ std::cout << "Created HelloWorldSession object." << std::endl; }
<span class="token operator">~</span><span class="token function">HelloWorldSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span> std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"Destroyed HelloWorldSession object."</span> <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span> <span class="token punctuation">}</span> std<span class="token operator">::</span>size_t mCallCount<span class="token punctuation">;</span>
};
class HelloWorldImpl{
public:
void Print(const std::string & s){
RCF::RcfSession & session = RCF::getCurrentRcfSession();
// 如果会话对象不存在,则创建它。
HelloWorldSession & hwSession = session.getSessionObject<HelloWorldSession>(true);
++hwSession.mCallCount;
std::cout << "I_HelloWorld service: " << s << std::endl;
std::cout << "I_HelloWorld service: " << "Total calls on this connection so far: " << hwSession.mCallCount << std::endl;
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
不同的RcfClient<>
实例将具有不同的到server的连接。这里我们从两个RcfClient<>
实例中调用Print()
三次:
for (std::size_t i=0; i<2; ++i){
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
client.Print("Hello World");
client.Print("Hello World");
client.Print("Hello World");
}
// 稍等一会儿,以便server有时间销毁最后一个会话。
RCF::sleepMs(1000);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
,产生如下输出:
Created HelloWorldSession object.
I_HelloWorld service: Hello World
I_HelloWorld service: Total calls on this connection so far: 1
I_HelloWorld service: Hello World
I_HelloWorld service: Total calls on this connection so far: 2
I_HelloWorld service: Hello World
I_HelloWorld service: Total calls on this connection so far: 3
Destroyed HelloWorldSession object.
Created HelloWorldSession object.
I_HelloWorld service: Hello World
I_HelloWorld service: Total calls on this connection so far: 1
I_HelloWorld service: Hello World
I_HelloWorld service: Total calls on this connection so far: 2
I_HelloWorld service: Hello World
I_HelloWorld service: Total calls on this connection so far: 3
Destroyed HelloWorldSession object.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
当客户机连接关闭时,将销毁服务器会话和任何关联的会话对象。
有关服务器会话的更多信息,请参见远程调用-服务器端。
6. Transports
RCF使更改远程调用的底层传输变得很容易。传输层由传递给RcfServer和RcfClient<>的端点参数决定。到目前为止,我们一直使用TcpEndpoint来指定TCP传输。
默认情况下,当指定只有端口号的TcpEndpoint时,RCF将使用127.0.0.1作为IP地址。所以下面两个片段是等价的:
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
- 1
RCF::RcfServer server( RCF::TcpEndpoint("127.0.0.1", 50001) );
- 1
,详情如下:
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
- 1
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint("127.0.0.1", 50001) );
- 1
127.0.0.1是IPv4回送地址。监听127.0.0.1的服务器只对与服务器位于同一台机器上的客户机可用。您可能希望跨网络运行客户机,在这种情况下,服务器需要监听外部可见的网络地址。最简单的方法是指定0.0.0.0(用于IPv4),或者::0(用于IPv6),这将使服务器监听所有可用的网络接口:
// Server-side.
RCF::RcfServer server( RCF::TcpEndpoint("0.0.0.0", 50001) );
// Client-side.
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(“Server123”, 50001) );
- 1
- 2
- 3
- 4
- 5
RCF还支持许多其他端点类型。要在UDP上运行服务器和客户端,请使用UdpEndpoint:
// Server-side.
RCF::RcfServer server( RCF::UdpEndpoint("0.0.0.0", 50001) );
// Client-side.
RcfClient<I_HelloWorld> client( RCF::UdpEndpoint(“Server123”, 50001) );
- 1
- 2
- 3
- 4
- 5
要在命名管道上运行服务器和客户机,请使用NamedPipeEndpoint:
// Server-side.
RCF::RcfServer server( RCF::Win32NamedPipeEndpoint("MyPipe") );
// Client-side.
RcfClient<I_HelloWorld> client( RCF::Win32NamedPipeEndpoint(“MyPipe”) );
- 1
- 2
- 3
- 4
- 5
NamedPipeEndpoint映射到Windows系统上的Windows命名管道,以及基于UNIX的系统上的UNIX本地域套接字。
使用UDP进行双向(请求/响应)消息传递通常是不实际的,因为UDP的不可靠语义意味着消息可能不会被传递,或者可能被无序地传递。UDP在单向消息传递场景中非常有用,在单向消息传递场景中,服务器应用程序逻辑对丢失或无序到达的消息具有弹性。
RCF支持通过HTTP和HTTPS协议进行远程调用的隧道传输。HttpEndpoint用于配置HTTP上的隧道。下面是一个客户端通过第三方HTTP代理远程调用服务器的例子:
// Server-side.
HelloWorldImpl helloWorldImpl;
RCF::RcfServer server( RCF::HttpEndpoint("0.0.0.0", 80) );
server.bind<I_HelloWorld>(helloWorldImpl);
server.start();
// Client-side.
// 该客户机将通过在proxy.acme.com:8080端口的HTTP代理连接到server1.acme.com。
RcfClient<I_HelloWorld> client( RCF::HttpEndpoint(“server1.acme.com”, 80) );
client.getClientStub().setHttpProxy(“proxy.acme.com”);
client.getClientStub().setHttpProxyPort(8080);
client.Print(“Hello World”);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
类似地,RCF::HttpsEndpoint可用于配置HTTPS上的tunneling:
// Server-side.
HelloWorldImpl helloWorldImpl;
RCF::RcfServer server( RCF::HttpsEndpoint("0.0.0.0", 443) );
server.bind<I_HelloWorld>(helloWorldImpl);
RCF::CertificatePtr serverCertPtr( new RCF::PfxCertificate("path/to/certificate.p12", "password", "CertificateName") );
server.setCertificate(serverCertPtr);
server.start();
// Client-side.
// 该客户机将通过在proxy.acme.com:8080端口的HTTP代理连接到server1.acme.com。
RcfClient<I_HelloWorld> client( RCF::HttpsEndpoint(“server1.acme.com”, 443) );
client.getClientStub().setHttpProxy(“proxy.acme.com”);
client.getClientStub().setHttpProxyPort(8080);
client.Print(“Hello World”);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
HTTPS的证书和证书验证配置为SSL传输协议(请参阅下面的传输协议)。
可以为一个RcfServer配置多个传输。例如,要配置在端口50001上同时接受IPv4和IPv6连接的服务器:
RCF::RcfServer server;
server.addEndpoint( RCF::TcpEndpoint("0.0.0.0", 50001) );
server.addEndpoint( RCF::TcpEndpoint("::0", 50001) );
server.start();
- 1
- 2
- 3
- 4
在某些平台上,底层网络堆栈允许您指定::0
来监听IPv4和IPv6接口,在这种情况下,这就足够了:
RCF::RcfServer server;
server.addEndpoint( RCF::TcpEndpoint("::0", 50001) );
server.start();
- 1
- 2
- 3
这是一个服务器接受连接TCP, UDP和命名管道:
RCF::RcfServer server;
server.addEndpoint( RCF::TcpEndpoint("::0", 50001) );
server.addEndpoint( RCF::UdpEndpoint("::0", 50002) );
server.addEndpoint( RCF::Win32NamedPipeEndpoint("MyPipe") );
server.start();
- 1
- 2
- 3
- 4
- 5
有关transports的更多信息,请参见Transports。
7. Transport protocols
传输协议在传输之上分层,并提供身份验证和加密。
RCF支持以下传输协议:
NTLM和Kerberos传输协议只在Windows上受支持,而SSL传输协议在所有平台上都受支持。
RcfServer可以配置为需要某些传输协议:
std::vector<RCF::TransportProtocol> protocols;
protocols.push_back(RCF::Tp_Ntlm);
protocols.push_back(RCF::Tp_Kerberos);
server.setSupportedTransportProtocols(protocols);
- 1
- 2
- 3
- 4
在客户端,ClientStub::setTransportProtocol()用于配置传输协议。例如,要在客户端连接上使用NTLM协议:
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);
client.Print("Hello World");
- 1
- 2
- 3
在本例中,客户机将使用登录用户的隐式凭证向服务器进行身份验证,并使用NTLM协议加密其连接。
要提供显式凭据,请使用ClientStub::setUsername()和ClientStub::setPassword():
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);
client.getClientStub().setUsername("SomeDomain\\Joe");
client.getClientStub().setPassword("JoesPassword");
client.Print("Hello World");
- 1
- 2
- 3
- 4
- 5
Kerberos协议也可以类似地配置。Kerberos协议要求客户机提供服务器的服务主体名称(SPN)。为此,可以调用ClientStub::setKerberosSpn():
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
client.getClientStub().setTransportProtocol(RCF::Tp_Kerberos);
client.getClientStub().setKerberosSpn("SomeDomain\\ServerAccount");
client.Print("Hello World");
- 1
- 2
- 3
- 4
如果客户端试图在不配置服务器所需的传输协议的情况下调用Print(),他们将得到一个错误:
try{
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
client.Print("Hello World");
} catch(const RCF::Exception & e) {
std::cout << "Error: " << e.getErrorString() << std::endl;
}
- 1
- 2
- 3
- 4
- 5
- 6
Error: Server requires one of the following transport protocols to be used: NTLM, Kerberos.
- 1
在Print()的服务器实现中,可以检索当前会话的传输协议。如果传输协议是NTLM或Kerberos,还可以检索客户机的用户名,并模拟客户机:
class HelloWorldImpl{ public: void Print(const std::string & s){ RCF::RcfSession & session = RCF::getCurrentRcfSession(); RCF::TransportProtocol protocol = session.getTransportProtocol();
<span class="token keyword">if</span> <span class="token punctuation">(</span> protocol <span class="token operator">==</span> RCF<span class="token operator">::</span>Tp_Ntlm <span class="token operator">||</span> protocol <span class="token operator">==</span> RCF<span class="token operator">::</span>Tp_Kerberos <span class="token punctuation">)</span><span class="token punctuation">{</span> std<span class="token operator">::</span>string clientUsername <span class="token operator">=</span> session<span class="token punctuation">.</span><span class="token function">getClientUsername</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> RCF<span class="token operator">::</span>SspiImpersonator <span class="token function">impersonator</span><span class="token punctuation">(</span>session<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 现在在客户端的Windows凭据下运行。</span> <span class="token comment">// ...</span> <span class="token comment">// 在我们退出作用域(scope)时模拟(Impersonation)结束。</span> <span class="token punctuation">}</span> std<span class="token operator">::</span>cout <span class="token operator"><<</span> s <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span> <span class="token punctuation">}</span>
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
RCF还支持使用SSL作为传输协议。RCF提供了两种SSL实现,一种基于OpenSSL,另一种基于Windows Schannel安全包。Windows Schannel实现在Windows上自动使用,而如果在构建RCF时定义了RCF_USE_OPENSSL,则使用OpenSSL实现。
启用SSL的服务器需要配置SSL证书。证书配置的机制因使用的实现而异。下面是一个使用Schannel SSL实现的例子:
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
RCF::CertificatePtr serverCertificatePtr( new RCF::PfxCertificate(
"C:\\ServerCert.p12",
"Password",
"CertificateName") );
server.setCertificate(serverCertificatePtr);
server.start();
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
client.getClientStub().setTransportProtocol(RCF::Tp_Ssl);
client.getClientStub().setEnableSchannelCertificateValidation(“localhost”);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
PfxCertificate用于从.pfx和.p12文件加载PKCS #12证书。RCF还提供了RCF::StoreCertificate类,用于从Windows证书存储中加载证书。
当使用基于openssl的SSL实现时,RCF::PemCertificate类用于从. PEM文件加载PEM证书。
RCF还支持远程调用的链路级压缩。压缩使用ClientStub::setEnableCompression()独立于传输协议进行配置。压缩阶段紧接在传输协议阶段之前应用。
下面是一个客户端通过HTTP代理连接到服务器的例子,使用NTLM进行身份验证和加密,并启用压缩:
RcfClient<I_HelloWorld> client( RCF::HttpEndpoint("server123.com", 80) );
client.getClientStub().setHttpProxy("proxy.mycompany.com");
client.getClientStub().setHttpProxyPort(8080);
client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);
client.getClientStub().setEnableCompression(true);
client.Print(“Hello World”);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
有关更多信息,请参见传输协议。
8. Server-side threading
默认情况下,RcfServer使用一个线程来处理传入的远程调用,因此远程调用是连续地分派的,一个接一个。
如果您有多个客户端,并且其中一些客户端正在进行花费大量时间才能完成的调用,那么这可能会成为一个问题。为了提高这些情况下的响应能力,可以将RcfServer配置为运行多个线程并并行分派远程调用。
要配置多线程RcfServer,请使用RcfServer::setThreadPool()将线程池分配给服务器。RCF线程池可以配置为使用固定数量的线程:
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
// 具有固定线程数(5)的线程池。
RCF::ThreadPoolPtr tpPtr( new RCF::ThreadPool(5) );
server.setThreadPool(tpPtr);
server.start();
- 1
- 2
- 3
- 4
- 5
,或随服务器负载变化的动态线程数:
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
// 具有不同数量线程(1到25个)的线程池。
RCF::ThreadPoolPtr tpPtr( new RCF::ThreadPool(1, 25) );
server.setThreadPool(tpPtr);
server.start();
- 1
- 2
- 3
- 4
- 5
通过RcfServer::setThreadPool()配置的线程池在该RcfServer的所有传输中共享。也可以使用I_ServerTransport::setThreadPool()为单个传输配置线程池:
RCF::RcfServer server;
RCF::ServerTransport & tcpTransport = server.addEndpoint(RCF::TcpEndpoint(50001));
RCF::ServerTransport & pipeTransport = server.addEndpoint(RCF::Win32NamedPipeEndpoint("MyPipe"));
// 最多有5个线程来服务TCP客户机的线程池。
RCF::ThreadPoolPtr tcpThreadPoolPtr( new RCF::ThreadPool(1, 5) );
tcpTransport.setThreadPool(tcpThreadPoolPtr);
// 使用单线程的线程池来服务命名管道客户端。
RCF::ThreadPoolPtr pipeThreadPoolPtr( new RCF::ThreadPool(1) );
pipeTransport.setThreadPool(pipeThreadPoolPtr);
server.start();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
9. Asynchronous remote calls
RCF支持客户端asynchronous remote call invocation和服务器端asynchronous remote call dispatching。
9.1 Asynchronous remote call invocation
在客户端,异步远程调用允许您避免阻塞正在进行远程调用的线程。异步远程调用在后台线程上完成,调用线程将在稍后调用完成时得到通知。
使用RCF::Future<>模板在RCF中实现异步远程调用。下面是一个等待异步调用完成的简单例子:
HelloWorldImpl helloWorld;
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
server.bind<I_HelloWorld>(helloWorld);
server.start();
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
RCF::Future<int> fRet;
// Asynchronous remote call.
fRet = client.Print(“Hello World”);
// Wait for the call to complete.
while (!fRet.ready()) RCF::sleepMs(1000);
// Check for errors.
std::auto_ptr<RCF::Exception> ePtr = client.getClientStub().getAsyncException();
if (ePtr.get()){
// Error handling.
// …
}else{
int howManyCharsPrinted = *fRet;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
远程调用是异步执行的,如果任何参数或返回值的类型是RCF::Future<>,或者如果RCF::AsyncOneway或RCF::AsyncTwoway指定为调用语义,则执行远程调用。一旦调用完成,RCF::Future<>实例就可以取消引用以检索返回值。如果调用完成时带有错误,则可以通过调用RCF::ClientStub::getAsyncException()检索错误。如果试图取消对相关RCF::Future<>实例的引用,也会抛出该错误。
不需要在调用线程上等待结果,我们可以为远程调用分配一个完成回调:
void onPrintCompleted(HelloWorldPtr clientPtr);
void onWaitCompleted(HelloWorldPtr clientPtr);
void onPrintCompleted(HelloWorldPtr clientPtr){
// Print() call completed. Wait for 10 seconds.
clientPtr<span class="token operator">-</span><span class="token operator">></span><span class="token function">getClientStub</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">wait</span><span class="token punctuation">(</span>
boost<span class="token operator">::</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token operator">&</span>onWaitCompleted<span class="token punctuation">,</span> clientPtr<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token number">10</span><span class="token operator">*</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
void onWaitCompleted(HelloWorldPtr clientPtr){
// 10 second wait completed. Make another Print() call.
clientPtr<span class="token operator">-</span><span class="token operator">></span><span class="token function">Print</span><span class="token punctuation">(</span>
RCF<span class="token operator">::</span><span class="token function">AsyncTwoway</span><span class="token punctuation">(</span> boost<span class="token operator">::</span><span class="token function">bind</span><span class="token punctuation">(</span>onPrintCompleted<span class="token punctuation">,</span> clientPtr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"Hello World"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
// Addresses to 50 servers.
std::vector<RCF::TcpEndpoint> servers(50);
// ...
// 创建到每个服务器的连接,并进行第一次调用。
std::vector<HelloWorldPtr> clients;
for (std::size_t i=0; i<50; ++i){
HelloWorldPtr clientPtr( new RcfClient<I_HelloWorld>(servers[i]) );
clients.push_back(clientPtr);
<span class="token comment">// Asynchronous remote call, with completion callback.</span>
clientPtr<span class="token operator">-</span><span class="token operator">></span><span class="token function">Print</span><span class="token punctuation">(</span>
RCF<span class="token operator">::</span><span class="token function">AsyncTwoway</span><span class="token punctuation">(</span> boost<span class="token operator">::</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token operator">&</span>onPrintCompleted<span class="token punctuation">,</span> clientPtr<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"Hello World"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
// 所有50台服务器现在每10秒调用一次。
// …
// 离开作用域时,所有客户机都将自动销毁。任何正在进行的远程调用都会自动取消。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
与使用同步远程调用阻塞50个线程不同,所有50个连接都由一个线程管理。对50台服务器的远程调用序列都在后台RCF线程上处理,当主线程离开作用域时,连接将自动销毁。
9.2 Asynchronous remote call dispatching
RCF还支持服务器端异步远程调用调度。当远程调用到达RcfServer时,它由服务器线程池中的一个线程处理。要在另一个线程上异步处理调用,可以使用RCF::RemoteCallContext<>模板来捕获远程调用的服务器端上下文,并对其进行排队,以便稍后处理。
下面是在服务器端代码中使用RemoteCallContext<>的示例。我们不是响应HelloWorldImpl::Print()中的Print()调用,而是将远程调用上下文传递给后台线程,以便每秒批量处理一次。后台线程使用RemoteCallContext::parameters()访问每个远程调用的参数,并使用RemoteCallContext::commit()将远程调用响应发送回客户机。
#include <RCF/RemoteCallContext.hpp>
RCF_BEGIN(I_HelloWorld, “I_HelloWorld”)
RCF_METHOD_R1(int, Print, const std::string &)
RCF_END(I_HelloWorld)
// I_HelloWorld servant object
class HelloWorldImpl{
public:
typedef RCF::RemoteCallContext<int, const std::string &> PrintCall;
<span class="token keyword">int</span> <span class="token function">Print</span><span class="token punctuation">(</span><span class="token keyword">const</span> std<span class="token operator">::</span>string <span class="token operator">&</span> s<span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">// Capture the remote call context and queue it in mPrintCalls.</span>
RCF<span class="token operator">::</span>Lock <span class="token function">lock</span><span class="token punctuation">(</span>mPrintCallsMutex<span class="token punctuation">)</span><span class="token punctuation">;</span>
mPrintCalls<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span> <span class="token function">PrintCall</span><span class="token punctuation">(</span>RCF<span class="token operator">::</span><span class="token function">getCurrentRcfSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Dummy return value.</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">HelloWorldImpl</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">// Start the asynchronous printing thread.</span>
mStopFlag <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
mPrintThreadPtr<span class="token punctuation">.</span><span class="token function">reset</span><span class="token punctuation">(</span> <span class="token keyword">new</span> RCF<span class="token operator">::</span><span class="token function">Thread</span><span class="token punctuation">(</span> boost<span class="token operator">::</span><span class="token function">bind</span><span class="token punctuation">(</span>
<span class="token operator">&</span>HelloWorldImpl<span class="token operator">::</span>processPrintCalls<span class="token punctuation">,</span>
<span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token operator">~</span><span class="token function">HelloWorldImpl</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">// Stop the asynchronous printing thread.</span>
mStopFlag <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
mPrintThreadPtr<span class="token operator">-</span><span class="token operator">></span><span class="token function">join</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
private:
// Queue of remote calls.
RCF::Mutex mPrintCallsMutex;
std::deque<PrintCall> mPrintCalls;
<span class="token comment">// Asynchronous printing thread.</span>
RCF<span class="token operator">::</span>ThreadPtr mPrintThreadPtr<span class="token punctuation">;</span>
<span class="token keyword">volatile</span> <span class="token keyword">bool</span> mStopFlag<span class="token punctuation">;</span>
<span class="token keyword">void</span> <span class="token function">processPrintCalls</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">// Once a second, process all queued Print() calls.</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token operator">!</span>mStopFlag<span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token function">Sleep</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Retrieve all queued print calls.</span>
std<span class="token operator">::</span>deque<span class="token operator"><</span>PrintCall<span class="token operator">></span> printCalls<span class="token punctuation">;</span><span class="token punctuation">{</span>
RCF<span class="token operator">::</span>Lock <span class="token function">lock</span><span class="token punctuation">(</span>mPrintCallsMutex<span class="token punctuation">)</span><span class="token punctuation">;</span>
printCalls<span class="token punctuation">.</span><span class="token function">swap</span><span class="token punctuation">(</span>mPrintCalls<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// Process them.</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span>std<span class="token operator">::</span>size_t i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator"><</span>printCalls<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span><span class="token punctuation">{</span>
PrintCall <span class="token operator">&</span> printCall <span class="token operator">=</span> printCalls<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> std<span class="token operator">::</span>string <span class="token operator">&</span> stringToPrint <span class="token operator">=</span> printCall<span class="token punctuation">.</span><span class="token function">parameters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>a1<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"I_HelloWorld service: "</span> <span class="token operator"><<</span> stringToPrint <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
printCall<span class="token punctuation">.</span><span class="token function">parameters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>r<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span> stringToPrint<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
printCall<span class="token punctuation">.</span><span class="token function">commit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
客户端代码不变:
int main() { RCF::RcfInitDeinit rcfInit;
HelloWorldImpl helloWorld<span class="token punctuation">;</span> RCF<span class="token operator">::</span>RcfServer <span class="token function">server</span><span class="token punctuation">(</span> RCF<span class="token operator">::</span><span class="token function">TcpEndpoint</span><span class="token punctuation">(</span><span class="token number">50001</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> server<span class="token punctuation">.</span>bind<span class="token operator"><</span>I_HelloWorld<span class="token operator">></span><span class="token punctuation">(</span>helloWorld<span class="token punctuation">)</span><span class="token punctuation">;</span> server<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> RcfClient<span class="token operator"><</span>I_HelloWorld<span class="token operator">></span> <span class="token function">client</span><span class="token punctuation">(</span> RCF<span class="token operator">::</span><span class="token function">TcpEndpoint</span><span class="token punctuation">(</span><span class="token number">50001</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">int</span> charsPrinted <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span><span class="token string">"Hello World"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
有关更多信息,请参见异步远程调用。
10. Publish/subscribe
RCF使设置发布/订阅提要变得很容易。例如,这里有一个发布者每秒钟发布一次“Hello World”:
RCF::RcfServer publishingServer( RCF::TcpEndpoint(50001) );
publishingServer.start();
// Start publishing.
typedef boost::shared_ptr< RCF::Publisher<I_HelloWorld> > HelloWorldPublisherPtr;
HelloWorldPublisherPtr pubPtr = publishingServer.createPublisher<I_HelloWorld>();
while (shouldContinue()){
Sleep(1000);
// Publish a Print() call to all currently connected subscribers.
pubPtr->publish().Print(“Hello World”);
}
// Close the publisher.
pubPtr->close();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
要创建一个对发布者的订阅:
// Start a subscriber.
RCF::RcfServer subscriptionServer(( RCF::TcpEndpoint() ));
subscriptionServer.start();
HelloWorldImpl helloWorld;
RCF::SubscriptionPtr subPtr = subscriptionServer.createSubscription<I_HelloWorld>(
helloWorld,
RCF::TcpEndpoint(50001));
// At this point Print() will be called on the helloWorld object once a second.
// …
// Close the subscription.
subPtr->close();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
发布者发出的每个调用都作为单向调用发送给所有订阅者。
有关发布/订阅消息传递的更多信息,请参见发布/订阅。
11. Callback connections
到目前为止,我们已经看到客户端对服务器进行远程调用。一旦客户端建立了连接,服务器也可以对客户端进行远程调用。为此,客户机启动自己的RcfServer,并调用RcfServer::createCallbackConnection():
// Client-side int main(){ RCF::RcfInitDeinit rcfInit; // Client needs a RcfServer to accept callback connections. RCF::RcfServer callbackServer(( RCF::TcpEndpoint() )); HelloWorldImpl helloWorld; callbackServer.bind<I_HelloWorld>(helloWorld); callbackServer.start();
<span class="token comment">// Establish client connection to server.</span> RcfClient<span class="token operator"><</span>I_HelloWorld<span class="token operator">></span> <span class="token function">client</span><span class="token punctuation">(</span> RCF<span class="token operator">::</span><span class="token function">TcpEndpoint</span><span class="token punctuation">(</span><span class="token number">50001</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create the callback connection.</span> RCF<span class="token operator">::</span><span class="token function">createCallbackConnection</span><span class="token punctuation">(</span>client<span class="token punctuation">,</span> callbackServer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Server can now call Print() on the helloWorld object.</span> <span class="token comment">// ...</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
在服务器端,RcfServer::setCallbackConnectionCb()用于在客户端创建回调连接后控制回调连接:
// Server-side
typedef boost::shared_ptr< RcfClient<I_HelloWorld> > HelloWorldPtr;
RCF::Mutex gCallbackClientsMutex;
std::vector< HelloWorldPtr > gCallbackClients;
void onCallbackConnectionCreated(
RCF::RcfSessionPtr sessionPtr,
RCF::ClientTransportAutoPtr transportAutoPtr){
<span class="token keyword">typedef</span> boost<span class="token operator">::</span>shared_ptr<span class="token operator"><</span> RcfClient<span class="token operator"><</span>I_HelloWorld<span class="token operator">></span> <span class="token operator">></span> HelloWorldPtr<span class="token punctuation">;</span>
HelloWorldPtr <span class="token function">helloWorldPtr</span><span class="token punctuation">(</span> <span class="token keyword">new</span> RcfClient<span class="token operator"><</span>I_HelloWorld<span class="token operator">></span><span class="token punctuation">(</span>transportAutoPtr<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
RCF<span class="token operator">::</span>Lock <span class="token function">lock</span><span class="token punctuation">(</span>gCallbackClientsMutex<span class="token punctuation">)</span><span class="token punctuation">;</span>
gCallbackClients<span class="token punctuation">.</span><span class="token function">push_back</span><span class="token punctuation">(</span> helloWorldPtr <span class="token punctuation">)</span><span class="token punctuation">;</span>
}
int main(){
RCF::RcfInitDeinit rcfInit;
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
server<span class="token punctuation">.</span><span class="token function">setOnCallbackConnectionCreated</span><span class="token punctuation">(</span>
boost<span class="token operator">::</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token operator">&</span>onCallbackConnectionCreated<span class="token punctuation">,</span> _1<span class="token punctuation">,</span> _2<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Wait for clients to create callback connections.</span>
<span class="token comment">// ...</span>
<span class="token comment">// Retrieve all created callback connections.</span>
std<span class="token operator">::</span>vector<span class="token operator"><</span>HelloWorldPtr<span class="token operator">></span> clients<span class="token punctuation">;</span><span class="token punctuation">{</span>
RCF<span class="token operator">::</span>Lock <span class="token function">lock</span><span class="token punctuation">(</span>gCallbackClientsMutex<span class="token punctuation">)</span><span class="token punctuation">;</span>
clients<span class="token punctuation">.</span><span class="token function">swap</span><span class="token punctuation">(</span>gCallbackClients<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// Call Print() on them.</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span>std<span class="token operator">::</span>size_t i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator"><</span>clients<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span><span class="token punctuation">{</span>
HelloWorldPtr clientPtr <span class="token operator">=</span> clients<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span>
clientPtr<span class="token operator">-</span><span class="token operator">></span><span class="token function">Print</span><span class="token punctuation">(</span><span class="token string">"Hello World"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
回调连接的功能与常规连接一样,只是回调连接不能重新连接。如果回调连接丢失,客户机将需要重新连接并调用RCF::createCallbackConnection(),以重新建立连接。
有关回调连接的更多信息,请参见回调连接。
12. File transfers
文件下载和上传在分布式系统中很常见。RCF通过RCF::FileDownload和RCF::FileUpload类为文件传输提供内置支持。RCF::FileDownload和RCF::FileUpload用作远程方法参数,以实现文件传输作为远程调用的一部分。
在服务器端,默认情况下禁用文件传输功能,需要显式启用:
HelloWorldImpl helloWorld;
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
server.bind<I_HelloWorld>(helloWorld);
server.start();
- 1
- 2
- 3
- 4
这里我们实现了一个PrintAndDownload()方法,它允许客户端下载文件:
RCF_BEGIN(I_HelloWorld, "I_HelloWorld")
RCF_METHOD_V1(void, Print, const std::string &)
RCF_METHOD_V2(void, PrintAndDownload, const std::string &, RCF::FileDownload)
RCF_END(I_HelloWorld)
class HelloWorldImpl{
public:
void Print(const std::string & s){
std::cout << "I_HelloWorld service: " << s << std::endl;
}
<span class="token keyword">void</span> <span class="token function">PrintAndDownload</span><span class="token punctuation">(</span><span class="token keyword">const</span> std<span class="token operator">::</span>string <span class="token operator">&</span> s<span class="token punctuation">,</span> RCF<span class="token operator">::</span>FileDownload fileDownload<span class="token punctuation">)</span><span class="token punctuation">{</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"I_HelloWorld service: "</span> <span class="token operator"><<</span> s <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
fileDownload <span class="token operator">=</span> RCF<span class="token operator">::</span><span class="token function">FileDownload</span><span class="token punctuation">(</span><span class="token string">"path/to/download"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
,这可以被调用像这样:
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
RCF::FileDownload fileDownload("path/to/download/to");
client.PrintAndDownload("Hello World", fileDownload);
std::string pathToDownload = fileDownload.getLocalPath();
RCF::FileManifest & downloadManifest = fileDownload.getManifest();
std::cout << "Client-local path to upload: " << pathToDownload << std::endl;
std::cout << "Number of files uploaded: " << downloadManifest.mFiles.size() << std::endl;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这里我们实现了一个PrintAndUpload()方法,它允许客户端上传文件:
RCF_BEGIN(I_HelloWorld, "I_HelloWorld")
RCF_METHOD_V1(void, Print, const std::string &)
RCF_METHOD_V2(void, PrintAndUpload, const std::string &, RCF::FileUpload)
RCF_END(I_HelloWorld)
class HelloWorldImpl{
public:
void Print(const std::string & s){
std::cout << "I_HelloWorld service: " << s << std::endl;
}
<span class="token keyword">void</span> <span class="token function">PrintAndUpload</span><span class="token punctuation">(</span><span class="token keyword">const</span> std<span class="token operator">::</span>string <span class="token operator">&</span> s<span class="token punctuation">,</span> RCF<span class="token operator">::</span>FileUpload fileUpload<span class="token punctuation">)</span><span class="token punctuation">{</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"I_HelloWorld service: "</span> <span class="token operator"><<</span> s <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
std<span class="token operator">::</span>string pathToUpload <span class="token operator">=</span> fileUpload<span class="token punctuation">.</span><span class="token function">getLocalPath</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
RCF<span class="token operator">::</span>FileManifest <span class="token operator">&</span> uploadManifest <span class="token operator">=</span> fileUpload<span class="token punctuation">.</span><span class="token function">getManifest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"Server-local path to upload: "</span> <span class="token operator"><<</span> pathToUpload <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"Number of files uploaded: "</span> <span class="token operator"><<</span> uploadManifest<span class="token punctuation">.</span>mFiles<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
,这可以被调用像这样:
// Upload files to server.
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) );
RCF::FileUpload fileUpload("path/to/files");
client.PrintAndUpload("Hello World", fileUpload);
- 1
- 2
- 3
- 4
可以监视、暂停、恢复和取消文件传输,还可以对进出给定服务器的文件传输应用带宽节流。
有关更多信息,请参见文件传输。
13. Protocol Buffers
RCF提供了与Protocol Buffers的本地集成。Protocol Buffers编译器生成的类可用于RCF接口,并通过相关 Protocol Buffers函数执行序列化和反序列化。
例如,在以下Protobuf接口上运行protoc编译器:
// Person.proto
message Person {
required int32 id = 1;
required string name = 2;
optional string email = 3;
}
message PbEmpty {
optional string log = 1;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
protoc Person.proto --cpp_out=.
- 1
,生成c++类Person,在Person.pb.h中定义。
通过包括Person.pb.h,我们可以将Person实例发送到Print()函数:
#include <../test/protobuf/messages/cpp/Person.pb.h>
RCF_BEGIN(I_HelloWorld, “I_HelloWorld”)
RCF_METHOD_V1(void, Print, const std::string &)
RCF_METHOD_V1(void, Print, const Person &)
RCF_END(I_HelloWorld)
class HelloWorldImpl{
public:
void Print(const std::string & s){
std::cout << s << std::endl;
}
<span class="token keyword">void</span> <span class="token function">Print</span><span class="token punctuation">(</span><span class="token keyword">const</span> Person <span class="token operator">&</span> person<span class="token punctuation">)</span><span class="token punctuation">{</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"Person name: "</span> <span class="token operator"><<</span> person<span class="token punctuation">.</span><span class="token function">name</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"Person email: "</span> <span class="token operator"><<</span> person<span class="token punctuation">.</span><span class="token function">email</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"Person id: "</span> <span class="token operator"><<</span> person<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
};
int main(){
RCF::RcfInitDeinit rcfInit;
HelloWorldImpl helloWorld;
RCF::RcfServer server( RCF::TcpEndpoint(50001) );
server.bind<I_HelloWorld>(helloWorld);
server.start();
RcfClient<span class="token operator"><</span>I_HelloWorld<span class="token operator">></span> <span class="token function">client</span><span class="token punctuation">(</span> RCF<span class="token operator">::</span><span class="token function">TcpEndpoint</span><span class="token punctuation">(</span><span class="token number">50001</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
Person person<span class="token punctuation">;</span>
person<span class="token punctuation">.</span><span class="token function">set_name</span><span class="token punctuation">(</span><span class="token string">"Bob"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
person<span class="token punctuation">.</span><span class="token function">set_email</span><span class="token punctuation">(</span><span class="token string">"bob@acme.com"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
person<span class="token punctuation">.</span><span class="token function">set_id</span><span class="token punctuation">(</span><span class="token number">123</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
client<span class="token punctuation">.</span><span class="token function">Print</span><span class="token punctuation">(</span>person<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
有关Protocol Buffers支持的更多信息,请参见Protocol Buffers。
14. JSON-RPC
RCF提供了一个内置的JSON-RPC服务器,允许从JSON-RPC客户机访问c++服务器功能,比如web页面上的Javascript代码。RCF支持HTTP和HTTPS上的JSON-RPC。
RCF使用JSON Spirit库读写JSON消息。您需要单独下载这个库,并在构建RCF时定义RCF_USE_JSON,以启用JSON-RPC支持(参见附录 - Building)。
要配置JSON-RPC端点,请在相关服务器传输上调用I_ServerTransport::setRpcProtocol()。要将服务对象公开给JSON-RPC客户机,请使用RcfServer::bindJsonRpc()。
下面是一个RCF服务器使用多个传输来同时接受RCF请求和JSON-RPC请求的例子:
RCF_BEGIN(I_HelloWorld, "I_HelloWorld")
RCF_METHOD_V1(void, Print, const std::string &)
RCF_END(I_HelloWorld)
class HelloWorldImpl{
public:
void Print(const std::string & s){
std::cout << "I_HelloWorld service: " << s << std::endl;
}
<span class="token keyword">void</span> <span class="token function">JsonPrint</span><span class="token punctuation">(</span>
<span class="token keyword">const</span> RCF<span class="token operator">::</span>JsonRpcRequest <span class="token operator">&</span> request<span class="token punctuation">,</span>
RCF<span class="token operator">::</span>JsonRpcResponse <span class="token operator">&</span> response<span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token comment">// Print out all the strings passed in, and return the number of</span>
<span class="token comment">// characters printed.</span>
<span class="token keyword">int</span> charsPrinted <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> json_spirit<span class="token operator">::</span>Array <span class="token operator">&</span> params <span class="token operator">=</span> request<span class="token punctuation">.</span><span class="token function">getJsonParams</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span>std<span class="token operator">::</span>size_t i<span class="token operator">=</span><span class="token number">0</span><span class="token punctuation">;</span> i<span class="token operator"><</span>params<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token operator">++</span>i<span class="token punctuation">)</span><span class="token punctuation">{</span>
<span class="token keyword">const</span> std<span class="token operator">::</span>string <span class="token operator">&</span> s <span class="token operator">=</span> params<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">get_str</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
std<span class="token operator">::</span>cout <span class="token operator"><<</span> <span class="token string">"I_HelloWorld service: "</span> <span class="token operator"><<</span> s <span class="token operator"><<</span> std<span class="token operator">::</span>endl<span class="token punctuation">;</span>
charsPrinted <span class="token operator">+</span><span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token function">size</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// Return number of characters printed.</span>
json_spirit<span class="token operator">::</span>mObject <span class="token operator">&</span> responseObj <span class="token operator">=</span> response<span class="token punctuation">.</span><span class="token function">getJsonResponse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
responseObj<span class="token punctuation">[</span><span class="token string">"result"</span><span class="token punctuation">]</span> <span class="token operator">=</span> charsPrinted<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
};
int main(){
RCF::RcfInitDeinit rcfInit;
RCF::RcfServer server;
<span class="token comment">// Accept RCF client requests on port 50001.</span>
HelloWorldImpl helloWorld<span class="token punctuation">;</span>
server<span class="token punctuation">.</span>bind<span class="token operator"><</span>I_HelloWorld<span class="token operator">></span><span class="token punctuation">(</span>helloWorld<span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">addEndpoint</span><span class="token punctuation">(</span> RCF<span class="token operator">::</span><span class="token function">TcpEndpoint</span><span class="token punctuation">(</span><span class="token number">50001</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Accept JSON-RPC requests over HTTP on port 80.</span>
server<span class="token punctuation">.</span><span class="token function">bindJsonRpc</span><span class="token punctuation">(</span>
boost<span class="token operator">::</span><span class="token function">bind</span><span class="token punctuation">(</span><span class="token operator">&</span>HelloWorldImpl<span class="token operator">::</span>JsonPrint<span class="token punctuation">,</span> <span class="token operator">&</span>helloWorld<span class="token punctuation">,</span> _1<span class="token punctuation">,</span> _2<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token string">"JsonPrint"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">addEndpoint</span><span class="token punctuation">(</span> RCF<span class="token operator">::</span><span class="token function">HttpEndpoint</span><span class="token punctuation">(</span><span class="token number">80</span><span class="token punctuation">)</span> <span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">setRpcProtocol</span><span class="token punctuation">(</span>RCF<span class="token operator">::</span>Rp_JsonRpc<span class="token punctuation">)</span><span class="token punctuation">;</span>
server<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// RCF clients can call Print() on port 50001.</span>
<span class="token comment">// ...</span>
<span class="token comment">// JSON-RPC clients can call JsonPrint() over HTTP on port 80.</span>
<span class="token comment">// ...</span>
<span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
有关JSON-RPC服务器的更多信息,请参见JSON-RPC。
</div>