|
Non-RMI Proxy for FileClassifierMany client-server programs communicate by message passing, often using a TCP socket. The two sides need to have an agreed-upon protocol; that is, they must have a standard set of message formats and know what messages to receive and what replies to send at any time. Jini can be used in this sort of case by providing a wrapper around the client and server, and making them available as a Jini service. The original client then becomes a proxy agent for the server and is distributed to Jini clients for execution. The original server runs within the Jini server and performs the real work of the service, just as in the thin proxy model. What differs is the class structure and how the components communicate. The proxy and the service do not need to belong to the same class, or even share common superclasses. Unlike the RMI case, the proxy is not derived from the service, so they do not have a shared class structure. The proxy and the service are written independently, using their own appropriate class hierarchies. However, the proxy still has to implement the FileClassifier interface, since that is what the client is asking for and the proxy is delivering. If RMI is not used, then any other distributed communication mechanism can be employed. Typically client-server systems will use something like reliable TCP ports ”this is not the only choice, but it is the one used in this example. Thus, the service listens on an agreed-upon port, the client connects to this port, and they exchange messages. The message format adopted for this solution is really simple:
The proxy will then use this reply to either return null or a new MIMEType object. FileClassifierProxyThe proxy object will be exported completely to a Jini client, such as TestFileClassifier . When this client calls the getMIMEType() method, the proxy opens up a connection to the service on an agreed-upon TCP port and exchanges messages on this port. It then returns a suitable result. The code looks like this: package socket; import common.FileClassifier; import common.MIMEType; import java.net.Socket; import java.io.Serializable; import java.io.IOException; import java.rmi.Naming; import java.io.*; /** * FileClassifierProxy */ public class FileClassifierProxy implements FileClassifier, Serializable { static public final int PORT = 2981; protected String host; public FileClassifierProxy(String host) { this.host = host; } public MIMEType getMIMEType(String fileName) throws java.rmi.RemoteException { // open a connection to the service on port XXX int dotIndex = fileName.lastIndexOf('.'); if (dotIndex == 1 dotIndex + 1 == fileName.length()) { // can't find suitable index return null; } String fileExtension = fileName.substring(dotIndex + 1); // open a client socket connection Socket socket = null; try { socket = new Socket(host, PORT); } catch(Exception e) { return null; } String type = null; String subType = null; /* * protocol: * Write: file extension * Read: "null" + '\n' * type + '\n' + subtype + '\n' */ try { InputStreamReader inputReader = new InputStreamReader(socket.getInputStream()); BufferedReader reader = new BufferedReader(inputReader); OutputStreamWriter outputWriter = new OutputStreamWriter(socket.getOutputStream()); BufferedWriter writer = new BufferedWriter(outputWriter); writer.write(fileExtension); writer.newLine(); writer.flush(); type = reader.readLine(); if (type.equals("null")) { return null; } subType = reader.readLine(); } catch(IOException e) { return null; } // and finally return new MIMEType(type, subType); } } // FileClassifierProxy FileServerImplThe FileServerImpl service will be running on the server side. It will run in its own thread (inheriting from Thread ) and will listen for connections. When one is received, it will create a new Connection object in its own thread, to handle the message exchange. (This creation of another thread is probably overkill here where the entire message exchange is very short, but it is good practice for more complex situations.) /** * FileServerImpl.java */ package socket; import java.net.*; import java.io.*; public class FileServerImpl extends Thread { protected ServerSocket listenSocket; public FileServerImpl() { try { listenSocket = new ServerSocket(FileClassifierProxy.PORT); } catch(IOException e) { e.printStackTrace(); } } public void run() { try { while(true) { Socket clientSocket = listenSocket.accept(); new Connection(clientSocket).start(); } } catch(Exception e) { e.printStackTrace(); } } } // FileServerImpl class Connection extends Thread { protected Socket client; public Connection(Socket clientSocket) { client = clientSocket; } public void run() { String contentType = null; String subType = null; try { InputStreamReader inputReader = new InputStreamReader(client.getInputStream()); BufferedReader reader = new BufferedReader(inputReader); OutputStreamWriter outputWriter = new OutputStreamWriter(client.getOutputStream()); BufferedWriter writer = new BufferedWriter(outputWriter); String fileExtension = reader.readLine(); if (fileExtension.equals("gif")) { contentType = "image"; subType = "gif"; } else if (fileExtension.equals("txt")) { contentType = "text"; subType = "plain"; } // etc if (contentType == null) { writer.write("null"); } else { writer.write(contentType); writer.newLine(); writer.write(subType); } writer.newLine(); writer.close(); } catch(IOException e) { e.printStackTrace(); } } } Service ProviderThe Jini service provider must start a FileServerImpl to listen for later connections. Then it can register a FileClassifierProxy proxy object with each lookup service, which will send them on to interested clients. The proxy object must know where the service backend object (the FileServerImpl ) is listening in order to attempt a connection to it, and this information is given by first making a query for the local host and then passing the hostname to the proxy in its constructor. package socket; import net.jini.discovery.LookupDiscovery; import net.jini.discovery.DiscoveryListener; import net.jini.discovery.DiscoveryEvent; import net.jini.core.lookup.ServiceRegistrar; import net.jini.core.lookup.ServiceItem; import net.jini.core.lookup.ServiceRegistration; import net.jini.core.lease.Lease; // import com.sun.jini.lease.LeaseRenewalManager; // Jini 1.0 // import com.sun.jini.lease.LeaseListener; // Jini 1.0 // import com.sun.jini.lease.LeaseRenewalEvent; // Jini 1.0 import net.jini.lease.LeaseRenewalManager; import net.jini.lease.LeaseListener; import net.jini.lease.LeaseRenewalEvent; import java.rmi.RMISecurityManager; import java.net.*; /** * FileClassifierServer.java */ public class FileClassifierServer implements DiscoveryListener, LeaseListener { protected FileClassifierProxy proxy; protected LeaseRenewalManager leaseManager = new LeaseRenewalManager(); public static void main(String argv[]) { new FileClassifierServer(); try { Thread.sleep(1000000L); } catch(Exception e) { } } public FileClassifierServer() { try { new FileServerImpl().start(); } catch(Exception e) { System.err.println("New impl: " + e.toString()); System.exit(1); } // set RMI scurity manager System.setSecurityManager(new RMISecurityManager()); // proxy primed with address String host = null; try { host = InetAddress.getLocalHost().getHostName(); } catch(UnknownHostException e) { e.printStackTrace(); System.exit(1); } proxy = new FileClassifierProxy(host); // now continue as before LookupDiscovery discover = null; try { discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS); } catch(Exception e) { System.err.println(e.toString()); System.exit(1); } discover.addDiscoveryListener(this); } public void discovered(DiscoveryEvent evt) { ServiceRegistrar[] registrars = evt.getRegistrars(); for (int n = 0; n < registrars.length; n++) { ServiceRegistrar registrar = registrars[n]; // export the proxy service ServiceItem item = new ServiceItem(null, proxy, null); ServiceRegistration reg = null; try { reg = registrar.register(item, Lease.FOREVER); } catch(java.rmi.RemoteException e) { System.err.print("Register exception: "); e.printStackTrace(); // System.exit(2); continue; } try { System.out.println("service registered at " + registrar.getLocator().getHost()); } catch(Exception e) { } leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this); } } public void discarded(DiscoveryEvent evt) { } public void notify(LeaseRenewalEvent evt) { System.out.println("Lease expired " + evt.toString()); } } // FileClassifierServer What Classes Need to Be Where?This section has considered a non-RMI proxy implementation. An application that uses this service implementation will need to deal with these classes:
Objects in these classes could be running on up to four different machines:
So, what classes need to be known to which machines? The server running FileClassifierServer needs to know the following classes and interfaces:
The lookup service does not need to know any of these classes. It just deals with them in the form of a java.rmi.MarshalledObject . The client needs to know the following:
In addition, the HTTP server needs to be able to load and store classes. It needs to be able to access the following:
Running the RMI Proxy FileClassifierA file classification application will have to run a server and a client, as in the earlier standalone implementation in Chapter 8 and the RMI implementation just a few pages earlier. The client is unchanged, as it does not care which server implementation is used: java -Djava.security.policy=policy.all client.TestFileClassifier The value of java.rmi.server. codebase must specify the protocol used by the HTTP server to find the class files. This could be the file protocol or the http protocol. For example, if the class files are stored on my Web server's pages under classes/socket/FileClassifierProxy.class , the codebase would be specified as java.rmi.server.codebase=http:// myWebHost /classes/ (where myWebHost is the name of the HTTP server host). The server also sets a security manager. This is a restrictive one, so it needs to be told to allow access. This can be done by setting the java.security.policy property to point to a security policy file, such as policy.all . Combining all these points leads to startups such as this: java -Djava.rmi.server.codebase=http://myWebHost/classes/ \ -Djava.security.policy=policy.all \ FileClassifierServer |