这是一篇关于纯C++RPC框架的文章。所以,我们先看看,我们有什么?

  1、一个什么都能干的C++。(前提是,你什么都干了)

  2、原始的Socket接口,还是C API。还得自己去二次封装...

  3、C++11,这是最令人兴奋的。有了它,才能够有这篇文章;否则,CORBA之类的才是唯一的选择。(因为需要代码生成)

那么,我们没有什么,或者需要什么?

  1、一套完善的序列化框架;在不同的进程间传输数据,序列化是第一步,如何可靠且方便地将对象转化为二进制(或者其他格式),在对端则是如何正确且安全地将其从二进制恢复为对象。

  2、完善的底层通信协议;其需要提供合适的语义抽象:服务端支持怎样的并发,是单客户单访问,还是多访问;而客户端的并发模型由服务端决定。当然,还需要健壮且足够的接口抽象,毕竟分布式环境,“一切皆有可能”,需要应对各种问题。

  3、一个可用的反射系统。是的,需要在C++环境下建立一个反射系统。这一步是最为关键的,其由C++11支持。因为,我们需要注册一个类的各种信息,以供RPC调用。

当然,本文是提纲式的;也就是,在这里暂时只会讲【目标】——一个可用的C++RPC所需要的全部组件。而更详细的内容,即如何构建这些组件,将由后续篇章回答。

一、序列化

  在分布式环境下,序列化是永远绕不过的一个坎;而作为一个支撑性组件,其所需要提供不只是“可用”,而是易用且安全!安全!重要的事说两遍。我们不能够约定从网络传来的内容,代表了什么;而是其本身必须带有信息,告诉我们它是什么。

  在纯C++环境下,要从零开始构建一个序列化框架,是困难的。毕竟语言本身并没有提供什么保证。所幸的是,C++并非完全没有提供,只是需要我们去发掘。template,模板可以给我们所需要的一切;C++中最令人兴奋的部分,其提供了另一个简单且可靠的扩展模型:通过特化模板,能够提供类似面向对象的“接口”。当然,需要的还不止这些,我们需要更强大的工具:模板元编程,由其来提供所需要的类型信息(在我的系统中有两种扩展方式:特化模板和成员函数,后者需要类型信息)。

  下面将是我们需要完成的目标:

Buffer buffer;
Object obj1;
//序列化
Serialize(&buffer, obj1);

Object obj2;
//反序列化
Deserialize(&buffer, obj2);

  其中的buffer,便是我们的成果;其以二进制保存了对象。当然,还有许多的细节需要补充。

二、通信

  UDP还是TCP,这是一个需要纠结的问题。但我们的关注点并不在这,我们需要一个完善的、健壮的通信组件。为了完成这个目标,需要付出一些努力:并发模型、中断与超时通知。前者是整个RPC框架的语义保证,我们需要保证调用语义的完整性:客户端的并发访问请求,到了服务端不能变成串行请求;而普通的通信协议,并不能够直接提供这点,我选择了从零开始构建。中断与超时,是整个调用语义,所面临的最大挑战;所以,我们需要合适的通知,以决定如何进行下一步操作。

  当然,UDP与TCP,依旧是一个重要的话题;我的实现,提供了二者的完整实现,所需的只是,选择。

三、反射

  在C++的世界里,讲这个话题是不合时宜的。毕竟,无论怎样的努力,在一个没有完整运行时环境的语言里,“反射”这样奢侈的东西,只是一个梦想。但,我们并不是想要重新打造一门语言,而仅仅是给我们的RPC提供一些服务而已。

  在RPC的世界里讲“反射”并不是一件突兀的事。看一看,各种没有IDL的RPC在Java里的实现,无一不是使用了反射这一技术。是的,我的RPC里并没有IDL,我并不打算再创建一门语言(我有一个脚本语言),哪怕只是一个DSL(领域特定语言)。为了“封装”类型的各种信息:构造函数、析构函数以及一些我们需要的方法。反射,提供了最合适的语义。

  我的RPC是非侵入式的,在不改变任何原有的代码的前提下,提供相应的RPC调用。而这一“调用”通过反射来实现。

四、RPC

  前面三大组件,和我的RPC并没有什么必然的联系。但没有这三个组件,我的RPC框架,将不复存在。所以,最后再来讲讲我们的主角:RPC。

  其实,RMI(远程方法调用)更合适一点。我的RPC提供了3中“对象”:

    1、由单一客户端独占的,非共享对象;只有该客户端能够访问该对象,其创建和析构,都由一个客户端发起和执行。

    2、服务对象,这在服务端本身是一个“服务”;也就是其本身是一个单例,因此,所有客户都是直接在一个对象上,执行各种请求;该对象的构造和析构,可由服务端的本地服务管理框架(我的另一个东西)管理。这个方式,提供了“服务化”的最佳语义;当然,也就需要面对并发。

    3、注册对象,其也是全局可访问的,所有客户端都可以发送请求;不同的是,其是由服务端的某段代码显示注册的,该对象的所有权也由其持有;客户端只能访问其接口。一旦服务端取消了注册,便也消失在所有的客户端眼前,并且是可能在任何时刻取消注册,即使有请求还在路上(当然,有请求正在执行,需要等待其执行完成)。

  还有一个重要的事情,我只提供了,同步访问;因为,我有一个分布式消息系统,可提供异步服务(这只是托词,懒,而已)。

  其实,在拥有了前面的三个组件后,RPC本身便只剩下,需不需要构建,而不是如何。这样,我们也就可以将重心转向RPC的语义保证上了。一般有三种语义可以提供:至少一次、至多一次,可能一次。分别代表如下3中结果:

    1、该请求被执行,因为我们将重复地发送请求,直到答复。也就意味着,该请求可能被重复执行多次。

    2、该请求被执行,我们会一直发送重复的请求,直到答复。但服务端,将识别这些重复的请求,并只会执行一次。

    3、该请求被发送,但我们并不保证被执行;因为,我们没有等待答复。

  对于通用的RPC,除了“至多一次”,并没有其他的选择。当然,为了完成这点,需要一些努力,所幸并不困难。我的RPC正是提供这样的调用语义。

  我们将要构建的RPC并没有使用IDL。所以,客户端的访问,将会是显示的RPC调用;而非是其他框架所宣传的,能够完全屏蔽本地和分布,但我们是在C++的环境下,要完全屏蔽,是不可能的;所以,我所提供的调用方式,并没有排除原始的方案。其与本地调用的不同是:需要统一从一个RPC客户端对象中传入所需要的对象名字,以及在方法调用时,需要一个额外的方法名字。

  比如:

RPCClient client("Something");
int val = client.Invoke("GetValue", 12, "I Want ...").To<int>();

  当然,如果你不习惯这样的方式(你会习惯的);我还提供了类似IDL的功能:代码生成。其能够将上面的显示访问,封装起来,提供和本地调用完全一样的方式:

#include "Generated\\Something.h"//这个头文件是生成的

Something obj;
int val = obj.GetValue(12, "I Want ...");

  而在服务端则,使用如下方式提供远程对象:

Class<Somthing>* type = GetServiceAs<ReflectionService>()->Register<Something>();//也可以重新命名:Register<Something>("OtherName");
type->AddMethod("GetValue", &Somthing::GetValue);//也可注册重载函数(方式后面再讲)

  总之,本篇,完。

  PS:如需,请期待下一篇——序列化。

  PS:以及,下下篇——通信。

  PS:还有,下下下篇——反射。

从RPC开始(一)的更多相关文章

  1. RPC 使用中的一些注意点

    最近线上碰到一点小问题,分析其原因发现是出在对 RPC 使用上的一些细节掌握不够清晰导致.很多时候我们做业务开发会把 RPC 当作黑盒机制来使用,但若不对黑盒的工作原理有个基本掌握,也容易犯一些误用的 ...

  2. 谈谈如何使用Netty开发实现高性能的RPC服务器

    RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...

  3. 游戏编程系列[1]--游戏编程中RPC协议的使用[3]--体验

    运行环境,客户端一般编译为.Net 3.5 Unity兼容,服务端因为用了一些库,所以一般为4.0 或往上.同一份代码,建立拥有2个项目.客户端引用: WindNet.Client服务端引用: OpL ...

  4. python通过protobuf实现rpc

    由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc.rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行g ...

  5. spider RPC入门指南

    本部分将介绍使用spider RPC开发分布式应用的客户端和服务端. spider RPC中间件基于J2SE 8开发,因此需要确保服务器上安装了JDK 8及以上版本,不依赖于任何额外需要独立安装和配置 ...

  6. Netty实现高性能RPC服务器优化篇之消息序列化

    在本人写的前一篇文章中,谈及有关如何利用Netty开发实现,高性能RPC服务器的一些设计思路.设计原理,以及具体的实现方案(具体参见:谈谈如何使用Netty开发实现高性能的RPC服务器).在文章的最后 ...

  7. 基于Netty打造RPC服务器设计经验谈

    自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...

  8. Redola.Rpc 的一个小目标

    Redola.Rpc 的一个小目标 Redola.Rpc 的一个小目标:20000 tps. Concurrency level: 8 threads Complete requests: 20000 ...

  9. 闲话RPC调用

    原创文章转载请注明出处:@协思, http://zeeman.cnblogs.com 自SOA架构理念提出以来,应用程序间如何以最低耦合度通信的问题便呈现在所有架构师面前. 互联网系统的复杂度让我们不 ...

随机推荐

  1. 【知识必备】内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye

    一.写在前面 对于C++来说,内存泄漏就是new出来的对象没有delete,俗称野指针:而对于java来说,就是new出来的Object放在Heap上无法被GC回收:而这里就把我之前的一篇内存泄漏的总 ...

  2. Microservice Anti-patterns

    在最近的一次Microservices Practitioner Summit中,原Netflix工程师介绍了一种越来越常见的对Microservice的误用.简单地说,大家在搭建一个基于Micros ...

  3. C#为IE编写BHO插件心得

    啥是BHO,其实大家都用过,没听过只是没在意而已,来张图你就知道是什么了 是不是很熟悉,就是这么个玩意~~ 先说说我要用来干嘛~我们有个库,里面数据很全面,但是某个部门需要在第三方的B/S系统录入某些 ...

  4. [转载]敏捷开发之Scrum扫盲篇

    现在敏捷开发是越来越火了,人人都在谈敏捷,人人都在学习Scrum和XP...      为了不落后他人,于是我也开始学习Scrum,今天主要是对我最近阅读的相关资料,根据自己的理解,用自己的话来讲述S ...

  5. Java

    2016-12-17  21:10:28 吉祥物:Duke(公爵)    Logo:咖啡(爪哇岛盛产咖啡)  An overview of the software development proce ...

  6. 使用LogMaster4Net实现应用程序日志的集中管理

    日志在软件系统中的重要性我在此也不赘述了,几乎所有程序员每天都会更日志打交道. 那么你是否曾今为这样的一些事情而困扰过: - 远程登录到不同的服务器,找到应用程序目然后查看应用日志: - 来回切换于不 ...

  7. 针对Linux ASP.NET MVC网站中 httpHandlers配置无效的解决方案

    近期有Linux ASP.NET用户反映,在MVC网站的Web.config中添加 httpHandlers 配置用于处理自定义类型,但是在运行中并没有产生预期的效果,服务器返回了404(找不到网页) ...

  8. WebEssentials 在vs2013 update5安装报错的解决方法.

    WebEssentials 最高支持到update4 如果更新到了update5 RC, 则无法直接安装. 解决方法是 1,下载WebEssentials2013.vsix 文件. 2, 安装7zip ...

  9. mac 无法识别seagate硬盘、无法向其写入文件

    1,无法识别 Seagate 硬盘 新买的mac air Captian 10.11.6系统,连上硬盘根本不出现盘符,usb插头不要插得太深,慢慢的插入,看到硬盘白灯亮起就可以了 2,无法向 Seag ...

  10. redis成长之路——(六)

    redis配置 为了码农在代码上只关心业务以及代码上的统一性,wenli.drive.redis内部使用配置来完成那些不同的场景,也就是说随便填填配置就能适应不同的场景! 当然配置多了码农也会受不了, ...