1. RPC概念
RPC(Remote Procedure Call),远程过程调用,是一个分布式系统间通信的技术。最核心要解决的问题是,如何调用执行另一个地址空间上的函数、方法,就感觉如同在本地调用一样。这个是什么意思的呢?假设有两台主机host A和host B,host B中有一个函数,比如add()
函数,那么host A调用host B的add()
的过程,就叫做RPC。
那么针对RPC通过上图可以看到,在整个RPC通信过程中,需要考虑的主要问题有以下两点
- 序列化和反序列化,在请求端需要做到序列化将对象转换为二进制,在服务端需要做到反序列化将收到的二进制转化为对象。当然这边还需要涉及到一定的协议结构,这些觉得都是为了保证请求端和服务端能正确的处理发送相关调用信息;
- 传输的问题,针对RPC来说,需要确保通信的可靠,所以一般来说通信是建立在TCP之上的。
针对RPC的体系认识,可以看一下这篇博客:
- 体系化认识 RPC
2. RPC框架—Thrift简介
Thrift是一个融合了序列化 +RPC的跨语言的RPC框架,最初由Facebook于2007年开发,2008年进入Apache开源项目。Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml等),并由生成的代码负责RPC协议层和传输层的实,RPC是C-S模式的。
对于接口语言的理解,因为Thrift是支持多语言的,客户端和服务端能使用不同的语言开发,那么一定就要有一种中间语言来关联客户端和服务端的语言,那么这种语言就是IDL(Interface Description Language)语言,当我们定义了统一的IDL语言之后,在生成不同的语言之后,照样实现互相正确的通信。当然也可以理解成,IDL语言之后会被生成一系列接口,那么叫做接口语言(比较牵强的理解)。
3. Thrift实践
对于Thrift可以通过实践操作入个门,可以下载Thrift官方源码,进行源码编译和安装之后。进入源代码根目录的tutorial
目录,该目录中有很多不同语言的示例代码,
而tutorial.thrift
这个文件里的内容就是thrift框架中所定义的IDL语言,截取一段如下
......
typedef i32 MyInteger
const i32 INT32CONSTANT = 9853
const map<string,string> MAPCONSTANT = {'hello':'world', 'goodnight':'moon'}
enum Operation {
ADD = 1,
SUBTRACT = 2,
MULTIPLY = 3,
DIVIDE = 4
}
......
下面使用C++的示例代码来进行演示,进入cpp
目录,该目录中有CppClient.cpp
、CppServer.cpp
两个文件,前者是RPC中客户端的代码,后者是RPC中服务端的代码
该目录中还有gen-cpp
目录,该目录内的文件是根据tutorial.thrift
生成的接口代码,包括了protocol和transport层的代码,同时包括了生成的服务端的示例代码SharedService_server.skeleton.cpp
,只是这个示例程序中使用的是自己编写的客户端和服务端编写的。
下面先运行cpp
目录下的TutorialServer
服务端程序,之后启动TutorialClient
客户端程序,整个运行效果如下所示。
下面我们来看一下CppClient.cpp
中的代码
int main() {
......
try {
transport->open();
client.ping();
cout << "ping()" << endl;
cout << "1 + 1 = " << client.add(1, 1) << endl;
Work work;
work.op = Operation::DIVIDE;
work.num1 = 1;
work.num2 = 0;
try {
client.calculate(1, work);
cout << "Whoa? We can divide by zero!" << endl;
} catch (InvalidOperation& io) {
cout << "InvalidOperation: " << io.why << endl;
}
work.op = Operation::SUBTRACT;
work.num1 = 15;
work.num2 = 10;
int32_t diff = client.calculate(1, work);
cout << "15 - 10 = " << diff << endl;
SharedStruct ss;
client.getStruct(ss, 1);
cout << "Received log: " << ss << endl;
transport->close();
} catch (TException& tx) {
cout << "ERROR: " << tx.what() << endl;
}
}
client中调用了几个函数,比如ping()
函数,add()
函数等,那么实际上这些函数调用的是server中的ping
、add
函数
class CalculatorHandler : public CalculatorIf {
public:
CalculatorHandler() {}
void ping() { cout << "ping()" << endl; }
int32_t add(const int32_t n1, const int32_t n2) {
cout << "add(" << n1 << ", " << n2 << ")" << endl;
return n1 + n2;
}
int32_t calculate(const int32_t logid, const Work& work) {
cout << "calculate(" << logid << ", " << work << ")" << endl;
int32_t val;
switch (work.op) {
case Operation::ADD:
val = work.num1 + work.num2;
break;
case Operation::SUBTRACT:
val = work.num1 - work.num2;
break;
case Operation::MULTIPLY:
val = work.num1 * work.num2;
break;
case Operation::DIVIDE:
if (work.num2 == 0) {
InvalidOperation io;
io.whatOp = work.op;
io.why = "Cannot divide by 0";
throw io;
}
val = work.num1 / work.num2;
break;
default:
InvalidOperation io;
io.whatOp = work.op;
io.why = "Invalid Operation";
throw io;
}
SharedStruct ss;
ss.key = logid;
ss.value = to_string(val);
log[logid] = ss;
return val;
}
void getStruct(SharedStruct& ret, const int32_t logid) {
cout << "getStruct(" << logid << ")" << endl;
ret = log[logid];
}
void zip() { cout << "zip()" << endl; }
protected:
map<int32_t, SharedStruct> log;
};
之后的博文还对整个调用过程和源码进行分析。上面只是根据官方提供的示例代码来进行演示,当然我们也可以自己动手实现客户端和用户端程序会更加了解这个框架,这边感兴趣的大佬,可以自行搜索一波。