Time Record: 2019-7-24
There are many subsystems in distributed systems, and data interaction between subsystems will be through remote procedure call ** (RPC)**. So what should we pay attention to in remote procedure call? In fact, the most important thing is the speed of transmission and use. The process of the data we transmit and the process of using the data we get. Since remote calls are inseparable from remote objects, how do our clients know about remote objects? How do remote objects make calls? How does the client know what this remote object looks like in memory? How to use this remote object?
The following java-based RMI describes the implementation of RPC. [Individuals have a lot of questions about the design of rpc, and it's hard not to be validated.)
Basic Realization Ideas
First, rmi is divided into server and client, that is, the server provides remote object, and the client obtains the data of the object according to an ID of the remote object, and then executes it on the client. The client requests the call, and the server returns the execution result to the client. The process here is executed directly through a certain type, which is simpler than the reset method and more convenient for data conversion. Here we mainly notice a few points.
1: Both the client and the server have actual classes (i.e. those that need to be invoked)
2: The server registers the corresponding services and is exposed
3: The client accesses the service correctly, and there are instances on the server side.
4: Proxy for client and server, that is, proxy for serialized data transmission
5: Data format [Object serialization? Is that all? ]
First look at a simple example and then analyze how the code is implemented.
Server
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(); } } }
Client
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(); } } }
Actual 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"); } }
Implementation results
Server side: Hello world Client: Hello world toll As you can see here, the specific method is to execute on the server side, and then return the returned data to the client side. Note: When making a remote call, it will determine what the return type of Method is, and if it is Void type, it will not communicate [here]. Can we make notices in this way? ]
Analysis of specific implementation process
We know how to store a specific object on the server side [how to find this object]. We mentioned Remote's interface before. This interface will be used to proxy the instance, otherwise how painful it will be if all the classes need to be processed. After the object is stored on the server side, how can the client discover this? Here's another key point. The object is given an ID, which the client can discover by using. So how do you call specific methods when you find this object? This is where you need to know the Method and the specific object, and then make a call to return the specific results to the client. We mentioned that there is a proxy object on the server side, and there is also a proxy on the client side that returns specific execution data. In this way, it feels that the client is executing, which is easy to understand.
Specific code analysis
Our Hello Interface Impl is inherited from Unicast RemoteObject, where the service has been exposed. Let's look at the specific code. The constructor of the parent class is called in the constructor of HelloInterfaceImpl, and a method of ** exportObject((Remote) this, port)** is called in the constructor of the parent class.
protected UnicastRemoteObject(int port) throws RemoteException { this.port = port; exportObject((Remote) this, port); //Here's what exposes specific Remote s. } |To the following method public static Remote exportObject(Remote obj, int port) throws RemoteException { return exportObject(obj, new UnicastServerRef(port)); } |To the following 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); } |To the following 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); } //Notice here, save remote in Target Target target = new Target(impl, this, stub, ref.getObjID(), permanent); ref.exportObject(target);//Expose hashToMethod_Map = hashToMethod_Maps.get(implClass);//Here, the method is saved as a Method object, and then the method name of the client is received and searched, and the Method object is returned. return stub; } |To the following public void exportObject(Target target) throws RemoteException { ep.exportObject(target); } |To the following 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(); //Here listener exposes this object service 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 { //Here, the target is kept in the server-side object table for queries to find specific objects. super.exportObject(target); ok = true; } finally { if (!ok) { synchronized (this) { decrementExportCount(); } } } } |Look at the listening section below. 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. */ //Given a Runnable [AcceptLoop], perform tasks 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); } } } |Now, look at the specific tasks. 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) { } } } |Keep looking executeAcceptLoop Method 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 { //After receiving the request, perform the task, that is, the client makes the request task and makes a method call. 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); } } } } |Let's keep looking. ConnectionHandler Method 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);//Processing specific transactions, finding the executed objects and calling methods, the rest are 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); } } } |Let's see. handleMessages Method 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 specific cal to make method calls 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 } } } |Let's see. serviceCall,How to find objects and execute corresponding methods public boolean serviceCall(final RemoteCall call) { try { /* read object id */ final Remote impl; ObjID id; try { id = ObjID.read(call.getInputStream());//Object ID passed by the client for object lookup } 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));//Find in the server-side object table and place the object in the object table corresponding to the previous one. 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);//Implementation of specific methods 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; } |We already know how to find the object. Look again. dispatch How to execute the method 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();//The method Key passed by the client } 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);//Find Method Objects for Specific Methods 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);//Obtain objects and methods for execution and get results } catch (InvocationTargetException e) { throw e.getTargetException(); } // marshal return value try { ObjectOutput out = call.getResultStream(true); Class<?> rtype = method.getReturnType(); if (rtype != void.class) {//Here is the return type judgment and the previous analysis correspondence. marshalValue(rtype, result, out);//Write objects to streams } } 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(); } }
We've found out from above that we expose objects, so what does Locate Registry do?
Registry is a remote interface to a simple remote object registry. It provides a way to store and retrieve remote object references bound to arbitrary string names. The bind, unbind and rebind methods are used to change the name binding in the registry, and the lookup and list methods are used to query the current name binding. In its typical use, Registry enables RMI client booting: it provides a simple way for clients to get an initial reference to a remote object. Therefore, remote object implementations of exported registries usually have known addresses, such as known ObjID and TCP port numbers (default is 1099).
The process on the server side is roughly so implemented, but there are many objects in it, so it needs to be carefully sorted out in the future with the new content.
On the server side, we know the method name and the id of the object sent by the client, so how do we do it?
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);//Tissue lookup method try { java.io.ObjectOutput out = call.getOutputStream(); out.writeObject($param_String_1);//Write the parameters of the request for the specific object name injected into the service gas terminal } 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();//Serialization to specific objects } 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); } } |The above is to get the specified object, and then call the method of the acquired object. How is this method organized? All execution methods are dealt with by proxy. //That is to call the remote object and return the result of remote execution. 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 For specific execution, the parameters are proxy classes, methods, parameters [corresponding to previous object exposure, used by the server to know how to adjust] 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; } } |The required parameters are organized and then passed to the server for invocation. 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();//When the communication is executed, the server receives the information, parses the method parameters, then calls the method [reflection] of the remote object, and then writes the result to the stream for return. try {//Return the result of execution 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); } } } |Regrettably, I don't know if there are any agents here. Debug[image cglib Medium Debug You can see the generated proxy class]
Summary: Here we find that both the client and the server encapsulate an interactive stub (skeleton) to operate the object, and then it is a parameter problem. How does the server know what method the client calls and how does the client know the existence of the server?
There are still some questions.
1: When the client finds the object, it will find the object. So we don't really need the object. We only need the method. Then this step may be cancelled.
2: When we get the remote object, if there is a reference count for gc at this time, how about this
3: The speed of transmission. If the returned data is too large, does serialization have compression or something like that? Or does it rely entirely on the data compression of transmission?
These problems need to be verified, RPC framework and other grpc,dubbo, etc. also need to be understood and compared.
Time Record: 2019-8-8