This is a simple application I designed recently to represents how remote method invocations is implemented in Java. This write up focuses on demonstrating how to enable remote operations in a client server in a java based environment, but without using standard JAVA RMI itself. This gives you more flexibility to design your own applications the way how you want to implement the code. In this very first article, I am presenting only fundamental infrastructure required to make remote method invocation possible. Complex functionalities will be discussed in later blogs. The source code of this application will be made available in near future . But Fist I’ll have to find some place to host them.;D
Distributed Objects?
Distributed objects which resides in a separate address spaces and methods of which can be triggered by a remote method call. A remote call is issued in an address space separate to the address space where the target object lives. A client is an application that requests the call from a target object, known as server object or mostly remote objects. Similarly, the process which serves the manipulation of the remote object is referred to as server.
General Architecture
Remote method invocation is established using client stub and a server skeleton which is the same approach used by java RMI. The stub class act as a proxy for the remote objects on the client side, and is responsible for creating remote call requests. The stub marshals the request in order to communicate with the server using server skeleton. The skeleton class that receives requests for method invocation from a stub, and invokes the method requested on the remote object. The skeleton maintains a table of remote object reference to remote object instances in order to uniquely identify which objects needs to be invoked for a given request.
The overall process of making a remote call can be described in the following order.
1. At the server side, server creates a remote object and binds it with the skeleton.
2. Skeleton listens to client connation by creating a serverSocket.
3. Client creates an instance of Stub
4. Client invoke local method invocation using the Stub (stub does the calling of remote method)
5. Stub prepares the method call, with the parameters and make a MethodRequest object.
6. Stub request a socket connection using the YourRemoteRef object. (Information about the connection is hardcoded into the YourRemoteRef class). Server must be running at this point to listen to client requests.
7. Stub serializes the request (marshalling) and creates an ObjectOutputStream to send the request object to the server.
8. Skeleton reads the request from ObjectInputStream of the socket, and reconstruct (unmarshalling) the request object.
9. Skeleton identifies the remote object reference using the object table.
10. Skeleton identifies the remote method call to invoke using the request object.
11. Skeleton invokes the remote method call and creates a MethodResponse object.
12. Skeleton serialize the response object, send it out to ObjectoutputStream.
13. Stub reads the input stream and reconstructs the response object.
14. Stub can return the result to the client or handle the response appropriately.
Key design decisions
YourRemoteRef class contains all the information needed to identify a remote object connection , including hostname, port number, class name and an id to uniquely identify objects in case, more than one object of the same class to be a remote objects. YourRemoteRef provides methods to make socket connection to the remote object.
Another design decision I opted in this application is returning a meaningful message back to the stub from server (skeleton) after a completion of each method invocation, even if the method itself is void type. For example, void addLogMessage(String msg) does not return anything to the client. In order to client know what happened at the server side, the response object encapsulates a meaningful message (as its return value) back to the client, so that client can be sure of the remote method invocations’ status.
API of developed Classes
Stub The Stub class that acts as a proxy for the remote object. The methods in this class are proxy methods that are responsible for performing the remote method invocation requests, i.e. they request a socket connection to be established with the skeleton using the YourRemoteRef and use the Marshalling and send the method request to the Skeleton, and then read any return result.
Skeleton Class that receives requests for method invocation from Stubs, and invokes the method requested on the remote object.
Marshalling This class is used to serialize / unserialise an object (and all objects within that object).
MethodRequest This class encapsulates everything needed to invoke a method, i.e. the method name, the remote object reference, the parameter list in order to make a remote request.
MethodResponse This class encapsulates response to a method call. It includes the method name, the remote object reference, return object.
YourRemoteRef This class is used to represent a remote object connection and encapsulate all of the information needed to identify a remote connection, i.e. a hostname, port number and a class name.
Observations
When I ran multiple clients with one server listening, I get the same log being shared by all the clients. That is because, at the current design there is no way to identify unique remote objects of the same class as all the clients are getting same YourRemoteRef object which is hardcoded with required details. If the client-A creates a new log and call addNewMessage(“Msg-1”) and Client-B also call addNewMassage(“Msg-2”), now both of the clients will get same result for readLog(), because both are sharing the same log at the server.
To avoid this, each object can be bind with a unique name (using className + id ) to identify each service. This way, separate clients can request for separate services, or can have its own log.
The design of Server used in this assignment is single threaded. A single threaded server would process incoming requests in the same thread that accepts the client connection. The draw back of this is that the clients can only connect to the server while the server is inside the serverSocket.accept() method call. If the server has to spend more time outside this serverSocket.accept() method call, it will increase the probability of more client requests to be denied.
If we use a multithreaded server, when the server listening thread receives a client request, the connection is handed over to a new thread to process the client request and the server continues to listen to more client requests. This makes the server thread that listens to client connection to spend most of its time on the method call serverSocket.accept() hence the less probability of clients being denied of service. But to implement that, we need to implement a data structure to identify each client requests separately so that we can send correct response to the clients once the method call is completed.
Another observation I made from this design is, after making a remote call and while the client is waiting for the reply it is in a blocking state until it receives a reply or JVM throws a time out and stops the execution in case of a delayed response. To deal with this, Future and Promises latency hiding approach will be used in later implementations.
No comments:
Post a Comment