Skip to content

Latest commit

 

History

History
803 lines (511 loc) · 25.1 KB

tce.md

File metadata and controls

803 lines (511 loc) · 25.1 KB

Tce

 about: 	adoggie		13916624477  
  			sockref@hotmail.com 
  			

  			
revision:
2015.7.6  scott  文档创建
2016.9.9  scott 
   1. 增加 2.3.6 跨模块类型引用
2016.9.10 scott 
   1. IDL 接口和函数增加元数据定义 Annotation 

1.1 背景

互联网技术飞速发展,各种internet应用服务满天飞,每天都有新的idea层出不绝。互联网应用推成出新的速度非常的快,业务和技术快速更新和迭代,传统的开发模式和技术不能满足互联网模式的要求。

很久以前,要驱动一个项目的设计、开发和实施,要求和成本还是比较的高。开发者必须熟练一到二门开发语言之外,还必须了解多种数据库,通信架构,交互模式等。c/c++/delphi/vb时代基本都是c/s的技术模式,但随着互联网时代、大数据时代、移动社交时代的到来,传统开发技术已慢慢被淘汰,各种开发技术涌现对开发者要求越来越低,能完成业务目标,技术不再是硬门槛。

曾几何时,基于http的webservice大行其道,各种技术手段和业务模式都往http靠拢,网络提速、硬件性能的提升,令http性能问题不再显得扎眼,俨然,http已成为工业标准 。

对于一个js开发者来讲,前台到后台贯串整个业务实现流水线完全由一人完成,这是多么令人振奋,老板定是喜欢。 现在的大系统,上了规模,那必定是 集群化、分布式、网络异构和技术异构的,一种技术定乾坤肯定是不合理。

1.2 Tce是什么?

tce 的目标是提供一套易用的、一致编程接口标准的通信库。

使开发者只需将关注点放在应用业务代码上,而不用关心数据如何被传递、交互和处理,避免了开发者重复开发通信程序的成本。

使用tce可快速构建移动互联网系统,在移动端建立与平台系统的桥梁,提供平台系统之间数据交换通道,并解决大规模移动终端接入、集群消息分派和调度、消息反向推送等问题,为社交系统、即时通信系统提供基础支撑。

使用tce能很方便的架构系统集群,为大数据,大并发做好准备,省去了大量的繁琐的非业务相关的技术工作。

tce提供的服务都是基于接口的服务,开发者只需定义业务接口,采用自己熟悉的开发工具实现业务功能。开发人员只需如同调用本地函数方式调用不同的业务服务功能,这些服务可以是本地的、远程的、分布式或集群的,且都是被接口化定义和隔离。

http是标准也很普及,但是我觉得还不够

对开发者而言,对其有真正有帮助的是让业务与技术实现分开。虽然http够标准,使用也不是问题,但还是需要开发者过多的介入,比如要处理POST、GET请求,数据封包是urlencoded、xml或json.所以tce在html5的应用场景下对websocket进行了rpc的实现,网页应用可以以简单接口方式的使用平台服务功能,且这些传输是二进制的,所以是高效的、安全的,为html5的实时应用提供了保证。

RPC技术

rpc 是sun公司提出的技术,其内容是XDR一种实现. RPC的技术实现有很多,例如 : rmi、AJP, corba,visibroker,dcom,zeroc,xmlrpc,soap等等 。

soap是基于xml的rpc技术,通过wsdl等方式定义基于webservice的接口,soap可以是http或者tcp的,应用全体很广泛,扩展性也很强,出现了很多对soap的支持。例如: gsoap就是c++的soap服务实现,使用者定义wsdl之后,生成调用框架代码,实现客户与服务的接口函数级的调用。

以上诸多的技术均可实现rpc服务,并且其各有特点和限制。例如 corba是工业级标准,但其过于复杂和庞大,不够灵活,学习成本高; dcom 是微软体系的rpc技术,当然只能在ms范围内使用。基于文本的xml/soap虽然扩展性强,但数据量大是个问题。

开发tce是在学习zeroc代码之后产生的想法,原因在于 zeroc的rpc里面有很多我认为是比较累赘的东西,本想将其瘦身,后来发现代码关联太紧,花时间去改造还不如自己实习一套。

我需要的rpc应该是轻量级的、灵活的、易扩展的,与开发技术、网络、平台、通信协议、应用无关。 rpc应提供简易的编程接口,简化网络编程的工作,避免重复造轮子的过程。

实现RPC有哪些内容:

  1. 接口定义
  2. 数据序列化和反序列化
  3. 通信传输
  4. 消息分派
  5. 调用模型

tce有哪些特点

1. 多语言支持

支持的程序语言包括:

  1. c++ ( stl/boost/asio) *
  2. actionscript
  3. java *
  4. python (gevent/libev/websocket) *
  5. javascript
  6. php
  7. object-c
  8. node-js
  9. csharp
2. 系统平台
  1. android
  2. ios
  3. html5
  4. windows/linux (c++/java supported)
3. 网络通信
  1. socket (tcp)
  2. mq (qpid,zeromq,easymq)
  3. websocket (http)

2. 走进TCE

2.1 第一个程序 echo

开始我们第一个程序 server, 其功能很简单,仅仅提供一个echo方法,服务程序接收调用者发送的消息,并将其打印,并将消息回显到调用端。

使用tce实现只需几个步骤:

  1. 编写idl test.idl
  2. 生成代码存根 skeleton test.py
  3. 编写服务代码 server.py
  4. 编写客户代码 client.py
  5. rocking&roll

编写接口 test.idl :

	module test{
	
		interface Server{
			string echo(string greeting);
		};
	}

编译idl ,产生 test.py文件

	tce2py -i test.idl -o ./	

编写服务程序 server.py

	import os,time,traceback
	import tcelib as tce 
	from test import Server 
	
	class ServerImpl(Server):
		def __init__(self):
			Server.__init__(self)
			
		def echo(self,greetings,ctx):
			return greetings
			
	
	def main():
		tce.RpcCommunicator.instance().init('server')
		ep = tce.RpcEndPoint(host='',port=16005)
		adapter = tce.RpcCommunicator.instance().createAdapter('first_server',ep)
		servant = ServerImpl()
		adapter.addServant(servant)
		tce.RpcCommunicator.instance().waitForShutdown()
		
	if __name__ == '__main__':
		sys.exec(main())

编写客户端程序 client.py:

	import os,time,traceback
	import tcelib as tce 
	from test import ServerPrx 
	
	
	tce.RpcCommunicator.instance().init()
	
	ep = tce.RpcEndPoint(host='localhost',port=16005)
	prx = ServerPrx.create(ep)
	
	print prx.echo('hello')


2.2 编写接口IDL

接口是指不同业务系统之间对业务功能的一种外部呈现,接口的功能是业务提供方实现,并由使用方调用。 idl文件用于定义应用业务中包涵的数据类型功能接口

2.2.1 idl关键字

id name comment
1 import 导入其他idl模块
2 module 表示一个模块定义,可理解为定义namespace
3 interface 服务接口定义
4 extends 接口继承
5 struct 复合数据集合
6 sequence 数组类型
7 dictionary 字典类型
8 byte,short,int,long,float,double,string,bool 基础数据类型

####关键字的映射

NO name c++ python java as object-c csharp
1 module namespace filename package package namespace
2 interface class class class class interface class

2.2.2 编写 idl

idl文件可以用任何文字编辑工具生成。

	idl定义基本样式 
	
	import otheridl
	module{
		sequence<> seq;
		dictionary<> dict;
		struct {} ;
		interface server extends baseserver{
			void hello();
		};
	}

使用 import 可以导入多个外部的idl

一个idl文件中可以包含多个module定义,module不支持嵌套, {}之后没有; , 但内部定义元素之间必须以;分隔。

2.2.3 行注释

tce的idl文件支持两种行注释 // 或者 #

2.3 数据类型

编写idl时,采用tce的数据类型,通过简单的数据类型可以组装成复杂的数据对象。

2.3.1 基础类型

NO name size c++ python java as object-c csharp
1 byte 1 uint8_t byte uint8 byte
2 short 2 int16_t short short short
3 int 4 int32_t int int32_t int
4 long 8 long long long long
5 float 4 float float float float
6 double 8 double double double float
7 string n+4 std::string string String NSString string
8 bool 1 bool True/False boolean NSBoolean bool

2.3.2 复合类型 struct

idl c++ python java as object-c csharp
struct struct class class interface class

struct 提供了属性集对象的功能。从oo的角度来看,任何对象都可以是一个结构类型的定义 。

struct Cat {
	string  skin; 
	int		age;
	int 	categary;
   ...
};

2.3.3 容器类型 sequence / dictionary

idl c++ python java as object-c csharp
sequence std::vector list Vector Array NSArray List
dictionary std::map dict HashMap HashMap NSDictionary Dictionary<T,V>
2.3.3.1 数组

关键字: sequence<T>

sequence表示数组,其应该是线性的。 T可以是简单数据类型、容器类型、结构类型。

	sequence<string> string_list;
	sequence<int> 	int_list;
	sequence<string_list> tables; 
	
	更多
	struct Pet{ ... };
	sequence<Pet> PetList;
	dictionary<string,Pet> PetsNamed;
	
	sequence<PetsNames> PetsNamedList; 
	
2.3.3.2 字典

关键字: dictionary<K,V>

dictionary 是字典结构(hash),K 必须是简单数据类型,不能是复合对象; V可以是简单数据类型、容器类型、结构类型。

2.3.4 字节数组特殊处理

idl c++ python java as object-c csharp
sequence std::vector< byte > str byte[] byte[] NSData byte[]

2.3.5 复合数据组装

有了基础数据类型、struct、容器之后,我们可以面对复杂的对象进行包装了。 struct、sequence、dictionary支持定义嵌套,例如:

 struct Pet {
 	string	name;
 	string  skin; 
 	int		age;
 	int 	categary;
	...
 };
 
 sequence<Pet> PetList;
 
 struct Family{
 	string name;
 	PetList pets;	 //宠物列表
 };
	

###2.3.6 跨模块引用

当跨模块引用数据类型时,需使用" module_prefix::type name "形式

  module A{
    struct P1{ .. }
  }
  
  module B{
    sequence<string> NameList_t;
    interface Car{	    	
    	NameList_t start(A::P1 p);
    }
  }

以上定义中B模块的接口Car的start()函数参数引用了A模块的P1结构类型。

2.4 接口 interface

关键字: interface 接口是用户定义的一组服务功能的集合

2.4.1 接口元属性Annotation定义

为了方便控制编写idl和生成语言框架代码,在最近的tmake中支持对接口和函数进行元属性定义。

元属性,这个概念类似C#,java中的标注Annotation,我借用了C#的语法,在 接口和函数定义之前可添加标注属性。

属性定义的格式 :

[key=value,key=value,...] 

目前支持的key类型:  
    index  - 自定义接口序列化编号
    comment - 描述
    skeleton_xxx - 规定接口实现是否生成服务端代码

examples:

module test{
    [index=11,comment="base server"]
    interface BaseServer{
        [index=10]
        string datetime();
    };

    [skeleton_js=false,skeleton_objc=false,skeleton_as=false,skeleton_cpp=false,skeleton_csharp=false,skeleton_java=false,comment=""]
    interface Server extends BaseServer{
        string echo(string text);
        [index=5,comment="test"]
        void  timeout(int secs);
        [index=10]
        void heartbeat(string hello);
        [index=11]
        void bidirection();
    };
}

在以上idl定义中,将 BaseServer的序列化编号定义为11 (当然这个序列化编号对用户是透明的,无需关心,由tce自动维护,但是在软件版本变更的场景时是必须的, 因为接口增加、删除会打乱原始的序列化,所以提供一种方法令用户固定唯一的接口编号,困了,讲得有点糊涂 :-> )

Server接口定义了诸多 'skeleton_xxx',每一项对应不同程序语言的输出控制,如果是false,则 Server接口不输出服务侧代码。

2.5 接口编译 tmake

tmake是一组编译idl生成框架代码的工具,其位置: $TCE/bin/tmake/tce2xxx.py。

基本的使用方法:

tce2java.py -i $TCE/idl/test.idl -o ./  
tmake根据test.idl接口文件生成java的框架代码到当前目录下 [test]

如需客户化接口序号和函数序号,需要定义 if-index-list.txt 文件;

2.6 调用模式

tce为不同调用模式自动生成不同的代理接口

test.idl 定义

	
module test{

interface BaseServer{
	string datetime();
};

interface ITerminalGatewayServer{
	void ping();
};


interface Server extends BaseServer{
	string echo(string text);
	void  timeout(int secs);
	void heartbeat(string hello);
	void bidirection();
};


interface ITerminal{
    void onMessage(string  message);
};

}
			

2.6.1 two-way

阻塞式同步调用,调用发起并等待返回。 这种调用方式常见与c/s交互场景,阻塞调用对client来说是最易于理解和使用的模式,发起功能调用直到到服务处理返回或者异常产生,期间调用者会一直保持阻塞,直到超时产生。

python版本的tce实现采用gevent,底部是libev,提供单线程的io异步复用的协程运行模式,免去了多线程的资源开销和内核调用工作,使gevent的效率比多线程实现更高,由于是单线程模式,基本免去了
很多互斥、同步等问题,降低了编程复杂度,提高了性能。

tce的调用模式有同步和异步之分,但内部实现均是io异步处理。

异常类型:

  1. 发送失败
  2. 数据类型错
  3. 服务端错误
客户程序 client.py

	def call_twoway():
		print prx.echo("hello",timeout=0,extra={'date':'1926-12-12'})

2.6.2 one-way

oneway调用应用在单向呼叫,并无需返回等待的场景,表示业务功能接口没有返回,oneway的接口函数类型必须是 void 类型.

tce 为 void 接口函数自动生成 以 _oneway后缀的函数名

def call_oneway():
	prx.heartbeat_oneway('hello world!',extra={})

2.6.3 async-call

async调用在用户发起请求之后,通过回调函数来接收返回值。 tce自动生成 后缀"_async"的函数名。

def call_async():

	def hello_callback_async(result,proxy,cookie):#回调接口
		print 'async call result:',result
		print 'cookie:',cookie

	prx.echo_async('pingpang',hello_callback_async,cookie='cookie',extra={})

2.6.4 timeout-call

timeout-call提供阻塞调用等待超时的功能,同步调用方式是最容易理解和操作的方式,用户可以指定期待处理的等待时间,在调用发起后等待返回,直到超时发生。

def call_timeout():
	try:
		print prx.timeout(3,6,extra={})
	except tce.RpcException, e:
		print e.what()

2.6.5 bidirection

在互联网应用环境中,客户机往往都是安置在NAT之后,服务器与客户机通信必须由客户机发起对服务器的连接,常规的rpc服务往往都是单向的,连接发起者是服务的消费者,被连接者是服务的提供者,所以在互联网环境中,客户机可以使用rpc方式调用服务器上的服务,但服务器作为服务提供者却不能直接访问客户机上的服务。

bidirection提供的客户端连接复用方式解决了服务器调用客户机上接口的功能.

client.py 

class TerminalImpl(ITerminal):
	def __init__(self):
		ITerminal.__init__(self)

	def onMessage(self,message,ctx):
		print 'onMessage:',message
		
def call_bidirection():
	adapter = tce.RpcCommAdapter('adapter')
	impl = TerminalImpl()
	adapter.addConnection(prx.conn)
	adapter.addServant(impl)
	tce.RpcCommunicator.instance().addAdapter(adapter)

	prx.bidirection_oneway()
	
--------------------------------------------
server.py

	def bidirection(self,ctx):
		"""
		not supported in message-queue scene.
		birection适用在 链路双向复用的场景 ,例如: socket
		:return:
		"""
		self.clientprx = ITerminalPrx(ctx.conn)
		self.clientprx.onMessage_oneway('server push message!')

###2.6.7 Promise使用

异步编程已是主流,主要应用在 移动前端App(禁止阻塞调用线程) 、系统服务器(大规模接入和并发数据)。

Promise充斥在js各个角落,各种知名第三方软件项目都存在类似Promise的组件。

那Promise是个什么样的东西,且为何要使用Promise呢?

Promise表示一个未发生的执行事件的定义,在Java的Netty项目中对应的组件叫做 `Future`.
现在越来越的应用系统开发采用异步IO来提高系统处理的能力,且事实证明其的确是高效。
javascript ES6已经内置了Promise组件, js所有的io操作均是异步操作。 
Android 通过handler处理异步操作,防止主线程被阻塞。 
Tornado,NodeJs,epoll,Netty,libaio,libev都是相关的异步处理技术。
但是异步处理也带来的开发的复杂性,使得交互处理行为无法被串行处理,不同的操作步骤被分割到不同的
异步回调函数中钩挂,使得代码可读性、可维护性变得很差。
Promise的作用是将 异步调用的嵌套 转为同一平面的调用,提供类似同步调用的方式。

tce的Promise

tce的client调用模式中的异步调用在使用时需提供返回接口callback,callback实现方式可以是函数地址,委托,接口方式。
异步带来的问题在于调用嵌套,例如:a,b,c是一组连续调用,且后者必须在前者调用成功之后 被调用,这就导致tree嵌套调用,使得代码可读性,可维护性变得很糟糕。
针对这种异步调用嵌套的解决方案就是Promise,类似链表管理节点一样链接不同的调用,提供一种同步调用的方式。
异步调用不再是返回void了,而是RpcPromise对象,然后通过promise.then(result,error)来驱动串行调用。
通过promise传递两次调用的输出和输入值。
需要在异步接口函数加入promise对象,用于保存本次异步调用需向后一个调用传递的值。
promise.then()的回调函数也需增加promise输入来获得上一处理结果的输出。

Promise调用流程

C# example:

void test_promise() {
        RpcPromise p = new RpcPromise();
        p.then(delegate(RpcAsyncContext ctx) {
            ctx.promise.data = "abc";
            Console.WriteLine("step 1.1");
            ctx.promise.onNext(ctx);
        }).then(delegate(RpcAsyncContext ctx) {
            Console.WriteLine("step 1.2");
            Console.WriteLine(ctx.promise.data);
            //ctx.promise.onNext(ctx);
            ctx.promise.onError(ctx);
        });
        RpcPromise p2 = p.error(delegate(RpcAsyncContext ctx) {
            //p.onNext(ctx,ctx.promise); 
            Console.WriteLine("step 2.1");
            ctx.promise.onError(ctx);
        });
        RpcPromise p3 = p2.error(delegate(RpcAsyncContext ctx) {
            Console.WriteLine("step 3.1");
            ctx.promise.onNext(ctx);
        });
        p3.then(delegate(RpcAsyncContext ctx) {
            Console.WriteLine("step 2.2");
            ctx.promise.onNext(ctx);
        });
        
        p.then(delegate(RpcAsyncContext ctx) {
            Console.WriteLine("step 1.3");
            ctx.promise.onNext(ctx);
        }).final(delegate(RpcAsyncContext ctx) {
            Console.WriteLine("final.");
            Console.WriteLine(ctx.promise.data);
            Console.ReadKey(true);
        }).end();

    }

2.7 内部结构

几个重要部件:

  1. Communicator
  2. Adapter
  3. Servant
  4. Proxy
  5. Connection

他们是tce核心功能部件,这些部件在不同的开发语言中都有相同的实现。

2.7.1 Communicator 通信器

它是应用application的全局服务对象,也是个单例对象。communicator是tce运行的容器,提供多种功能:

  1. 初始和配置
  2. 本地服务对象管理
  3. 通信管理
  4. 消息分派

###2.7.2. Adapter adapter是服务实现的容器,并且adapter有是通信连接的容器

###2.7.3. Proxy

列集了服务功能接口,是访问interface的客户端设施

使用proxy等同调用本地函数

由tce根据idl定义自动产生

完成通信和消息序列化工作

多种调用模式: 阻塞、异步、单向、超时

tce为idl中的interface自动生成框架代码,其包含两部分内容: Proxy and ServantBase。

Proxy包装客户端访问远程服务的功能,tce根据idl定义自动生成Proxy对象,客户端程序调用Proxy的功能接口就可完成与远端服务的交互。

ServantBase 是指服务接口的定义,在这里我们将每个interface的实现称之为servant对象。tce自动生成这些servant对象的定义,要实现server端的功能,只需从这些ServantBase派生,并实现其关心的接口即可。

	test.idl 
	
	interface Server{
		string echo(string text);
	};

	tce2py.idl 生成skeleton test.py
	
	class ServerPrx:
		def echo(self,text,extra={}):
			pass
			
		def echo_oneway(self,text,extra={}):
			pass
			
		def echo_async(self,text,asyc_back,extra={}):
			pass
			
		....
		
		
	class Server:
		def echo(self,text,ctx):
			return ''
			
	

tce2py.py 自动生成了Server定义的Proxy对象(ServerPrx)和ServantBase对象(Server)

###2.7.4 Connection

###2.7.5 Servant

##2.8 外带数据控制 OOB 每个代理对象内的接口函数最后一个参数extra就是OOB,extra类型是dictionary,携带的OOB参数必须是 KEY:VALUE类型,且必须是字符串类型。

  1. c++ : map<string,string>
  2. java: HashMap<string,string>
  3. python: {}
  4. javascript: object
  5. objc: NSDictionary

python代码:

	server:
	
	class ServerImpl(Server):
		def __init__(self):
			Server.__init__(self)
			
		def echo(self,greetings,ctx):
			print 'extra OOB data:',ctx.msg.extra.props
			return greetings
			
	
	
	client:

	tce.RpcCommunicator.instance().init()	
	ep = tce.RpcEndPoint(host='localhost',port=16005)
	prx = ServerPrx.create(ep)
	print prx.echo('hello',{'first':1,'second':2})

##2.9 通信消息协议

##2.10 技术实现 tce已经完成多语言通信支持能力,部分语言仅仅实现客户端rpc,c++,java,python已实现服务器通信,也就是说服务器程序代码可以使用c++,java,python任何一种语言编写,支持服务器之间、服务器与客户端之间的跨语言的rpc调用。

name | client | client-detail |server | server-detail | ---|------|-------| ----|--------|------- | -----| c++ | yes | boost::asio | yes | boost::asio , websocket , qpid | java | yes | socket , netty ,android | yes | netty , qpid ,jms | python | yes | gevent | yes | gevent , websocket , qpid | csharp | yes | socket | no | | javascript| yes | websocket | no | actionscript| yes | socket | no | objc | yes | NSSocket | no |

3. 应用场景

###GWServer - 网关服务

接入大数量的客户请求连接,提供身份认证(token),数据安全通道(ssl)传输和消息路由分派功能。  
消息分派:  GWServer根据客户的Rpc请求调用的接口,将消息转发到后端服务系统,并从后端服务系统接收Rpc消息转发到前段客户程序。 GWServer扮演Rpc请求中间人角色,负责传递客户端与服务器之间Rpc消息的传递。 

事务性消息推入MQ系统,后端的业务服务器进行FanIn/FanOut消息的读取和发送。
实时性消息分派到HaProxy,并Fanout给后端业务服务器

###如何从后端服务调用前端接口的Rpc请求

从后端服务器发起调用前端客户程序的Rpc接口函数,必须定位客户端程序与哪个Gwserver连接进入的,进而LogicServer构造Rpc请求,并将其通过MQ发送给指定的GWServer,继而传递到客户端程序。 
* Server端消息传递必须是单向的,或者是async异步的
   server到前端客户程序只能是单向 
GWServer负责登记client id 到Redis,以便LogicServer根据client id查找GWServer的位置。 

4. 移动互联网平台架构