通过前两篇文章的介绍,大家应该对Threerings框架有了初步的了解,前面笔者也提到过客户端对服务端的请求主要是通过对服务的调用来实现,即InvocationService,这种调用与Java API中的远程方法调用有点类似。而Threerings在框架层面对这个机制提供了完善的支持。今天我们就来对这个InvocationService来仔细研究一番。其实在前面的例子中,我们已经接触过了InvocationService的一个例子,即取得分布式对象DObject的object id,不过为了简化所要论述的问题,在这里,我们只是简单的打印客户端的请求。
首先我们来看一下presents框架中的InvocationService这个接口,我们自己定义的所有服务都要从继承这个接口开始。
public interface InvocationService
{
public static interface InvocationListener
{
void requestFailed (String cause);
}
public static interface ConfirmListener extends InvocationListener
{
void requestProcessed ();
}
public static interface ResultListener extends InvocationListener
{
void requestProcessed (Object result);
}
}
这个InvocationService接口本身没有包含任何的方法,但是包含了三个静态listener接口作为回调方法,从方法名当中也能看出这主要是为了方便对服务调用后不同的状态作出相应的回应。
下面我们再来看下我们自己的TestService
public interface TestService extends InvocationService
{
public static interface TestFuncListener extends InvocationListener
{
public void testSucceeded (String one, int two);
}
public void test (Client client, String one, int two, List<Integer> three,
TestFuncListener listener);
}
TestFuncListener接口继承了InvocationListener接口,包含的testSucceeded方法作为服务调用成功之后的回调方法,而test方法则是我们的服务方法。接下来我们则需要运行框架提供给我们的一个ant target genservice来生成InvocationService完整定义的其他几个类,分别为TestProvider,TestDispatcher,TestMarshaller。这个genservice是个自定义ant target,可以在Threerings源码分发包里找到。
<!-- generates marshaller and dispatcher classes for all invocation service declarations --> <target name="genservice" depends="preptools"> <!-- make sure the service class files are all compiled --> <javac srcdir="src/java" destdir="${classes.dir}" includeAntRuntime="false" debug="on" optimize="${build.optimize}" deprecation="on" source="1.5" target="1.5"> <classpath refid="classpath"/> <include name="**/*Service.java"/> <exclude name="**/InvocationService.java"/> </javac> <!-- now generate the associated files --> <genservice header="lib/SOURCE_HEADER" asroot="src/as" classpathref="classpath"> <fileset dir="src/java" includes="**/*Service.java"> <exclude name="**/InvocationService.java"/> <exclude name="**/peer/**"/> <exclude name="**/admin/**"/> </fileset> <providerless service="ChatService"/> <providerless service="SimulatorService"/> <providerless service="TimeBaseService"/> </genservice> <genservice header="lib/SOURCE_HEADER" classpathref="classpath"> <fileset dir="src/java" includes="**/peer/**/*Service.java"/> <fileset dir="src/java" includes="**/admin/**/*Service.java"/> </genservice> </target>
我们来分别看下生成的这几个类
TestProvider:
public interface TestProvider extends InvocationProvider
{
void test (ClientObject caller, String arg1, int arg2, List<Integer> arg3, TestService.TestFuncListener arg4)
throws InvocationException;
}
TestDispatcher:
public class TestDispatcher extends InvocationDispatcher<TestMarshaller>
{
public TestDispatcher (TestProvider provider)
{
this.provider = provider;
}
public TestMarshaller createMarshaller ()
{
return new TestMarshaller();
}
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case TestMarshaller.TEST:
((TestProvider)provider).test(
source, (String)args[0], ((Integer)args[1]).intValue(), (List<Integer>)args[2], (TestService.TestFuncListener)args[3]
);
return;
default:
super.dispatchRequest(source, methodId, args);
return;
}
}
}
TestMarshaller:
public class TestMarshaller extends InvocationMarshaller
implements TestService
{
public static class TestFuncMarshaller extends ListenerMarshaller
implements TestFuncListener
{
public static final int TEST_SUCCEEDED = 1;
public void testSucceeded (String arg1, int arg2)
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, TEST_SUCCEEDED,
new Object[] { arg1, Integer.valueOf(arg2) }, transport));
}
public void dispatchResponse (int methodId, Object[] args)
{
switch (methodId) {
case TEST_SUCCEEDED:
((TestFuncListener)listener).testSucceeded(
(String)args[0], ((Integer)args[1]).intValue());
return;
default:
super.dispatchResponse(methodId, args);
return;
}
}
}
public static final int TEST = 1;
public void test (Client arg1, String arg2, int arg3, List<Integer> arg4, TestService.TestFuncListener arg5)
{
TestMarshaller.TestFuncMarshaller listener5 = new TestMarshaller.TestFuncMarshaller();
listener5.listener = arg5;
sendRequest(arg1, TEST, new Object[] {
arg2, Integer.valueOf(arg3), arg4, listener5
});
}
}
其中TestService是客户端调用接口,TestProvider是服务端调用接口,而TestDispatcher和TestMarshaller则是底层的通讯实现。在除去这些个类之外,我们还需要手工创建一个provider实现类来实现具体的服务,在这里我们把这个类叫做TestManager,它仅仅只是简单的打印输出,并调用回调接口,这里我们要注意的是,区别与服务方法调用的是回调方法在服务端被调用,但是在客户端被执行。
public class TestManager implements TestProvider
{
public void test (ClientObject caller, String one, int two, List<Integer> three,
TestService.TestFuncListener listener)
throws InvocationException
{
log.info("Test request", "one", one, "two", two, "three", three);
listener.testSucceeded(one, two);
}
}
要运行整个完整的例子,我们还需要分别创建一个客户端和服务端的实现,并分别注册该服务。
TestClient:
public class TestClient implements SessionObserver{
public void clientDidLogoff(Client client) {
log.info("Client did logoff [client=" + client + "].");
System.exit(0);
}
public void clientDidLogon(Client client) {
log.info("Client did logon [client=" + client + "].");
TestService service = client.requireService(TestService.class);
// send a test request
ArrayList<Integer> three = new ArrayList<Integer>();
three.add(3);
three.add(4);
three.add(5);
service.test(client, "one", 2, three, new TestService.TestFuncListener() {
public void testSucceeded (String one, int two) {
log.info("Got test response [one=" + one + ", two=" + two + "].");
}
public void requestFailed (String reason) {
log.info("Urk! Request failed [reason=" + reason + "].");
}
});
}
public void clientObjectDidChange(Client client) {
log.info("Client object did change [client=" + client + "].");
}
public void clientWillLogon(Client client) {
client.addServiceGroup("test");
}
public static void main (String[] args)
{
TestClient tclient = new TestClient();
UsernamePasswordCreds creds =
new UsernamePasswordCreds(new Name("test"), "test");
BasicRunQueue rqueue = new BasicRunQueue();
Client client = new Client(creds, rqueue);
tclient.setClient(client);
client.addClientObserver(tclient);
client.setServer("localhost", Client.DEFAULT_SERVER_PORTS);
client.logon();
// start up our event processing loop
rqueue.run();
}
public void setClient (Client client)
{
_client = client;
}
protected Client _client;
}
TestServer:
public class TestServer extends PresentsServer{
@Override
public void init (Injector injector)
throws Exception
{
super.init(injector);
// register our test provider
_invmgr.registerDispatcher(
new TestDispatcher(injector.getInstance(TestManager.class)), "test");
}
public static void main (String[] args)
{
Injector injector = Guice.createInjector(new Module());
TestServer server = injector.getInstance(TestServer.class);
try {
server.init(injector);
server.run();
} catch (Exception e) {
log.warning("Unable to initialize server.", e);
}
}
}
分别启动服务端和客户端,得到服务端输出为:
Starting up server [os=Windows XP (5.1-x86), jvm=1.6.0_07, Sun Microsystems Inc.] Unable to register Sun signal handlers [error=java.lang.IllegalArgumentException: Unknown signal: USR2] Could not load libsignal.so; signal handling disabled. DOMGR running. Server listening on 0.0.0.0/0.0.0.0:47624. Accepting request: [type=AREQ, msgid=1, creds=[username=test, password=test], version=] Session initiated [type=Name, who=test, conn=[id=2, addr=/127.0.0.1]] Test request [one=one, two=2, three=(3, 4, 5)]
客户端输出为:
Connecting [host=localhost/127.0.0.1, port=47624] Client did logon [client=Client [hostname=localhost, ports=(47624), clOid=2, connId=2, creds=[username=test, password=test]]]. Got test response [one=one, two=2].
观察输出内容,并且对应前面的代码,我们看到,test方法在客户端被调用,在服务端被执行;而回调方法在服务端被调用,在客户端被执行。
整个InvocationService各个部件的调用关系见下图。
InvocationReceiver:
Threerings框架除了提供客户端远程调用服务端服务的InvocationSerivce机制外,还提供了一种供服务端调用客户端方法的机制,即InvocationReceiver机制。与InvocationSerivce类似,我们需要为被调用的方法创建一个receiver接口,该接口继承自框架的InvocationReceiver接口。
public interface TestReceiver extends InvocationReceiver
{
/**
* Dispatches a test notification.
*/
public void receivedTest (int one, String two);
}
同时我们运行框架提供的ant 自定义任务genreceiver,用以生成对应的Sender和Decoder。
Decoder:
public class TestDecoder extends InvocationDecoder
{
/** The generated hash code used to identify this receiver class. */
public static final String RECEIVER_CODE = "f388690ba85f419e8baf8e6165343e86";
/** The method id used to dispatch {@link TestReceiver#receivedTest}
* notifications. */
public static final int RECEIVED_TEST = 1;
/**
* Creates a decoder that may be registered to dispatch invocation
* service notifications to the specified receiver.
*/
public TestDecoder (TestReceiver receiver)
{
this.receiver = receiver;
}
@Override // documentation inherited
public String getReceiverCode ()
{
return RECEIVER_CODE;
}
@Override // documentation inherited
public void dispatchNotification (int methodId, Object[] args)
{
switch (methodId) {
case RECEIVED_TEST:
((TestReceiver)receiver).receivedTest(
((Integer)args[0]).intValue(), (String)args[1]
);
return;
default:
super.dispatchNotification(methodId, args);
return;
}
}
}
Sender:
public class TestSender extends InvocationSender
{
/**
* Issues a notification that will result in a call to {@link
* TestReceiver#receivedTest} on a client.
*/
public static void sendTest (
ClientObject target, int arg1, String arg2)
{
sendNotification(
target, TestDecoder.RECEIVER_CODE, TestDecoder.RECEIVED_TEST,
new Object[] { Integer.valueOf(arg1), arg2 });
}
}
我们可以把方法的调用放在TestManager中
public void test (ClientObject caller, String one, int two, List<Integer> three,
TestService.TestFuncListener listener)
throws InvocationException
{
log.info("Test request", "one", one, "two", two, "three", three);
// and issue a response to this invocation request
listener.testSucceeded(one, two);
TestSender.sendTest(caller, 1, "two");
}
方法的实现放在客户端,我们可以让TestClient来实现TestReceiver接口,同时给出该方法的具体实现。
public class TestClient implements SessionObserver, TestReceiver{
......
@Override
public void receivedTest(int one, String two) {
log.info("Received test notification [one=" + one + ", two=" + two + "].");
}
在可以顺利运行之前,我们还需要在客户端注册该receiver。
public void clientDidLogon(Client client) {
......
// register ourselves as a test notification receiver
client.getInvocationDirector().registerReceiver(new TestDecoder(this));
......
}
运行之后客户端的输出:
Connecting [host=localhost/127.0.0.1, port=47624] Client did logon [client=Client [hostname=localhost, ports=(47624), clOid=2, connId=2, creds=[username=test, password=test]]]. Got test response [one=one, two=2]. Received test notification [one=1, two=two].
可以看到在Got test response之后又多了一行Received test notification,这说明了sendTest方法在服务端被调用而在客户端被执行。这是一种服务端远程调用客户端方法的机制。
各个部件的调用关系可以用下图来说明。
一般来说InvocationSerivce在整个框架中被使用的比较多,而InvocationReceiver则使用的比较少,算是对前一种机制的补充吧,甚至在GameGarden开发包中都没有包括genReceiver这个ant 任务,需要我们自己从narya包中复制过来,但这种机制在某些场合中还是会被用到的,比如LocationReceiver.forcedMove()。 The LocationDirector 实现了LocationReceiver接口,在client登录后把自己注册到InvocationDirector。 然后服务端传入相应的client object 并调用LocationSender.forcedMove() 方法。有兴趣的读者朋友可以自行查看源码研究一番,不过在今后的系列文章中我们还会再遇到这几个类。