Creating a Client Program
The compute engine is a relatively simple program: it runs tasks that are handed to it. The clients for the compute engine are more complex. A client needs to call the compute engine, but it also has to define the task to be performed by the compute engine.
创建客户端程序
计算引擎是一个相对简单的程序:通过运行任务来处理计算,但计算引擎的客户端就要复杂一点了。客户端不仅需要调用计算引擎,而且还要定义由计算引擎执行的任务。
Two separate classes make up the client in our example. The first class, ComputePi, looks up and invokes a Compute object. The second class, Pi, implements the Task interface and defines the work to be done by the compute engine. The job of the Pi class is to compute the value of to some number of decimal places.
在我们的示例中,使用了两个独立的类来构建客户端。第一个类,ComputePi,用于查找和调用Compute对象。第二个类,Pi,实现了Task接口。Pi类的工作是用来计算一些带有小数位数值的值。
The non-remote Task interface is defined as follows:
非远程Task接口的定义如下:
package compute;public interface Task<T> { T execute();}
The code that invokes a Compute object's methods must obtain a reference to that object, create a Task object, and then request that the task be executed. The definition of the task class Pi is shown later. A Pi object is constructed with a single argument, the desired precision of the result. The result of the task execution is a java.math.BigDecimal representing calculated to the specified precision.
调用Compute对象方法的代码必须先获得远程对象的引用,创建一个Task对象,随后再向远端发出请求,以使任务会被执行。类Pi的定义随后就可以看到。Pi对象通过一个单独的期望结果精度参数来进行构建。任务执行的结果类型是java.math.BigDecimal,它表示根据指定的精度而计算出来的结果。
Here is the source code for client.ComputePi, the main client class:
以下是client.ComputePi的源码,主要的客户端:
package client;import java.rmi.registry.LocateRegistry;import java.rmi.registry.Registry;import java.math.BigDecimal;import compute.Compute;public class ComputePi { public static void main(String args[]) { if (System.getSecurityManager() == null) { System.setSecurityManager(new SecurityManager()); } try { String name = "Compute"; Registry registry = LocateRegistry.getRegistry(args[0]); Compute comp = (Compute) registry.lookup(name); Pi task = new Pi(Integer.parseInt(args[1])); BigDecimal pi = comp.executeTask(task); System.out.println(pi); } catch (Exception e) { System.err.println("ComputePi exception:"); e.printStackTrace(); } } }
Like the ComputeEngine server, the client begins by installing a security manager. This step is necessary because the process of receiving the server remote object's stub could require downloading class definitions from the server. For RMI to download classes, a security manager must be in force.
同ComputeEngine服务器一样,客户端还是以安装安全管理器开始的。这个步骤是必须的,因为接收服务器远程对象stub过程,需要从服务器上下载字节码的定义。对于从RMI中下载字节码,安全管理器是具有强制性的。
After installing a security manager, the client constructs a name to use to look up a Compute remote object, using the same name used by ComputeEngine to bind its remote object. Also, the client uses the LocateRegistry.getRegistry API to synthesize a remote reference to the registry on the server's host. The value of the first command-line argument, args[0], is the name of the remote host on which the Compute object runs. The client then invokes the lookup method on the registry to look up the remote object by name in the server host's registry. The particular overload of LocateRegistry.getRegistry used, which has a single String parameter, returns a reference to a registry at the named host and the default registry port, 1099. You must use an overload that has an int parameter if the registry is created on a port other than 1099.
在安装了安全管理器后,客户端创建了一个name(此name与ComputeEnine绑定它的远程对象的名字相同)来查询远程Compute对象。另外,客户端也使用LocateRegistry.getRegistry API来获得服务器主机上的registry引用。第一个命令行参数args[0],表示运行Compute对象所在的远程主机名称。然后客户端在服务器注册表上根据name调用lookup方法来查找远程对象。这将使用它带有一个参数的重载LocateRegistry.getRegistry方法,其结果是返回一个由已指定主机和默认1099端口指定的注册表引用。如果注册表端口不是1099的话,你必须使用一个带有int参数的重载方法,此int参数用于具体的注册表端口。
Next, the client creates a new Pi object, passing to the Pi constructor the value of the second command-line argument, args[1], parsed as an integer. This argument indicates the number of decimal places to use in the calculation. Finally, the client invokes the executeTask method of the Compute remote object. The object passed into the executeTask invocation returns an object of type BigDecimal, which the program stores in the variable result. Finally, the program prints the result. The following figure depicts the flow of messages among the ComputePi client, the rmiregistry, and the ComputeEngine.
紧接着,客户端创建了一个新的Pi对象,并将第二个命令行参数args[1]解析成整数传递给构造函数。此参数用于指定计算中小数位的数目。最后,客户端调用Compute远程对象的executeTask方法,将对象参数传递给executeTask方法,调用结果将返回存储了变量结果的类型为BigDecimal的对象。最终,程序将打印出结果。下面的图描述了ComputePi客户端,RMI注册表以及ComputeEngine之的流程
The Pi class implements the Task interface and computes the value of to a specified number of decimal places. For this example, the actual algorithm is unimportant. What is important is that the algorithm is computationally expensive, meaning that you would want to have it executed on a capable server.
类Pi实现了Task接口,根据指定的小数位个数来计算其结果。对于此例来说,实际的算法不是很重要。重要的是运算量,即你可能需要能够胜任此任务的服务器来执行。
Here is the source code for client.Pi, the class that implements the Task interface:
以下是client.Pi的源码,它实现了Task接口
package client;import compute.Task;import java.io.Serializable;import java.math.BigDecimal;public class Pi implements Task<BigDecimal>, Serializable { private static final long serialVersionUID = 227L; /** constants used in pi computation */ private static final BigDecimal FOUR = BigDecimal.valueOf(4); /** rounding mode to use during pi computation */ private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN; /** digits of precision after the decimal point */ private final int digits; /** * Construct a task to calculate pi to the specified * precision. */ public Pi(int digits) { this.digits = digits; } /** * Calculate pi. */ public BigDecimal execute() { return computePi(digits); } /** * Compute the value of pi to the specified number of * digits after the decimal point. The value is * computed using Machin's formula: * * pi/4 = 4*arctan(1/5) - arctan(1/239) * * and a power series expansion of arctan(x) to * sufficient precision. */ public static BigDecimal computePi(int digits) { int scale = digits + 5; BigDecimal arctan1_5 = arctan(5, scale); BigDecimal arctan1_239 = arctan(239, scale); BigDecimal pi = arctan1_5.multiply(FOUR).subtract( arctan1_239).multiply(FOUR); return pi.setScale(digits, BigDecimal.ROUND_HALF_UP); } /** * Compute the value, in radians, of the arctangent of * the inverse of the supplied integer to the specified * number of digits after the decimal point. The value * is computed using the power series expansion for the * arc tangent: * * arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 + * (x^9)/9 ... */ public static BigDecimal arctan(int inverseX, int scale) { BigDecimal result, numer, term; BigDecimal invX = BigDecimal.valueOf(inverseX); BigDecimal invX2 = BigDecimal.valueOf(inverseX * inverseX); numer = BigDecimal.ONE.divide(invX, scale, roundingMode); result = numer; int i = 1; do { numer = numer.divide(invX2, scale, roundingMode); int denom = 2 * i + 1; term = numer.divide(BigDecimal.valueOf(denom), scale, roundingMode); if ((i % 2) != 0) { result = result.subtract(term); } else { result = result.add(term); } i++; } while (term.compareTo(BigDecimal.ZERO) != 0); return result; }}
Note that all serializable classes, whether they implement the Serializable interface directly or indirectly, must declare a private static final field named serialVersionUID to guarantee serialization compatibility between versions. If no previous version of the class has been released, then the value of this field can be any long value, similar to the 227L used by Pi, as long as the value is used consistently in future versions. If a previous version of the class has been released without an explicit serialVersionUID declaration, but serialization compatibility with that version is important, then the default implicitly computed value for the previous version must be used for the value of the new version's explicit declaration. The serialver tool can be run against the previous version to determine the default computed value for it.
注意,对于所有序列化类来说,不管它们是直接还是间接实现Serializable接口,都必须声明一个访问修饰符为private static final名为serialVersionUID的字段,以保证在不同版本之间的序列化兼容性。
如果此类的先前版本没有发布,那么此字段的值可以为任何一个long类型的值,比如Pi中使用的227L,在将来版本的中,使用也要一致.
如果此类的先前版本已经发布,但没有明确的serialVersionUID声明,而序列化兼容性对于那个版本又非常重要,那么先前版本的默认隐含计算值必须用作新版本明确声明的值。
序列化工具可以通过先前版本来计算它的默认计算值。
The most interesting feature of this example is that the Compute implementation object never needs the Pi class's definition until a Pi object is passed in as an argument to the executeTask method. At that point, the code for the class is loaded by RMI into the Compute object's Java virtual machine, the execute method is invoked, and the task's code is executed. The result, which in the case of the Pi task is a BigDecimal object, is handed back to the calling client, where it is used to print the result of the computation.
这个示例最有趣的特性是Compute实现对象直到Pi对象作为参数传递给executeTask方法时,才需要Pi类的定义。在那个时刻,字节码是通过RMI的加载到Compute对象的JVM中的,然后调用execute方法,执行任务代码。在Pi任务中,其结果是BigDecimal,将被返回给客户端处理,这里的处理仅是用来打印计算的结果。
The fact that the supplied Task object computes the value of Pi is irrelevant to the ComputeEngine object. You could also implement a task that, for example, generates a random prime number by using a probabilistic algorithm. That task would also be computationally intensive and therefore a good candidate for passing to the ComputeEngine, but it would require very different code. This code could also be downloaded when the Task object is passed to a Compute object. In just the way that the algorithm for computing is brought in when needed, the code that generates the random prime number would be brought in when needed. The Compute object knows only that each object it receives implements the execute method. The Compute object does not know, and does not need to know, what the implementation does.
事实上,用户提供的用于计算值的Pi对象与ComputeEngine对象是不相关的。你也可以实现另外一个对象,如,通过使用概率算法来生成一个随机数。这个任务仍然是与具体计算无关的,因此可以将一个好的候选对象传递给ComputeEngine,但这可能需要不同的代码。
这个代码仍然可以在它传递给Compute对象的时候被下载。在那种方式中,用于计算的算法只在当我们需要的时候才会加载,即只有当需要的时候,生成随机数的算法才会被引入。Compute对象只知道它接收到的对象实现了execute方法,它不知道也没有必要知道实现是如何做的。