时间记录:2019-7-24
在分布式系统中有很多的子系统,而子系统之间进行数据的交互会通过远程过程调用**(RPC)**来进行数据的交互的,那么在远程过程调用中需要注意到什么呢?其实最主要的就是传输的速度和使用的速度。我们传输的数据的过程和拿到数据进行使用的过程。既然是远程调用那么离不开远程的对象,那我们的客户机是怎么知道远程的对象呢?远程的对象怎么进行调用呢?客户机怎么知道这个远程对象在内存中是什么样子呢?如何使用这个远程对象呢?
以下基于java的RMI来描述RPC的实现【个人对rpc的设计有很多疑问,未得到验证很难受】
基本实现思路
首先rmi分为服务端和客户端,也就是服务端提供远程对象,而客户端根据远程对象的一个ID来获取到这个对象的数据,然后在客户端进行执行。客户端进行调用的请求,服务端将执行的结果返回给客户端。这里的过程是直接通过某类进行执行的,比reset方式跟简单,且对数据的转换跟方便。这里我们主要注意到几点。
1:客户端和服务端都存在实际的类(即需要被调用的)
2:服务端注册了对应的服务且被暴露出来
3:客户端对服务的访问正确,且服务端存在实例
4:客户端和服务端的代理,即进行序列化数据传输的代理
5:数据的格式【对象序列化?全部都是如此?】
先看简单例子然后具体分析代码如何实现的
服务端
package com.huo.rmi;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RmiServer {
public static void main(String[] args) {
try {
HelloInterface helloInterface = new HelloInterfaceImpl();
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
registry.bind("hello", helloInterface);
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端
package com.huo.rmi;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RmiClient {
public static void main(String[] args) {
try {
Registry registry = LocateRegistry.getRegistry("localhost");
HelloInterface remoteMath = (HelloInterface)registry.lookup("hello");
remoteMath.sayHello();
}catch(Exception e) {
e.printStackTrace();
}
}
}
实际的Remote
package com.huo.rmi;
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface HelloInterface extends Remote {
public void sayHello() throws RemoteException;;
}
package com.huo.rmi;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
public class HelloInterfaceImpl extends UnicastRemoteObject implements HelloInterface{
protected HelloInterfaceImpl() throws RemoteException {
super();
// TODO Auto-generated constructor stub
}
@Override
public void sayHello() throws RemoteException{
// TODO Auto-generated method stub
System.out.println("Hello world");
}
}
执行结果显示
服务端:
Hello world
客户端:
Hello world toll
由这里可以知道,具体的方法是在服务端进行执行的,然后将return的数据返回到客户端
注:在进行远程调用的时候会判断Method的返回类型是什么,如果是Void类型那么不进行通信【这里
这种方式我们是不是可以做成通知类的东西?】
具体的执行过程分析
我们知道在服务端保存了一个具体的对象,那么这个对象是怎么存储在服务端的呢【如何去发现这个对象】,我们之前提到了Remote的接口,这里会通过这个接口对实例进行一个代理,不然如果需要处理所有的类多么痛苦。对象存储在服务端了之后,那么客户端如何发现这个呢?这里就有另外一个关键的点,对象给予一个ID,客户端在使用这个ID发现就可以。那么发现了这个对象后是怎么调用具体的方法的呢?这是就需要知道Method和具体的对象,然后进行调用,将具体的结果返回给客户端。我们说到服务端存在一个代理对象,同样的客户端也存在有个代理将具体的执行数据返回。这样就感觉是客户端在进行执行,让人方便理解。
具体的代码分析
我们的HelloInterfaceImpl是继承自UnicastRemoteObject,在这里就已经将服务进行暴露出来了,我们看具体的代码。在HelloInterfaceImpl的构造函数中会调用父类的构造函数,在父类的构造函数中会调用**exportObject((Remote) this, port)**的一个方法
protected UnicastRemoteObject(int port) throws RemoteException
{
this.port = port;
exportObject((Remote) this, port); //这里就是将具体的Remote暴露出来的
}
|到下面的方法
public static Remote exportObject(Remote obj, int port)
throws RemoteException
{
return exportObject(obj, new UnicastServerRef(port));
}
|到下面
private static Remote exportObject(Remote obj, UnicastServerRef sref)
throws RemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null, false);
}
|到下面
public Remote exportObject(Remote impl, Object data,
boolean permanent)
throws RemoteException
{
Class<?> implClass = impl.getClass();
Remote stub;
try {
stub = Util.createProxy(implClass, getClientRef(), forceStubUse);
} catch (IllegalArgumentException e) {
throw new ExportException(
"remote object implements illegal remote interface", e);
}
if (stub instanceof RemoteStub) {
setSkeleton(impl);
}
//注意看这里,将remote保存在Target中
Target target =
new Target(impl, this, stub, ref.getObjID(), permanent);
ref.exportObject(target);//暴露
hashToMethod_Map = hashToMethod_Maps.get(implClass);//这里将方法保存成Method对象,后面在收到客户端的方法名然后进行查找,返回Method对象
return stub;
}
|到下面
public void exportObject(Target target) throws RemoteException {
ep.exportObject(target);
}
|到下面
public void exportObject(Target target) throws RemoteException {
/*
* Ensure that a server socket is listening, and count this
* export while synchronized to prevent the server socket from
* being closed due to concurrent unexports.
*/
synchronized (this) {
listen(); //这里的listener就是将这个对象服务暴露出去
exportCount++;
}
/*
* Try to add the Target to the exported object table; keep
* counting this export (to keep server socket open) only if
* that succeeds.
*/
boolean ok = false;
try {
//这里是将target保留在服务端的对象表中,供查询找到具体的对象
super.exportObject(target);
ok = true;
} finally {
if (!ok) {
synchronized (this) {
decrementExportCount();
}
}
}
}
|到下面 看监听部分
private void listen() throws RemoteException {
assert Thread.holdsLock(this);
TCPEndpoint ep = getEndpoint();
int port = ep.getPort();
if (server == null) {
if (tcpLog.isLoggable(Log.BRIEF)) {
tcpLog.log(Log.BRIEF,
"(port " + port + ") create server socket");
}
try {
server = ep.newServerSocket();
/*
* Don't retry ServerSocket if creation fails since
* "port in use" will cause export to hang if an
* RMIFailureHandler is not installed.
*/
//给定一个Runnable【AcceptLoop】,执行任务
Thread t = AccessController.doPrivileged(
new NewThreadAction(new AcceptLoop(server),
"TCP Accept-" + port, true));
t.start();
} catch (java.net.BindException e) {
throw new ExportException("Port already in use: " + port, e);
} catch (IOException e) {
throw new ExportException("Listen failed on port: " + port, e);
}
} else {
// otherwise verify security access to existing server socket
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkListen(port);
}
}
}
|到下面,看具体的任务
public void run() {
try {
executeAcceptLoop();
} finally {
try {
/*
* Only one accept loop is started per server
* socket, so after no more connections will be
* accepted, ensure that the server socket is no
* longer listening.
*/
serverSocket.close();
} catch (IOException e) {
}
}
}
|继续看executeAcceptLoop方法
private void executeAcceptLoop() {
if (tcpLog.isLoggable(Log.BRIEF)) {
tcpLog.log(Log.BRIEF, "listening on port " +
getEndpoint().getPort());
}
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
/*
* Find client host name (or "0.0.0.0" if unknown)
*/
InetAddress clientAddr = socket.getInetAddress();
String clientHost = (clientAddr != null
? clientAddr.getHostAddress()
: "0.0.0.0");
/*
* Execute connection handler in the thread pool,
* which uses non-system threads.
*/
try {
//收到请求后进行执行任务,也就是客户端进行请求的任务,进行某个方法的调用
connectionThreadPool.execute(
new ConnectionHandler(socket, clientHost));
} catch (RejectedExecutionException e) {
closeSocket(socket);
tcpLog.log(Log.BRIEF,
"rejected connection from " + clientHost);
}
} catch (Throwable t) {
try {
/*
* If the server socket has been closed, such
* as because there are no more exported
* objects, then we expect accept to throw an
* exception, so just terminate normally.
*/
if (serverSocket.isClosed()) {
break;
}
try {
if (tcpLog.isLoggable(Level.WARNING)) {
tcpLog.log(Level.WARNING,
"accept loop for " + serverSocket +
" throws", t);
}
} catch (Throwable tt) {
}
} finally {
/*
* Always close the accepted socket (if any)
* if an exception occurs, but only after
* logging an unexpected exception.
*/
if (socket != null) {
closeSocket(socket);
}
}
/*
* In case we're running out of file descriptors,
* release resources held in caches.
*/
if (!(t instanceof SecurityException)) {
try {
TCPEndpoint.shedConnectionCaches();
} catch (Throwable tt) {
}
}
/*
* A NoClassDefFoundError can occur if no file
* descriptors are available, in which case this
* loop should not terminate.
*/
if (t instanceof Exception ||
t instanceof OutOfMemoryError ||
t instanceof NoClassDefFoundError)
{
if (!continueAfterAcceptFailure(t)) {
return;
}
// continue loop
} else if (t instanceof Error) {
throw (Error) t;
} else {
throw new UndeclaredThrowableException(t);
}
}
}
}
|我们继续看ConnectionHandler方法
private void run0() {
TCPEndpoint endpoint = getEndpoint();
int port = endpoint.getPort();
threadConnectionHandler.set(this);
// set socket to disable Nagle's algorithm (always send
// immediately)
// TBD: should this be left up to socket factory instead?
try {
socket.setTcpNoDelay(true);
} catch (Exception e) {
// if we fail to set this, ignore and proceed anyway
}
// set socket to timeout after excessive idle time
try {
if (connectionReadTimeout > 0)
socket.setSoTimeout(connectionReadTimeout);
} catch (Exception e) {
// too bad, continue anyway
}
try {
InputStream sockIn = socket.getInputStream();
InputStream bufIn = sockIn.markSupported()
? sockIn
: new BufferedInputStream(sockIn);
// Read magic (or HTTP wrapper)
bufIn.mark(4);
DataInputStream in = new DataInputStream(bufIn);
int magic = in.readInt();
if (magic == POST) {
if (disableIncomingHttp) {
throw new RemoteException("RMI over HTTP is disabled");
}
tcpLog.log(Log.BRIEF, "decoding HTTP-wrapped call");
// It's really a HTTP-wrapped request. Repackage
// the socket in a HttpReceiveSocket, reinitialize
// sockIn and in, and reread magic.
bufIn.reset(); // unread "POST"
try {
socket = new HttpReceiveSocket(socket, bufIn, null);
remoteHost = "0.0.0.0";
sockIn = socket.getInputStream();
bufIn = new BufferedInputStream(sockIn);
in = new DataInputStream(bufIn);
magic = in.readInt();
} catch (IOException e) {
throw new RemoteException("Error HTTP-unwrapping call",
e);
}
}
// bufIn's mark will invalidate itself when it overflows
// so it doesn't have to be turned off
// read and verify transport header
short version = in.readShort();
if (magic != TransportConstants.Magic ||
version != TransportConstants.Version) {
// protocol mismatch detected...
// just close socket: this would recurse if we marshal an
// exception to the client and the protocol at other end
// doesn't match.
closeSocket(socket);
return;
}
OutputStream sockOut = socket.getOutputStream();
BufferedOutputStream bufOut =
new BufferedOutputStream(sockOut);
DataOutputStream out = new DataOutputStream(bufOut);
int remotePort = socket.getPort();
if (tcpLog.isLoggable(Log.BRIEF)) {
tcpLog.log(Log.BRIEF, "accepted socket from [" +
remoteHost + ":" + remotePort + "]");
}
TCPEndpoint ep;
TCPChannel ch;
TCPConnection conn;
// send ack (or nack) for protocol
byte protocol = in.readByte();
switch (protocol) {
case TransportConstants.SingleOpProtocol:
// no ack for protocol
// create dummy channel for receiving messages
ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
endpoint.getClientSocketFactory(),
endpoint.getServerSocketFactory());
ch = new TCPChannel(TCPTransport.this, ep);
conn = new TCPConnection(ch, socket, bufIn, bufOut);
// read input messages
handleMessages(conn, false);//处理具体的事务,找到执行的对象然后进行方法的调用,其余均是
break;
case TransportConstants.StreamProtocol:
// send ack
out.writeByte(TransportConstants.ProtocolAck);
// suggest endpoint (in case client doesn't know host name)
if (tcpLog.isLoggable(Log.VERBOSE)) {
tcpLog.log(Log.VERBOSE, "(port " + port +
") " + "suggesting " + remoteHost + ":" +
remotePort);
}
out.writeUTF(remoteHost);
out.writeInt(remotePort);
out.flush();
// read and discard (possibly bogus) endpoint
// REMIND: would be faster to read 2 bytes then skip N+4
String clientHost = in.readUTF();
int clientPort = in.readInt();
if (tcpLog.isLoggable(Log.VERBOSE)) {
tcpLog.log(Log.VERBOSE, "(port " + port +
") client using " + clientHost + ":" + clientPort);
}
// create dummy channel for receiving messages
// (why not use clientHost and clientPort?)
ep = new TCPEndpoint(remoteHost, socket.getLocalPort(),
endpoint.getClientSocketFactory(),
endpoint.getServerSocketFactory());
ch = new TCPChannel(TCPTransport.this, ep);
conn = new TCPConnection(ch, socket, bufIn, bufOut);
// read input messages
handleMessages(conn, true);
break;
case TransportConstants.MultiplexProtocol:
if (tcpLog.isLoggable(Log.VERBOSE)) {
tcpLog.log(Log.VERBOSE, "(port " + port +
") accepting multiplex protocol");
}
// send ack
out.writeByte(TransportConstants.ProtocolAck);
// suggest endpoint (in case client doesn't already have one)
if (tcpLog.isLoggable(Log.VERBOSE)) {
tcpLog.log(Log.VERBOSE, "(port " + port +
") suggesting " + remoteHost + ":" + remotePort);
}
out.writeUTF(remoteHost);
out.writeInt(remotePort);
out.flush();
// read endpoint client has decided to use
ep = new TCPEndpoint(in.readUTF(), in.readInt(),
endpoint.getClientSocketFactory(),
endpoint.getServerSocketFactory());
if (tcpLog.isLoggable(Log.VERBOSE)) {
tcpLog.log(Log.VERBOSE, "(port " +
port + ") client using " +
ep.getHost() + ":" + ep.getPort());
}
ConnectionMultiplexer multiplexer;
synchronized (channelTable) {
// create or find channel for this endpoint
ch = getChannel(ep);
multiplexer =
new ConnectionMultiplexer(ch, bufIn, sockOut,
false);
ch.useMultiplexer(multiplexer);
}
multiplexer.run();
break;
default:
// protocol not understood, send nack and close socket
out.writeByte(TransportConstants.ProtocolNack);
out.flush();
break;
}
} catch (IOException e) {
// socket in unknown state: destroy socket
tcpLog.log(Log.BRIEF, "terminated with exception:", e);
} finally {
closeSocket(socket);
}
}
}
|我们来看handleMessages方法
void handleMessages(Connection conn, boolean persistent) {
int port = getEndpoint().getPort();
try {
DataInputStream in = new DataInputStream(conn.getInputStream());
do {
int op = in.read(); // transport op
if (op == -1) {
if (tcpLog.isLoggable(Log.BRIEF)) {
tcpLog.log(Log.BRIEF, "(port " +
port + ") connection closed");
}
break;
}
if (tcpLog.isLoggable(Log.BRIEF)) {
tcpLog.log(Log.BRIEF, "(port " + port +
") op = " + op);
}
switch (op) {
case TransportConstants.Call:
// service incoming RMI call 具体的cal,进行方法的调用
RemoteCall call = new StreamRemoteCall(conn);
if (serviceCall(call) == false)
return;
break;
case TransportConstants.Ping:
// send ack for ping
DataOutputStream out =
new DataOutputStream(conn.getOutputStream());
out.writeByte(TransportConstants.PingAck);
conn.releaseOutputStream();
break;
case TransportConstants.DGCAck:
DGCAckHandler.received(UID.read(in));
break;
default:
throw new IOException("unknown transport op " + op);
}
} while (persistent);
} catch (IOException e) {
// exception during processing causes connection to close (below)
if (tcpLog.isLoggable(Log.BRIEF)) {
tcpLog.log(Log.BRIEF, "(port " + port +
") exception: ", e);
}
} finally {
try {
conn.close();
} catch (IOException ex) {
// eat exception
}
}
}
|我们来看serviceCall,是如何找到对象的并执行对应的方法的
public boolean serviceCall(final RemoteCall call) {
try {
/* read object id */
final Remote impl;
ObjID id;
try {
id = ObjID.read(call.getInputStream());//客户端传过来的对象ID,用于对象的查找
} catch (java.io.IOException e) {
throw new MarshalException("unable to read objID", e);
}
/* get the remote object */
Transport transport = id.equals(dgcID) ? null : this;
Target target =
ObjectTable.getTarget(new ObjectEndpoint(id, transport));//在服务器端的对象表中进行查找,对应前面的将对象放到对象表中
if (target == null || (impl = target.getImpl()) == null) {
throw new NoSuchObjectException("no such object in table");
}
final Dispatcher disp = target.getDispatcher();
target.incrementCallCount();
try {
/* call the dispatcher */
transportLog.log(Log.VERBOSE, "call dispatcher");
final AccessControlContext acc =
target.getAccessControlContext();
ClassLoader ccl = target.getContextClassLoader();
ClassLoader savedCcl = Thread.currentThread().getContextClassLoader();
try {
setContextClassLoader(ccl);
currentTransport.set(this);
try {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedExceptionAction<Void>() {
public Void run() throws IOException {
checkAcceptPermission(acc);
disp.dispatch(impl, call);//执行具体的方法
return null;
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (IOException) pae.getException();
}
} finally {
setContextClassLoader(savedCcl);
currentTransport.set(null);
}
} catch (IOException ex) {
transportLog.log(Log.BRIEF,
"exception thrown by dispatcher: ", ex);
return false;
} finally {
target.decrementCallCount();
}
} catch (RemoteException e) {
// if calls are being logged, write out exception
if (UnicastServerRef.callLog.isLoggable(Log.BRIEF)) {
// include client host name if possible
String clientHost = "";
try {
clientHost = "[" +
RemoteServer.getClientHost() + "] ";
} catch (ServerNotActiveException ex) {
}
String message = clientHost + "exception: ";
UnicastServerRef.callLog.log(Log.BRIEF, message, e);
}
/* We will get a RemoteException if either a) the objID is
* not readable, b) the target is not in the object table, or
* c) the object is in the midst of being unexported (note:
* NoSuchObjectException is thrown by the incrementCallCount
* method if the object is being unexported). Here it is
* relatively safe to marshal an exception to the client
* since the client will not have seen a return value yet.
*/
try {
ObjectOutput out = call.getResultStream(false);
UnicastServerRef.clearStackTraces(e);
out.writeObject(e);
call.releaseOutputStream();
} catch (IOException ie) {
transportLog.log(Log.BRIEF,
"exception thrown marshalling exception: ", ie);
return false;
}
}
return true;
}
|我们已经知道怎么找到对象的,再看dispatch是如何执行方法的
public void dispatch(Remote obj, RemoteCall call) throws IOException {
// positive operation number in 1.1 stubs;
// negative version number in 1.2 stubs and beyond...
int num;
long op;
try {
// read remote call header
ObjectInput in;
try {
in = call.getInputStream();
num = in.readInt();
} catch (Exception readEx) {
throw new UnmarshalException("error unmarshalling call header",
readEx);
}
if (skel != null) {
// If there is a skeleton, use it
oldDispatch(obj, call, num);
return;
} else if (num >= 0){
throw new UnmarshalException(
"skeleton class not found but required for client version");
}
try {
op = in.readLong();//客户端传递过来的方法Key
} catch (Exception readEx) {
throw new UnmarshalException("error unmarshalling call header",
readEx);
}
/*
* Since only system classes (with null class loaders) will be on
* the execution stack during parameter unmarshalling for the 1.2
* stub protocol, tell the MarshalInputStream not to bother trying
* to resolve classes using its superclasses's default method of
* consulting the first non-null class loader on the stack.
*/
MarshalInputStream marshalStream = (MarshalInputStream) in;
marshalStream.skipDefaultResolveClass();
Method method = hashToMethod_Map.get(op);//查找具体的方法的Method对象
if (method == null) {
throw new UnmarshalException("unrecognized method hash: " +
"method not supported by remote object");
}
// if calls are being logged, write out object id and operation
logCall(obj, method);
// unmarshal parameters
Object[] params = null;
try {
unmarshalCustomCallData(in);
params = unmarshalParameters(obj, method, marshalStream);
} catch (AccessException aex) {
// For compatibility, AccessException is not wrapped in UnmarshalException
// disable saving any refs in the inputStream for GC
((StreamRemoteCall) call).discardPendingRefs();
throw aex;
} catch (java.io.IOException | ClassNotFoundException e) {
// disable saving any refs in the inputStream for GC
((StreamRemoteCall) call).discardPendingRefs();
throw new UnmarshalException(
"error unmarshalling arguments", e);
} finally {
call.releaseInputStream();
}
// make upcall on remote object
Object result;
try {
result = method.invoke(obj, params);//获取到对象和方法进行执行,得到结果
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
// marshal return value
try {
ObjectOutput out = call.getResultStream(true);
Class<?> rtype = method.getReturnType();
if (rtype != void.class) {//这里就是进行返回类型判断和之前的分析对应。
marshalValue(rtype, result, out);//将对象写入流
}
} catch (IOException ex) {
throw new MarshalException("error marshalling return", ex);
/*
* This throw is problematic because when it is caught below,
* we attempt to marshal it back to the client, but at this
* point, a "normal return" has already been indicated,
* so marshalling an exception will corrupt the stream.
* This was the case with skeletons as well; there is no
* immediately obvious solution without a protocol change.
*/
}
} catch (Throwable e) {
Throwable origEx = e;
logCallException(e);
ObjectOutput out = call.getResultStream(false);
if (e instanceof Error) {
e = new ServerError(
"Error occurred in server thread", (Error) e);
} else if (e instanceof RemoteException) {
e = new ServerException(
"RemoteException occurred in server thread",
(Exception) e);
}
if (suppressStackTraces) {
clearStackTraces(e);
}
out.writeObject(e);
// AccessExceptions should cause Transport.serviceCall
// to flag the connection as unusable.
if (origEx instanceof AccessException) {
throw new IOException("Connection is not reusable", origEx);
}
} finally {
call.releaseInputStream(); // in case skeleton doesn't
call.releaseOutputStream();
}
}
我们从上面已经发现了,将对象暴露出去了,那么LocateRegistry是干什么的呢?
Registry 是简单远程对象注册表的一个远程接口,它提供存储和检索绑定了任意字符串名称的远程对象引用的方法。bind、unbind 和 rebind 方法用于改变注册表中的名称绑定,lookup 和 list 方法用于查询当前的名称绑定。在其典型用法中,Registry 启用 RMI 客户机引导:它为客户机提供获得对远程对象的初始引用的简单方式。因此,导出的注册表的远程对象实现通常具有已知的地址,如具有已知的 ObjID 和 TCP端口号(默认为 1099)。
服务端的流程大致如此实现,但是其中的对象很多,还需要仔细整理后续会跟新具体内容
服务端我们了解到客户端发送过来的方法名和对象的id,那么是如何做的呢
public java.rmi.Remote lookup(java.lang.String $param_String_1)
throws java.rmi.AccessException, java.rmi.NotBoundException, java.rmi.RemoteException {
try {
java.rmi.server.RemoteCall call = ref.newCall((java.rmi.server.RemoteObject) this, operations, 2, interfaceHash);//组织lookup方法
try {
java.io.ObjectOutput out = call.getOutputStream();
out.writeObject($param_String_1);//写入请求的参数,为在服务气端注入的具体对象名
} catch (java.io.IOException e) {
throw new java.rmi.MarshalException("error marshalling arguments", e);
}
ref.invoke(call);
java.rmi.Remote $result;
try {
java.io.ObjectInput in = call.getInputStream();
$result = (java.rmi.Remote) in.readObject();//序列化到具体的对象
} catch (java.io.IOException e) {
throw new java.rmi.UnmarshalException("error unmarshalling return", e);
} catch (java.lang.ClassNotFoundException e) {
throw new java.rmi.UnmarshalException("error unmarshalling return", e);
} finally {
ref.done(call);
}
return $result;
} catch (java.lang.RuntimeException e) {
throw e;
} catch (java.rmi.RemoteException e) {
throw e;
} catch (java.rmi.NotBoundException e) {
throw e;
} catch (java.lang.Exception e) {
throw new java.rmi.UnexpectedException("undeclared checked exception", e);
}
}
|以上为在获取到指定的对象,然后就是对获取到的对象的方法进行调用,那么这个方法的怎么组织的呢?通过代理的方式将所有的执行方法代理处理,
也就是调用远程的对象,然后将远程执行的结果返回
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
if (! Proxy.isProxyClass(proxy.getClass())) {
throw new IllegalArgumentException("not a proxy");
}
if (Proxy.getInvocationHandler(proxy) != this) {
throw new IllegalArgumentException("handler mismatch");
}
if (method.getDeclaringClass() == Object.class) {
return invokeObjectMethod(proxy, method, args);
} else if ("finalize".equals(method.getName()) && method.getParameterCount() == 0 &&
!allowFinalizeInvocation) {
return null; // ignore
} else {
return invokeRemoteMethod(proxy, method, args);
}
}
|invokeRemoteMethod为具体的执行,参数为代理类,方法,参数【与之前的对象暴露相对应,用于服务端知道怎么调】
private Object invokeRemoteMethod(Object proxy,
Method method,
Object[] args)
throws Exception
{
try {
if (!(proxy instanceof Remote)) {
throw new IllegalArgumentException(
"proxy not Remote instance");
}
return ref.invoke((Remote) proxy, method, args,
getMethodHash(method));
} catch (Exception e) {
if (!(e instanceof RuntimeException)) {
Class<?> cl = proxy.getClass();
try {
method = cl.getMethod(method.getName(),
method.getParameterTypes());
} catch (NoSuchMethodException nsme) {
throw (IllegalArgumentException)
new IllegalArgumentException().initCause(nsme);
}
Class<?> thrownType = e.getClass();
for (Class<?> declaredType : method.getExceptionTypes()) {
if (declaredType.isAssignableFrom(thrownType)) {
throw e;
}
}
e = new UnexpectedException("unexpected exception", e);
}
throw e;
}
}
|将需要的参数组织起来,然后传到服务端进行调用
public Object invoke(Remote obj,
Method method,
Object[] params,
long opnum)
throws Exception
{
if (clientRefLog.isLoggable(Log.VERBOSE)) {
clientRefLog.log(Log.VERBOSE, "method: " + method);
}
if (clientCallLog.isLoggable(Log.VERBOSE)) {
logClientCall(obj, method);
}
Connection conn = ref.getChannel().newConnection();
RemoteCall call = null;
boolean reuse = true;
/* If the call connection is "reused" early, remember not to
* reuse again.
*/
boolean alreadyFreed = false;
try {
if (clientRefLog.isLoggable(Log.VERBOSE)) {
clientRefLog.log(Log.VERBOSE, "opnum = " + opnum);
}
// create call context
call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum);
// marshal parameters
try {
ObjectOutput out = call.getOutputStream();
marshalCustomCallData(out);
Class<?>[] types = method.getParameterTypes();
for (int i = 0; i < types.length; i++) {
marshalValue(types[i], params[i], out);
}
} catch (IOException e) {
clientRefLog.log(Log.BRIEF,
"IOException marshalling arguments: ", e);
throw new MarshalException("error marshalling arguments", e);
}
// unmarshal return
call.executeCall();//执行通信,服务端接收到信息后,会解析方法参数然调用远程对象的方法【反射】,然后将结果写到流进行返回
try {//将执行的结果进行返回
Class<?> rtype = method.getReturnType();
if (rtype == void.class)
return null;
ObjectInput in = call.getInputStream();
/* StreamRemoteCall.done() does not actually make use
* of conn, therefore it is safe to reuse this
* connection before the dirty call is sent for
* registered refs.
*/
Object returnValue = unmarshalValue(rtype, in);
/* we are freeing the connection now, do not free
* again or reuse.
*/
alreadyFreed = true;
/* if we got to this point, reuse must have been true. */
clientRefLog.log(Log.BRIEF, "free connection (reuse = true)");
/* Free the call's connection early. */
ref.getChannel().free(conn, true);
return returnValue;
} catch (IOException | ClassNotFoundException e) {
// disable saving any refs in the inputStream for GC
((StreamRemoteCall)call).discardPendingRefs();
clientRefLog.log(Log.BRIEF,
e.getClass().getName() + " unmarshalling return: ", e);
throw new UnmarshalException("error unmarshalling return", e);
} finally {
try {
call.done();
} catch (IOException e) {
/* WARNING: If the conn has been reused early,
* then it is too late to recover from thrown
* IOExceptions caught here. This code is relying
* on StreamRemoteCall.done() not actually
* throwing IOExceptions.
*/
reuse = false;
}
}
} catch (RuntimeException e) {
/*
* Need to distinguish between client (generated by the
* invoke method itself) and server RuntimeExceptions.
* Client side RuntimeExceptions are likely to have
* corrupted the call connection and those from the server
* are not likely to have done so. If the exception came
* from the server the call connection should be reused.
*/
if ((call == null) ||
(((StreamRemoteCall) call).getServerException() != e))
{
reuse = false;
}
throw e;
} catch (RemoteException e) {
/*
* Some failure during call; assume connection cannot
* be reused. Must assume failure even if ServerException
* or ServerError occurs since these failures can happen
* during parameter deserialization which would leave
* the connection in a corrupted state.
*/
reuse = false;
throw e;
} catch (Error e) {
/* If errors occurred, the connection is most likely not
* reusable.
*/
reuse = false;
throw e;
} finally {
/* alreadyFreed ensures that we do not log a reuse that
* may have already happened.
*/
if (!alreadyFreed) {
if (clientRefLog.isLoggable(Log.BRIEF)) {
clientRefLog.log(Log.BRIEF, "free connection (reuse = " +
reuse + ")");
}
ref.getChannel().free(conn, reuse);
}
}
}
|这里比较遗憾的代理类不知道有没有Debug【像cglib中的Debug可以看到生成的代理类】
总结: 这里我们发现在客户端和服务端都封装了一个进行交互的东西【stub,skeleton】,进行对象的操作,然后就是一个参数的问题,服务器端怎么知道客户端要调用什么样的方法,客户端怎么知道服务器端的存在。
这里还是有一些的疑问,
1:客户端进行对象的查找的时候会找到对象,那么我们其实不一定需要这个对象,我们只需要其中的方法,那么这一步可能取消
2:我们拿到远程对象的时候这个时候对gc来说是否存在引用的计数,那这个怎么算
3:传输的速度问题,如果返回的数据过大,那序列化存在压缩之类的东西吗,还是完全依靠传输的数据压缩
这些问题还需要验证,RPC框架还有其他的grpc,dubbo等还需要了解和对比。
时间记录:2019-8-8