5.2
Basic Example using Typed Channels
In the following distributed client-server (C/S) application the client process (Listing 5.7) and the server process (Listing 5.6) communicate through a typed channel.
A typed channel provides a type-safe abstraction for network com- munication and is implemented by the two classes Channel (Listing 5.4) and ServerChannel (Listing 5.5) of the library package scala.remoting2. The class Channel defines basic generic send/receive operations.
class Channel(socket: Socket) {
def this(host: String, port: Int) =
this(new Socket(host, port))
val host = socket.getInetAddress.getHostAddress
val port = socket.getLocalPort
def receiveInt = receive[Int]
def receiveLong = receive[Long]
//.. more primitive types
def receive[T](implicit expected: Manifest[T]): T = { /*..*/ } def ?[T](implicit m: Manifest[T]): T = receive[T](m)
def send[T](x: T)(implicit m: Manifest[T]) { /*..*/ } def (implicit m: Manifest[T]) { send(x)(m) }
def close() { socket.close() } }
Listing 5.4: The Channel class (Scala API).
The class ServerChannel represents the second endpoint of the commu- nication channel and accepts incoming client requests on a given service port. The Scala programmer can define a subclass ofAbstractServerChannel in order to customize receive operations for user-defined messages.
class ServerChannel(p: Int) extends AbstractServerChannel[Channel] {
def newChannel(s: Socket) = new Channel(s) }
abstract class AbstractServerChannel[T <: Channel](_port: Int) {
def this() = this(0) // any free port
val host = serverSocket.getInetAddress.getHostAddress
val port = serverSocket.getLocalPort
//.. 2
protected def newChannel(s: Socket): T
def accept: T = { /*..*/ newChannel(serverSocket.accept) }
def close() { serverSocket.close() } }
Listing 5.5: The ServerChannel class (Scala API).
On the server side (Listing 5.6) we first create a server channel for accepting incoming requests and then enter the main loop (as for the previous example interaction details are hidden in trait ServerConsole). Once communication is established with a client, we receive the detached closure and evaluate it (line 4); the resulting integer value is then sent back to the client (line 12).
object Server extends ServerConsole {
2 private def computation(f: Int => Int): Int = {
//some time-consuming task
4 f(2)
}
6 def main(args: Array[String]) {
val server = new ServerChannel(args(0).toInt)
8 loop {
val client = server.accept
10 val f = client.receive[Int => Int]
val result = computation(f)
12 client ! result
}
14 server.close()
}
16 }
Listing 5.6: Basic C/S application (server).
On the client side (Listing 5.7) we first create a channel to commu- nicate with the server, we send a detached closure over it and, finally, we wait for the evaluation result and print the integer value 111113 to the console. The closure code contains several variable references and method calls declared in different contexts:
- Uses of variables include the formal parameterxof the anonymous function, the instance variables yInstVal and yInstVar declared in
3x+yInstVal+yInstVar+zLocVal+zLocVar = 2+10+99+1000+(9998+2) = 1111
5.2. BASIC EXAMPLE USING TYPED CHANNELS 79
objectClient(lines 5-6) and the local variables zLocValand zLocVar declared in a try/catch block (lines 13-14);
- Function calls include the library methodsprintln,System.out.println and Debug.info, method this.trace of theClient instance, method
Foo.trace of the top-level object Foo and method Bar.trace of the
field objectBar.
object Foo {
2 def trace(s: String) { info("[Foo.trace] "+s)}
}
4 object Client {
val yInstVal: Int = 10
6 var yInstVar: Int = 99
object Bar {
8 def trace(s: String) { info("[Bar.trace] "+s) }
}
10 def main(args: Array[String]) {
init(args)
12 val server = new Channel(host, port)
val zLocVal: Int = 1000
14 var zLocVar: Int = 9998
server ! detach(
16 (x: Int) => {
println("yInstVal = "+yInstVal)
18 this.trace("yInstVar = "+yInstVar)
Bar.trace("zLocVal = "+zLocVal)
20 Foo.trace("zLocVar = "+zLocVar)
zLocVar += 2
22 System.out.println("zLocVal = "+zLocVal)
Debug.info("zLocVar = "+zLocVar)
24 x + yInstVal + yInstVar + zLocVal + zLocVar
})
26 val result = server.receiveInt
println("result received: " + result)
28 }
private def trace(s: String) { info("[Client.trace] "+s) }
30 }
Listing 5.7: Basic C/S application (client).
which include the development phase (build process and configuration setup), the installation phase (deployment and customization) and the execution phase (test and production environments).
- In this example the distribution files generated during the build process include three Java archive files (client, server and deploy- ment files), one preconfigured Java security policy file and two shell scripts to launch the client and server applications.
- These files are then installed according to the chosen configuration settings (targeted to one, two or three different machines). Soft- ware requirements for the client and server applications are a recent Java run-time environment (version 1.5 or newer) and a modified Scala distribution (version 2.6 or newer with thedetach-extension4). - The two launch scripts (Listing 5.8 and Listing 5.9) can then be executed (in that order!). The server application runs forever and waits for incoming client requests.
#!/bin/sh ${JAVACMD:=java} \ -Xbootclasspath/a:$SCALA_HOME/lib/scala-library.jar \ -Dscala.remoting.logLevel=${LOGLEVEL:=warning} \ -Djava.security.manager \ -Djava.security.policy=$GENDIR/java.policy \ -Djava.rmi.server.codebase=$DEPLOYDIR/basic_deploy.jar \ -Djava.rmi.server.useCodebaseOnly=true \
-jar $GENDIR/basic_server.jar ${PORT:=8889}
Listing 5.8: Basic C/S application (server script).
#!/bin/sh ${JAVACMD:=java} \ -Xbootclasspath/a:$SCALA_HOME/lib/scala-library.jar \ -Dscala.remoting.logLevel=${LOGLEVEL:=warning} \ -Djava.security.manager \ -Djava.security.policy=$GENDIR/java.policy \
-jar $GENDIR/basic_client.jar 127.0.0.1 ${PORT:=8889}
Listing 5.9: Basic C/S application (client script).
4
5.2. BASIC EXAMPLE USING TYPED CHANNELS 81
A Java policy file (Listing 5.10) must be specified in both launch scripts (Listing 5.8 and Listing 5.9); it defines two groups of permission rules (or grant entries) : the first group of rules applies to class files loaded from any location and grants access permissions to Java sockets and library- defined properties; the second group of rules applies to the application class files and grants access permissions to RMI specific ressources.
grant {
permission java.net.SocketPermission "*:80", "connect,accept,listen"; permission java.net.SocketPermission "*:1024-", "connect,accept,listen"; permission java.util.PropertyPermission "scala.remoting.logLevel", "read";
permission java.util.PropertyPermission "scala.remoting.port", "read"; };
grant codeBase "@PROJECT_LIB_BASE@" {
permission java.lang.RuntimePermission "getClassLoader";
permission java.util.PropertyPermission "java.rmi.server.codebase", "read";
permission java.util.PropertyPermission "java.rmi.server.hostname", "read";
permission java.util.PropertyPermission "sun.rmi.dgc.server.gcInterval", "read, write";
};
Listing 5.10: Basic C/S application (policy file).
Both the client and server applications can be executed with differents levels 5 of logging information.
The two console outputs in Figure 5.2 correspond to the normal exe- cution (LOGLEVEL=warning) of the shell scripts presented in Listing 5.8 and Listing 5.9. The instructions println on line 17 and System.out.println on line 22 (Listing 5.7) write to the server console while the instruction
println on line 27 writes to the client console.
In Figure 5.3 we add some logging information to trace the execution of the client and server applications by setting the environment variable
LOGLEVEL to valueinfo.
For instance, the three instructionsthis.trace,Foo.traceandBar.trace (lines 18-20, Listing 5.7) write to the client console since they are application- specific, while the instructionsprintln,System.out.printlnandDebug.info (lines 17, 22 and 23) write to the server console since those three methods are defined in the Scala standard library.
Unlike the above logging information which is generated by instruc- tions appearing in the user code, the three notifications starting with
5
ClassDebugin packagescala.runtime.remotingcurrently defines five logging levels
unreferencedin the client console mean that the specified remote objects
are no more referenced (their binding can thus be removed from the remote registry).
Figure 5.2: Basic C/S application (consoles).
5.2. BASIC EXAMPLE USING TYPED CHANNELS 83
Finally, in Figure 5.4 we trace the execution of the server application and print out additional logging information about operations performed in typed channels by setting LOGLEVELto value info,lib before running the server script.
Figure 5.4: Basic C/S application (server console, option info,lib). Similarly, Figure 5.5 presents the displayed logging information when settingLOGLEVEL to value info,lib before running the client script.