通過前兩篇文章的介紹,大家應該對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() 方法。有興趣的讀者朋友可以自行查看源碼研究一番,不過在今後的系列文章中我們還會再遇到這幾個類。