「javarpc源码教学」java源码讲解

博主:adminadmin 2023-03-17 23:47:09 436

今天给各位分享javarpc源码教学的知识,其中也会对java源码讲解进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!

本文目录一览:

怎么编写一个java rpc接口给c#使用

这个就需要定义一个统一的数据交换标准,比较流行的大概是HTTP接口,或者webservice接口

SOFARPC源码解析-服务调用

简介摘要

SOFARPC服务调用创建服务引用配置ConsumerConfig,自定义设置接口名称、调用协议、直连调用地址以及连接超时时间等基础配置;通过服务消费者启动类ConsumerBootstrap引用服务,客户端集群Cluster调用消费端调用器ConsumerInvoker实现Client发送数据给Server调用过程。

SOFARPC以基于Netty实现的网络通信框架SOFABolt用作远程通信框架,使用者不用关心如何实现私有协议的细节,直接使用内置RPC通信协议,启动客户端与服务端同时注册用户请求处理器即可完成远程调用:

1.调用方式

SOFARPC服务调用提供同步Sync、异步Future、回调Callback以及单向Oneway四种调用类型:

使用Future异步调用SOFABoot配置服务引用需要设置sofa:global-attrs元素的type属性声明调用方式为future:

如上设置为异步调用的方式。客户端获取响应结果有两种方式:

(1)通过 SofaResponseFuture直接获取结果。第一个参数是获取结果的超时时间,第二个参数表示是否清除线程上下文中的结果。

(2)获取原生Futrue,该种方式获取JDK原生的Future,参数表示是否清除线程上下文中的结果。因为响应结果放在JDK原生的Future,需要通过JDK Future的get()方法获取响应结果。

当前线程发起调用得到RpcResponseFuture对象,当前线程可以继续执行下一次调用。在任意时刻使用RpcResponseFuture对象的get()方法来获取结果,如果响应已经回来此时就马上得到结果;如果响应没有回来则阻塞住当前线程直到响应回来或者超时时间到。

(3)Callback回调调用

客户端提前设置回调实现类,在发起调用后不会等待结果,是真正的异步调用,永远不会阻塞线程,结果处理是在异步线程里执行。SOFA-RPC在获取到服务端的接口后会自动执行该回调实现,目前支持 bolt 协议。客户端回调类需要实现com.alipay.sofa.rpc.core.invoke.SofaResponseCallback接口:

如上设置是服务级别的设置,也可以进行调用级别的设置:

使用Callback回调调用SOFABoot配置服务引用需要设置sofa:global-attrs元素的type属性声明调用方式为callback,通过callback-ref属性声明回调的实现类,使用该服务引用发起调用时结果返回时由SOFARPC自动执行该回调类:

当前线程发起调用则本次调用马上结束执行下一次调用。发起调用时需要注册回调,该回调需要分配异步线程池以待响应回来后在回调的异步线程池来执行回调逻辑。

(4)Oneway单向调用

客户端发送请求后不会等待服务端返回的结果,并且会忽略服务端的处理结果,目前支持bolt协议:

使用Oneway单向调用SOFABoot配置服务引用需要设置sofa:global-attrs元素的type属性声明调用方式为oneway:

当前线程发起调用后,不关心调用结果不做超时控制,只要请求已经发出就完成本次调用。单向调用不关心响应结果,请求线程不会被阻塞,使用Oneway调用需要注意控制调用节奏防止压垮接收方。注意Oneway调用不保证成功,而且发起方无法知道调用结果。因此通常用于可以重试,或者定时通知类的场景,调用过程是有可能因为网络问题、机器故障等原因导致请求失败,业务场景需要能接受这样的异常场景才能够使用。

2.直连调用

SOFARPC支持指定地址进行调用的场景,设置直连地址即可:

3.泛化调用

SOFARPC泛化调用方式能够在客户端不需要依赖服务端的接口情况下发起调用,目前支持bolt协议。由于不知道服务端的接口,因此需要通过字符串的方式将服务端的接口,调用的方法,参数及结果类进行描述:

如上通过setGeneric设置该服务为泛化服务,设置服务方的接口名。以GenericService作为泛化服务,通过GenericService能够发起泛化调用。发起调用时需要传入方法名、方法类型、方法参数。如果参数或者返回结果在客户端也需要泛化表示则通过GenericObject来实现获取序列化结果等:

(1)接口描述:所有泛化调用都需要在服务引用的时候声明interface为com.alipay.sofa.rpc.api.GenericService,这是SOFARPC提供的类。真正的服务接口通过sofa:global-attrs元素的generic-interface属性声明完成接口的描述。

(2)参数描述:由于客户端没有调用服务的参数类,因此通过GenericObject进行描述,GenericObject持有MapString, Object类型的变量,能够通过GenericObject提供的对该变量的操作方法将参数类的属性放到Map以此来描述参数类。

(3)发起泛化调用:接口描述通过XML配置声明泛化引用的bean,通过该泛化引用能够发起服务调用,发起泛化调用的第一个参数就是方法名,第二个参数就是参数类的全类名,第三个参数就是描述参数类的 GenericObject。

(4)获取泛化结果:发起泛化调用如果客户端同样没有泛化结果的类,那么同样以GenericObject对调用结果进行描述,通过GenericObject的getField方法能够获取结果类的属性值,通过GenericObject的getType方法能够获取结果类的全类名。

(5)泛化调用示例:SOFARPC泛化调用完整的泛化调用方式:

源码解析

1.调用方式

参考sofa-rpc-boot-projects范例模块( com.alipay.sofa.rpc.samples.invoke ):

运行调用方式服务端范例类InvokeServerApplication查看调用方式服务端输出日志:

sofa-rpc源码分析 4-全链路追踪技术

一、简介

sofa-rpc的全链路追踪技术是基于 Sofa-Tracer 实现的,Sofa-Tracer是基于ZipKin(谷歌Dapper)实现的,Sofa-Tracer参考了ZipKin的Trace-span设计;提供了相应的异步处理机制,每次会将父线程的上下文复制到子线程,为了防止多此异步调用的场景下,上下文的串用,不会再客户端相应阶段才清理上下文,而是会提前清理。

sofa-rpc在Sofa-Tracer的基础上,提供了以下新的特性

二、源码分析

ClientProxyInvoker.invoke //客户端调用入口,Trace生产流程

三、总结

1.sofa-rpc的全链路追踪依靠sofa-trace实现,包含上述说的trace/span、数据采样设计、异步刷新机制等设计,核心功能还是在sofa-trace里

java中几种常用的RPC框架介绍

1、RMI(远程方法调用)2、Hessian(基于HTTP的远程方法调用)3、Dubbo(淘宝开源的基于TCP的RPC框架)

Android源码解析RPC系列(一)---Binder原理

看了几天的Binder,决定有必要写一篇博客,记录一下学习成果,Binder是Android中比较综合的一块知识了,目前的理解只限于JAVA层。首先Binder是干嘛用的?不用说,跨进程通信全靠它,操作系统的不同进程之间,数据不共享,对于每个进程来说,它都天真地以为自己独享了整个系统,完全不知道其他进程的存在,进程之间需要通信需要某种系统机制才能完成,在Android整个系统架构中,采用了大量的C/S架构的思想,所以Binder的作用就显得非常重要了,但是这种机制为什么是Binder呢?在Linux中的RPC方式有管道,消息队列,共享内存等,消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,这样就有两次拷贝过程。共享内存不需要拷贝,但控制复杂,难以使用。Binder是个折中的方案,只需要拷贝一次就行了。其次Binder的安全性比较好,好在哪里,在下还不是很清楚,基于安全性和传输的效率考虑,选择了Binder。Binder的英文意思是粘结剂,Binder对象是一个可以跨进程引用的对象,它的实体位于一个进程中,这个进程一般是Server端,该对象提供了一套方法用以实现对服务的请求,而它的引用却遍布于系统的各个进程(Client端)之中,这样Client通过Binder的引用访问Server,所以说,Binder就像胶水一样,把系统各个进程粘结在一起了,废话确实有点多。

为了从而保障了系统的安全和稳定,整个系统被划分成内核空间和用户空间

内核空间:独立于普通的应用程序,可以访问受保护的内存空间,有访问底层硬件设备的所有权限。

用户空间:相对与内核空间,上层运用程序所运行的空间就是用户空间,用户空间访问内核空间的唯一方式就是系统调用。一个4G的虚拟地址空间,其中3G是用户空间,剩余的1G是内核空间。如果一个用户空间想与另外一个用户空间进行通信,就需要内核模块支持,这个运行在内核空间的,负责各个用户进程通过Binder通信的内核模块叫做Binder驱动,虽然叫做Binder驱动,但是和硬件并没有什么关系,只是实现方式和设备驱动程序是一样的,提供了一些标准文件操作。

在写AIDL的时候,一般情况下,我们有两个进程,一个作为Server端提供某种服务,然后另外一个进程作为Client端,连接Server端之后,就 可以使用Server里面定义的服务。这种思想是一种典型的C/S的思想。值得注意的是Android系统中的Binder自身也是C/S的架构,也有Server端与Client端。一个大的C/S架构中,也有一个小的C/S架构。

先笼统的说一下,在整个Binder框架中,由系列组件组成,分别是Client、Server、ServiceManager和Binder驱动程序,其中Client、Server和ServiceManager运行在用户空间,Binder驱动程序运行内核空间。运行在用户空间中的Client、Server和ServiceManager,是在三个不同进程中的,Server进程中中定义了服务提供给Client进程使用,并且Server中有一个Binder实体,但是Server中定义的服务并不能直接被Client使用,它需要向ServiceManager注册,然后Client要用服务的时候,直接向ServiceManager要,ServiceManager返回一个Binder的替身(引用)给Client,这样Client就可以调用Server中的服务了。

场景 :进程A要调用进程B里面的一个draw方法处理图片。

分析 :在这种场景下,进程A作为Client端,进程B做为Server端,但是A/B不在同一个进程中,怎么来调用B进程的draw方法呢,首先进程B作为Server端创建了Binder实体,为其取一个字符形式,可读易记的名字,并将这个Binder连同名字以数据包的形式通过Binder驱动发送给ServiceManager,也就是向ServiceManager注册的过程,告诉ServiceManager,我是进程B,拥有图像处理的功能,ServiceManager从数据包中取出名字和引用以一个注册表的形式保留了Server进程的注册信息。为什么是以数据包的形式呢,因为这是两个进程,直接传递对象是不行滴,只能是一些描述信息。现在Client端进程A联系ServiceManager,说现在我需要进程B中图像处理的功能,ServiceManager从注册表中查到了这个Binder实体,但是呢,它并不是直接把这个Binder实体直接给Client,而是给了一个Binder实体的代理,或者说是引用,Client通过Binder的引用访问Server。分析到现在,有个关键的问题需要说一下,ServiceManager是一个进程,Server是另一个进程,Server向ServiceManager注册Binder必然会涉及进程间通信。当前实现的是进程间通信却又要用到进程间通信,这就好象蛋可以孵出鸡前提却是要找只鸡来孵蛋,确实是这样的,ServiceManager中预先有了一个自己的Binder对象(实体),就是那只鸡,然后Server有个Binder对象的引用,就是那个蛋,Server需要通过这个Binder的引用来实现Binder的注册。鸡就一只,蛋有很多,ServiceManager进程的Binder对象(实体)仅有一个,其他进程所拥有的全部都是它的代理。同样一个Server端Binder实体也应该只有一个,对应所有Client端全部都是它的代理。

我们再次理解一下Binder是什么?在Binder通信模型的四个角色里面;他们的代表都是“Binder”,一个Binder对象就代表了所有,包括了Server,Client,ServiceManager,这样,对于Binder通信的使用者而言,不用关心实现的细节。对Server来说,Binder指的是Binder实体,或者说是本地对象,对于Client来说,Binder指的是Binder代理对象,也就是Binder的引用。对于Binder驱动而言,在Binder对象进行跨进程传递的时候,Binder驱动会自动完成这两种类型的转换。

简单的总结一下,通过上面一大段的分析,一个Server在使用的时候需要经历三个阶段

1、定义一个AIDL文件

Game.aidl

GameManager .aidl

2、定义远端服务Service

在远程服务中的onBind方法,实现AIDL接口的具体方法,并且返回Binder对象

3、本地创建连接对象

以上就是一个远端服务的一般套路,如果是在两个进程中,就可以进程通信了,现在我们分析一下,这个通信的流程。重点是GameManager这个编译生成的类。

从类的关系来看,首先接口GameManager 继承 IInterface ,IInterface是一个接口,在GameManager内部有一个内部类Stub,Stub继承了Binder,(Binder实现了IBinder),并且实现了GameManager接口,在Stub中还有一个内部类Proxy,Proxy也实现了GameManager接口,一个整体的结构是这样的

现在的问题是,Stub是什么?Proxy又是什么?在上面说了在Binder通信模型的四个角色里面;他们的代表都是“Binder”,一个Binder对象就代表了所有,包括了Server,Clinet,ServiceManager,为了两个进程的通信,系统给予的内核支持是Binder,在抽象一点的说,Binder是系统开辟的一块内存空间,两个进程往这块空间里面读写数据就行了,Stub从Binder中读数据,Proxy向Binder中写数据,达到进程间通信的目的。首先我们分析Stub。

Stub 类继承了Binder ,说明了Stub有了跨进程传输的能力,实现了GameManager接口,说明它有了根据游戏ID查询一个游戏的能力。我们在bind一个Service之后,在onServiceConnecttion的回调里面,就是通过asInterface方法拿到一个远程的service的。

asInterface调用queryLocalInterface。

mDescriptor,mOwner其实是Binder的成员变量,Stub继承了Binder,在构造函数的时候,对着两个变量赋的值。

如果客户端和服务端是在一个进程中,那么其实queryLocalInterface获取的就是Stub对象,如果不在一个进程queryLocalInterface查询的对象肯定为null,因为不同进程有不同虚拟机,肯定查不到mOwner对象的,所以这时候其实是返回的Proxy对象了。拿到Stub对象后,通常在onServiceConnected中,就把这个对象转换成我们多定义AIDL接口。

比如我们这里会转换成GameManager,有了GameManager对象,就可以调用后querryGameById方法了。如果是一个进程,那直接调用的是自己的querryGameById方法,如果不是一个进程,那调用了就是代理的querryGameById方法了。

看到其中关键的一行是

mRemote就是一个IBinder对象,相对于Stub,Proxy 是组合关系(HAS-A),内部有一个IBinder对象mRemote,Stub是继承关系(IS-A),直接实现了IBinder接口。

transact是个native方法,最终还会回掉JAVA层的onTransact方法。

onTransact根据调用号(每个AIDL函数都有一个编号,在跨进程的时候,不会传递函数,而是传递编号指明调用哪个函数)调用相关函数;在这个例子里面,调用了Binder本地对象的querryGameById方法;这个方法将结果返回给驱动,驱动唤醒挂起的Client进程里面的线程并将结果返回。于是一次跨进程调用就完成了。

***Please accept mybest wishes for your happiness and success ! ***

如何实现一个简单的RPC框

0,服务接口定义---Echo.java

/*

* 定义了服务器提供的服务类型 */public interface Echo {    public String echo(String string);

}

一,客户端代码分析--实现类:MainClient.java

客户端实现包括:获得一个代理对象,并使用该代理对象调用服务器的服务。获取代理对象时,需要指定被代理的类(相当于服务器端提供的服务名),Server IP,Port,这样客户端就能找到服务端的服务了。

延伸:分布式环境下,Client如何打到Server的服务?---因为,在服务器中运行的某些服务不像标准服务有着固定的端口,如HTTP的80端口。

一种解决方法是:在运行服务的每台机器上都运行一个特殊的守护进程,该守护进程负责跟踪位于该机器中每一项服务所使用的端口;此外,守护进程还监听一个特定的已经端口,Client通过这个端口与守护进程联系,请求得到指定服务的端口。

复杂的RPC实现框架中,比如可以把服务注册到ZooKeeper中,Client也从ZooKeeper中查询服务。参考:一个更复杂的RPC框架实现

Echo echo = RPC.getProxy(Echo.class, "127.0.0.1", 20382);

System.out.println(echo.echo("hello,hello"));//使用代理对象调用服务器的服务.并将结果输出

二,服务器端分析--实现类:MainServer.java

服务器实现包括:创建一个服务器对象,将它能提供的服务注册,并启动进程监听客户端的连接

Server server = new RPC.RPCServer();        /*

* server 启动后,需要注册server端能够提供的服务,这样client使用 服务的名字、

* 服务器的IP、以及服务所运行的端口 来调用 server 的服务         */

server.register(Echo.class, RemoteEcho.class);//注册服务的名字

server.register(AnOtherEchoService.class, AnOtherEchoServiceImpl.class);

server.start();//启动server

三,服务器监听Client连接分析----实现类:Listener.java

当server.start()后,它要创建一个Listener对象,这是一个线程类,该线程用来监听Client连接。

public void start() {

System.out.println("启动服务器");

/*

* server 启动时,需要Listener监听是否有client的请求连接

* listener 是一个线程,由它来监听连接             */

listener = new Listener(this);            this.isRuning = true;

listener.start();//listener 是一个线程类,start()后会执行线程的run方法

}

其实,监听连接就是JAVA ServerSocket类和Socket类提供的相关功能而已。

/*

* accept()是一个阻塞方法,server_socket 一直等待client 是否有连接到来 */

Socket client = server_socket.accept();//建立一条TCP连接

四,动态代理对象 生成---RPC.java

客户端只需要编写生成代理对象,用代理对象去调用远程服务的代码即可。但是,底层的功能如:建立连接,序列化(本例中也没有考虑),跨语言调用(未考虑)...是由RPC框架完成的。

当MainClient 语句:RPC.getProxy(Echo.class, "127.0.0.1", 20382);执行时,会由

/*

* @param Class[]{} 该参数声明了动态生成的代理对象实现了的接口,即 clazz 所代表的接口类型 .

* 这表明了生成的代理对象它是一个它所实现了的接口类型的对象

* 从而就可以用它来调用它所实现的接口中定义的方法

*

* @param handler 生成代理实例对象时需要传递一个handler参数

* 这样当该 代理实例对象调用接口中定义的方法时,将会委托给InvocationHandler 接口中声明的invoke方法

* 此时,InvocationHandler 的invoke 方法将会被自动调用         */

T t = (T) Proxy.newProxyInstance(RPC.class.getClassLoader(), new Class[] {clazz}, handler);        return t;

返回该代理对象,然后就会委托第三个参数 handler 自动执行 invoke(),invoke将客户端调用的所有相关信息封装到Invocation 对象中(后面分析)。然后执行第16行代码发起连接。

1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 2                 Invocation invo = new Invocation(); 3                 invo.setInterfaces(clazz); 4                  5                 //利用反射机制将java.lang.reflect.Method 所代表的方法名,参数 封装到 Invocation invo对象中 6                 invo.setMethod(new org.jy.rpc.protocal.Method(method.getName(),method.getParameterTypes())); 7                 invo.setParams(args); 8                  9                 /*10                  * 当把需要调用的远程server端的方法名和参数封装到invo之后,Client 对象 就可以把 invo 作为参数 传递给服务器了.11                  * 为什么需要这样做呢?InvocationHandler 的invoke方法是自动执行的,在该方法里面,它根据生成的代理对象 proxy (第一个参数)12                  * 所实现的接口(由 Proxy.newProxyInstance()的第二个参数指定) 就可以知道这个接口中定义了哪些方法13                  * InvocationHandler 的 invoke 方法的第二个参数Method method 就可以解析出接口中的方法名和参数了14                  * 把它们封装进Invocation invo对象中,再将 invo 作为 client.invoke(invo)的参数 发送到服务器方15                  */16                 client.invoke(invo);//invoke 先调用init发起一个Socket连接,再将invo 发送至输出流中17                 return invo.getResult();18             }

五,“客户端存根”--Client.java

最重要的是它的 invoke方法(注意与InvocationHandler的invoke()区分)。它负责建立连接,打开输入、输出流,向服务器发送字节数据。

1     public void invoke(Invocation invo) throws UnknownHostException, IOException, ClassNotFoundException {2         init();3         System.out.println("写入数据");4         oos.writeObject(invo);//将Client 需要调用的Server的 接口、方法、参数 封装起来 发给服务器5         oos.flush();6         ois = new ObjectInputStream(socket.getInputStream());//用来接收从 server 返回 回来的执行结果 的输入流7         Invocation result = (Invocation) ois.readObject();8         invo.setResult(result.getResult());//将结果 保存到 Invocation result对象中9     }

六,“服务器存根“---实现类:RPCServer.java

上面提到,服务器通过Listener监听客户端连接,当建立客户端连接后,Socket client = server_socket.accept(); 不再阻塞,服务器调用它的call()方法完成客户端请求的功能。也即,客户端请求的结果实际上是在服务器执行生成的。返回的结果是在Client.java 的 invoke() 方法里被读取出来 。call()再一次用到了JAVA反射(第11行) 参考:JAVA动态代理

1 public void call(Invocation invo) { 2             System.out.println(invo.getClass().getName()); 3             Object obj = serviceEngine.get(invo.getInterfaces().getName()); 4             if(obj!=null) { 5                 try { 6                     Method m = obj.getClass().getMethod(invo.getMethod().getMethodName(), invo.getMethod().getParams()); 7                     /* 8                      * 利用JAVA反射机制来执行java.lang.reflect.Method 所代表的方法 9                      * @param result : 执行实际方法后 得到的 服务的执行结果10                      */11                     Object result = m.invoke(obj, invo.getParams());12                     invo.setResult(result);//将服务的执行结果封装到invo对象中。在后面的代码中,将该对象写入到输出流中13                 } catch (Throwable th) {14                     th.printStackTrace();15                 }16             } else {17                 throw new IllegalArgumentException("has no these class");18             }19         }

七,”RPC 编码、解码,协议的定义“---Invocation.java   Method.java

其实,这里并不是那种实用的开源RPC框架如Thrift中所指的编码、IDL……上面两个类只是RPC实现过程中辅助完成Java动态代理的实现,说白了就是封装客户端需要调用的方法,然后指定生成的代理对象需要实现的接口(服务).

八,总结:

先运行MainServer.java启动服务器,然后,再运行MainClient.java 启动一个客户端连接服务器就可以看到执行结果。

当需要添加新的服务时:按以下步骤即可:①定义服务接口及其实现类,如:AnOtherEchoService.java  ②:在MainServer.java中注册新添加的服务。

③:在MainClient.java中编写获得新服务的代理对象的代码,并用该代理对象调用新服务接口中声明的方法。

这样,在客户端就能够远程地调用服务器上的一个新服务了。

关于javarpc源码教学和java源码讲解的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。