XML Web サービス、Java RMI、dRuby

並行システム

                               システム情報工学研究科コンピュータサイエンス専攻、電子・情報工学系
                               新城 靖
                               <yas@is.tsukuba.ac.jp>

このページは、次の URL にあります。
http://www.cs.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22
あるいは、次のページから手繰っていくこともできます。
http://www.cs.tsukuba.ac.jp/~yas/sie/
http://www.cs.tsukuba.ac.jp/~yas/

■今日の重要な話

Modern な RPC。

■XML

XML(eXtensible Markup Language)

XML は、WWW で使われている HTML (Hyper Text Markup Language) と同様に、マークアップ言語の一種。

マークアップ言語とは、文書(テキスト)に、「ここは表題」、「ここは箇条 書」といった、文書の構造を示す目印(マーク)を付ける機能を持つ言語。

マークアップ言語の種類

◆拡張可能性(extensible)

HTML でも XML でも、マークアップのために、テキストにタグ(tag)を埋め込 む。HTML の場合、タグの種類と意味があらかじめ規定されており、その規定 の範囲でしか WWW データを表現することができない。

これに対して、XML では、定められた方式で新たなタグを定義することが可能 になっている。XML を使えば、特定の応用分野に特化したマークアップ言語を 文書の作成者が設計することができるようになる。

XML 形式の文書は、WWW ブラウザでの利用のように、人間が目にすることがあ る場所でも使われるが、むしろプログラムとプログラムがネットワークを越え てデータを交換するために使われることが多い。

World Wide Web コンソーシアムのXMLのページ
http://www.w3.org/XML/

■XML Webサービス

◆XML Webサービスとは

Web サービスとは、非常に紛らわしい用語であるが、本来の意味は、 Web of services であり、XML 技術を用いてソフトウェア・コンポー ネント(部品)をネットワークを通じて利用可能にしたものである。

Web サービスを、一般の World Wide Web と紛れないように呼ぶ時には、 「XML Web サービス」と言う。

◆プログラミング手法の発展

XML サービスでよく使われている遠隔手続き呼出しの仕組みは、 XML-RPC と SOAP である。

Web サービスでは、異なるプログラミング言語で書かれたコンポーネントも相 互に接続できる。

◆XML-RPC と SOAP

XML-RPC と SOAP は、両方とも、 XML の技術を使って遠隔手続き呼出しを実現したもの。 XML-RPC の方が古い。

XML-RPC では、クライアントとサーバの間の通信は、XML により行われる。ク ライアントは、手続きの名前や入力パラメタを XML の形式にまとめて、サー バへ送る。サーバでは、手続きが実行され、その結果もXMLの形でクライアン トへ返される。 XML-RPC では、クライアントとサーバの間の通信には、WWW で使わ れている HTTP が使われる。

XML-RPC では、メッセージが長くなり過ぎたり、型付けに問題があっ た。これを解決するために、SOAP (Simple Object Access Protocol) が作られた。

SOAP は、最初は、Simple Object Access Protocol の略であったが、 Object-Oriented ではないということで、この略は捨てられた。

XML-RPC では、データを交換するための中間形式として XML を使っていただ けであったが、SOAP では、XML のデータを直接 XML として渡すことができる。

SOAP では、HTTP の他に電子メールも通信媒体として使う事ができる。 SOAP 用に、HTTP のヘッダがいくつか拡張されたが、 実際問題として活用しにくい。 SOAP は、Microsoft 社などにより提案され、その後、W3C で標準化が行われ ている。

http://www.w3.org/TR/SOAP/

◆WebサービスとWorld Wide Web

一般的な WWW
Web サービス

◆Web サービスの2つの利用形態

◆WSDL (Web Services Description Language)

WSDL は、Web サービスの内容を説明するための、XML に基づく言語。

WSDL で記述するもの

・データ型。
Web サービスのクライアントとサーバの間で交換され るデータ形式の定義。
・メッセージ。
要求メッセージ(クライアントからサーバへ送られ る)と応答メッセージ(逆方向)の形式。(厳密には、Web サービ スでは単方向の通信も許されている。)
・手続きの集合(portType)。
オブジェクト指向言語のクラスに相当する。
・通信プロトコルとの対応(binding)。
多くの Web サービスでは、通信のために SOAP が使われる。
・サーバの位置(service)。
URL などで記述される。
Web サービスでは、WSDL により、クライアントとサーバの間のインタフェー スを規定する。WSDL によるのインタフェース記述より、クライアント側とサー バ側のスタブを自動的に生成することができる。

◆WSDLの例(GLUEにより自動生成されたもの)

[c1.wsdl]
   1: <?xml version='1.0' encoding='UTF-8'?>
   2: 
   3: <!--generated by GLUE-->
   4: 
   5: <definitions name='Counter'
   6:     targetNamespace='http://www.themindelectric.com/wsdl/Counter/'
   7:     xmlns:tns='http://www.themindelectric.com/wsdl/Counter/'
   8:     xmlns:electric='http://www.themindelectric.com/'
   9:     xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'
  10:     xmlns:http='http://schemas.xmlsoap.org/wsdl/http/'
  11:     xmlns:mime='http://schemas.xmlsoap.org/wsdl/mime/'
  12:     xmlns:xsd='http://www.w3.org/2001/XMLSchema'
  13:     xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/'
  14:     xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'
  15:     xmlns='http://schemas.xmlsoap.org/wsdl/'>
  16: 
  17:     <message name='getValue0SoapIn'/>
  18:     <message name='getValue0SoapOut'>
  19:         <part name='Result' type='xsd:int'/>
  20:     </message>
  21:     <message name='reset1SoapIn'>
  22:         <part name='newVal' type='xsd:int'/>
  23:     </message>
  24:     <message name='reset1SoapOut'/>
  25:     <message name='up2SoapIn'/>
  26:     <message name='up2SoapOut'/>
  27: 
  28:     <portType name='CounterSoap'>
  29:         <operation name='getValue'>
  30:             <input name='getValue0SoapIn' message='tns:getValue0SoapIn'/>
  31:             <output name='getValue0SoapOut' message='tns:getValue0SoapOut'/>
  32:         </operation>
  33:         <operation name='reset' parameterOrder='newVal'>
  34:             <input name='reset1SoapIn' message='tns:reset1SoapIn'/>
  35:             <output name='reset1SoapOut' message='tns:reset1SoapOut'/>
  36:         </operation>
  37:         <operation name='up'>
  38:             <input name='up2SoapIn' message='tns:up2SoapIn'/>
  39:             <output name='up2SoapOut' message='tns:up2SoapOut'/>
  40:         </operation>
  41:     </portType>
  42: 
  43:     <binding name='CounterSoap' type='tns:CounterSoap'>
  44:         <soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/>
  45:         <operation name='getValue'>
  46:             <soap:operation soapAction='getValue' style='rpc'/>
  47:             <input name='getValue0SoapIn'>
  48:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  49:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  50:             </input>
  51:             <output name='getValue0SoapOut'>
  52:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  53:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  54:             </output>
  55:         </operation>
  56:         <operation name='reset'>
  57:             <soap:operation soapAction='reset' style='rpc'/>
  58:             <input name='reset1SoapIn'>
  59:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  60:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  61:             </input>
  62:             <output name='reset1SoapOut'>
  63:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  64:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  65:             </output>
  66:         </operation>
  67:         <operation name='up'>
  68:             <soap:operation soapAction='up' style='rpc'/>
  69:             <input name='up2SoapIn'>
  70:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  71:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  72:             </input>
  73:             <output name='up2SoapOut'>
  74:                 <soap:body use='encoded' namespace='http://tempuri.org/Counter'
  75:                     encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/>
  76:             </output>
  77:         </operation>
  78:     </binding>
  79: 
  80:     <service name='Counter'>
  81:         <documentation>
  82:         // ICounter.java//</documentation>
  83:         <port name='CounterSoap' binding='tns:CounterSoap'>
  84:             <soap:address location='http://127.0.0.1:4031/soap/Counter/c1'/>
  85:         </port>
  86:     </service>
  87: </definitions>

◆UDDI (Universal Description, Discovery, and Integration)

UDDI は、Web サービスによる電子商取引を活性するための Web サー ビスを検索するための仕組み。

UDDI では、UDDI レジストリと呼ばれるデータベースに次のような情報を格納 する。

ビジネス:
サービス提供者の会社名、企業コード、分類情報など。
サービス:
Web サービスの集合。各Webサービスごとに、名前やバ インディング・テンプレートを登録する。
バインディング・テンプレート:
1つのWeb サービスを利用するた めに必要な情報(TModel)への参照。
UDDI を利用する Web サービスのサーバ TModel は、WSDL などWeb サービスを利用するために必要な情報を含んでいる。

UDDI を利用する Web サービスのクライアント

インターネット上で利用可能な Web サービスのための UDDI レジ ストリとしては、IBM やマイクロソフトが運営しているもがある。

◆Webサービスの開発キット

XML を扱う環境は、Java が強い。スクリプト言語からも利用可能になりつつ ある。

◆Glue

Glue は、The Mind Electric社 により開発された Web サービス開発のためのツールキット。

HTTP サーバを内蔵しているので、手軽に試すのに適している。 WSDL を自動生成する機能がある。

The Mind Electric社は、2003年にwebMethods社により買収された。 webMethods社は、2007年に Software AG社によって買収された。

■ローカルのカウンタ

ローカルで動作するプログラムを、Web サービスとして動作するように書き直す。

◆ローカル/インタフェース

[ICounter.java]
   1: //
   2: // ICounter.java
   3: //
   4: 
   5: public interface ICounter
   6: {
   7:     void up();
   8:     int getValue();
   9:     void reset(int newVal);
  10: };
単に利用するだけならば、インタフェースを定義しなくてもよいが、リモート との比較のためにあえて定義する。カウンタは、3つの手続きを持つものであ る。

◆ローカル/オブジェクト

[Counter.java]
   1: //
   2: // Counter.java
   3: //
   4: public class Counter implements ICounter
   5: {
   6:     int val;
   7:     public Counter(int initVal)
   8:     {
   9:         val = initVal ;
  10:     }
  11:     public void up()
  12:     {
  13:         val ++ ;
  14:     }
  15:     public int getValue()
  16:     {
  17:         return( val );
  18:     }
  19:     public void reset(int newVal)
  20:     {
  21:         val = newVal ;
  22:     }
  23: };
3つの手続きとコンストラクタを実現している。

◆ローカル/利用

[CounterLocal.java]
//
// CounterLocal.java
//

class CounterLocal
{
    public static void main(String argv[])
    {
	ICounter c1 = new Counter( 10 );
	for( int i=0 ; i<3 ; i++ )
	{
	    c1.up();
	    System.out.println("c1.value=="+c1.getValue());
	}
    }
};
coins での実行例:
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Makefile [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/ICounter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Counter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/CounterLocal.java [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make CounterLocal [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar ICounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar Counter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterLocal.java
% make run-CounterLocal [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterLocal
c1.value==11
c1.value==12
c1.value==13
% make run-CounterLocal [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterLocal
c1.value==11
c1.value==12
c1.value==13
% []

オブジェクト c1 を、main で作って実行している。実行する度に新しいオブ ジェクトが new される。

■Web サービスによるカウンタ

ローカルで動作するプログラムを、Web サービスとして 動作するように書き 直す。

◆サーバ

[CounterServer.java]
   1: 
   2: import electric.registry.Registry;
   3: import electric.server.http.HTTP;
   4: 
   5: public class CounterServer
   6: {
   7:     public static void main( String[] args ) throws Exception
   8:     {
   9:         if( args.length != 2 )
  10:         {
  11:             System.err.println("Usge: %% java CounterServer portno name");
  12:             System.exit( 1 );
  13:         }
  14:         String portno = args[0];
  15:         String name = args[1];
  16:         String url = "http://localhost:" + portno + "/soap" ;
  17:         HTTP.startup( url );
  18: 
  19:         Registry.publish( name, new Counter(10) );
  20:         System.out.println("Ok.");
  21:     }
  22: }
このプログラムは、2つの引数を取る。 ポート番号としては、以下の Makefile では、8080 が指定されている。他の 人とぶつからないように、クライアント側、サーバ側とも書き換えることが望 ましい。

HTTP.Startup() で、Glue に含まれている Web サービスのサーバを起動して いる。その結、果新しいスレッドが作られるので、このプログラムは、main() が終了しても、動き続けることになる。

Registry.publish() は、Glue に含まれている機能である。 これは、与えられた名前で、オブジェクトをアクセス可能にする。

◆クライアント

[CounterClient.java]
   1: 
   2: import electric.registry.Registry;
   3: 
   4: public class CounterClient
   5: {
   6:     public static void main( String[] args ) throws Exception
   7:     {
   8:         if( args.length != 3 )
   9:         {
  10:             System.err.println("Usge: %% java CounterClient hostname portno name");
  11:             System.exit( 1 );
  12:         }
  13:         String hostname = args[0];
  14:         String portno = args[1];
  15:         String name = args[2];
  16:         String url = "http://" + hostname + ":" + portno + "/soap/" + name + ".wsdl";
  17:         System.out.println("url is " + url );
  18:         ICounter c1 = (ICounter) Registry.bind( url, ICounter.class );
  19:         for( int i=0 ; i<3 ; i++ )
  20:         {
  21:             c1.up();
  22:             System.out.println("c1.value=="+c1.getValue());
  23:         }
  24:     }
  25: }
このプログラムは、3つの引数を取る。
hostname
サーバが動いているホストの名前。同じホストで動いている 時には、localhost を与える。
portno
ポート番号。サーバを実行した時と同じもの。
name
Counter オブジェクトの名前。サーバで指定したものと同じもの。
Registry.bind() により、クライアント側のスタブ(プロキシ)が自動的に生 成され、インタフェース ICounter のオブジェクトが返される。 このオブジェクトは、class Counter のものではない。このオブジェクトにア クセスすると、RPC が行われる。このプログラムは、3回ループを回って、 up() という手続きと getValue() という手続きを呼んでいる。合計、6 回の RPC が行われる。

◆コンパイルと実行(サーバ側)

実行する時には、サーバ側とクライアント側でそれぞれ1つウインドウを開く。

サーバは、は自動的に終了しないので、実験が終わったら ^C (Control-C)で 殺す。

% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Makefile [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/ICounter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Counter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/CounterServer.java [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make CounterServer [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar ICounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar Counter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterServer.java
% make run-CounterServer [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterServer 8080 Counter/c1
GLUE 1.2 (c) 2001 The Mind Electric
startup server on http://127.0.0.1:8080/soap
Ok.
(最後に ^C で止める)

◆コンパイルと実行(クライアント側)

% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Makefile [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/ICounter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/CounterClient.java [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make CounterClient [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar ICounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient.java
% ls [←]
CounterClient.class     ICounter.class          Makefile
CounterClient.java      ICounter.java
% make run-CounterClient [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient.java
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient localhost 8080 Counter/c1
url is http://localhost:8080/soap/Counter/c1.wsdl
c1.value==11
c1.value==12
c1.value==13
% make run-CounterClient [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient.java
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterClient localhost 8080 Counter/c1
url is http://localhost:8080/soap/Counter/c1.wsdl
c1.value==14
c1.value==15
c1.value==16
% []
クライアントのプログラムを2回実行しても、同じカウンタが使われている所 に注意する。

実際に Web サービスを利用する場合には、いくつかの方法が選べる。

◆サーバ側のエラー

% make run-CounterServer [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterServer 8080 Counter/c1
Exception in thread "main" java.net.BindException: Address already in use
        at java.net.PlainSocketImpl.socketBind(Native Method)
        at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:331)
        at java.net.ServerSocket.bind(ServerSocket.java:309)
        at java.net.ServerSocket.(ServerSocket.java:183)
        at java.net.ServerSocket.(ServerSocket.java:139)
        at javax.net.DefaultServerSocketFactory.createServerSocket(DashoA6275)
        at electric.net.socket.tcp.TCPSocketFactory.createServerSocket(Unknown Source)
        at electric.net.socket.SocketServer.createServerSocket(Unknown Source)
        at electric.net.socket.SocketServer.startup(Unknown Source)
        at electric.net.http.WebServer.startup(Unknown Source)
        at electric.net.http.WebServer.startWebServer(Unknown Source)
        at electric.server.http.HTTP.startup(Unknown Source)
        at electric.server.http.HTTP.startup(Unknown Source)
        at electric.server.http.HTTP.startup(Unknown Source)
        at CounterServer.main(CounterServer.java:17)
make: *** [run-CounterServer] Error 1
% []

同じポート番号が使われていた時、Address already in use というエラーが 出る。その時には、まず、自分のプログラム(の残がい)がどこかで動いてい ないかを調べる。他の同じ演習をしている人のプログラムの残がいを見つけた 時には、残がいだったら殺してもらう。本当に別のプログラムに使われていた 時には、別のコンピュータに移動するか、以下のように別のポート番号を指定 して走らせる。

% make run-CounterServer port=1231 [←]
java -classpath .:/略/GLUE-STD.jar:./略/servlet.jar CounterServer 1231 Counter/c1
GLUE 1.2 (c) 2001 The Mind Electric
startup server on http://127.0.0.1:1231/soap
Ok.
(最後に ^C で止める)

◆WSDL の取得

Glue では、オブジェクトをRegistry.publish() すると、WSDL が自動的に作 られ、HTTP でアクセス可能になる。WWW ブラウザで読むこともできる。下の 例では、wget コマンドで取得している。
% telnet 127.0.0.1 8080 [←]
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
GET /soap/Counter/c1.wsdl HTTP/1.0[←]
[←]
HTTP/1.1 200 OK
Content-Type: text/xml
Server: GLUE/1.2
Content-Length: 2959

<?xml version='1.0' encoding='UTF-8'?>
<!--generated by GLUE-->
<definitions name='Counter' ... > ...
</definitions>
Connection closed by foreign host.
% []
得られた WSDLの例

◆アクセス制御

RPC では、クライアント、サーバ、相互にアクセス制御を行う必要がある。

Web サービスでは、WWW の技術を使ってアクセス制御を行うことができる。

Java で記述された Glue では、これに加えて、Java のセキュリティ機能も使 える。

■Java RMI

Remote Method Invocation. Java 言語で書かれたプログラム間のORB (Object Request Broker). ORB は、オブジェクト指向が入っている RPC (Remote Procedure Call).

別の Java 仮想計算機間オブジェクトのメソッドを呼び出す仕組み。 RMI いくつかの層を見えなくする。

◆HORB

RMI が入ったのは、JDK 1.1 から。それ以前に、電総研(現在の産電総研)の URL_Ref(http://staff.aist.go.jp/hirano-s/,平野氏)によるHORB が開発された。

比較的軽い。組込みシステムで使われているような遅めのコンピュータに向い ている。

◆(Java で TCP/IP を直接使う)

RMI より下のレベルでは、TCP/IP を直接使うクラスがある。
java.net.Socket
TCP/IP クライアント側
java.net.ServerSocket
TCP/IP サーバ側
java.net.DatagramSocket
UDP/IP
java.net.MulticastSocket
UDP/IP マルチキャスト

◆RMI におけるサーバとクライアント

サーバもクライアントも Java で書かれたオブジェクト。 サーバは、実行に先立ち、RMI のサーバであると宣言する必要がある(非対称)。

中身が空のインタフェース Remote を付ける。

src/java/rmi/Remote.java
public interface Remote {}
これを見つけると、コンパイラが特殊なコードを生成する。
Stub
クライアント側のスタブ(RPC用語)。サーバのオブジェクトと同じインタフェース を持つ。受け付けたメソッド呼出しを通信に変換する。
Skelton
サーバ側のスタブ(RPC用語)。
binding のために registry がある。転送には、TCP/IP が使われる。

◆java.rmi.server.UnicastRemoteObject

サーバ側で継承すべきクラス。
java.lang.Object (class)
  |
  +--java.rmi.server.RemoteObject (class)
        |
        +--java.rmi.server.RemoteServer (class)
              |
              +--java.rmi.server.UnicastRemoteObject (class)
これに加えて、interface Remote を implements する。

クライアント側は、これに比べて簡単。違いは、サーバに接続する部分部分と、 分散固有の例外を受ける部分。

■ローカルのカウンタ

ローカルで動作するプログラムを、RMI で動作するように書き直す。

■RMIによるカウンタの利用

ローカルで動作するプログラムを、RMI で動作するように書き直す。

◆リモート・インタフェース

RMI でアクセス可能にするオブジェクトには、必ずインタフェースを定義する。

[IRCounter.java]

   1: //
   2: // IRCounter.java
   3: //
   4: 
   5: public interface IRCounter extends java.rmi.Remote
   6: {
   7:     public void up() throws java.rmi.RemoteException;
   8:     public int  getValue() throws java.rmi.RemoteException;
   9:     public void reset(int newVal) throws java.rmi.RemoteException;
  10: }
リモート・インタフェースの特徴:

◆リモート・オブジェクト

[RCounter.java]
   1: //
   2: // RCounter.java
   3: //
   4: 
   5: import java.rmi.*;
   6: import java.rmi.server.*;
   7: 
   8: public class RCounter extends java.rmi.server.UnicastRemoteObject
   9:     implements IRCounter
  10: {
  11:     int val;
  12:     public RCounter(int initVal) throws RemoteException
  13:     {
  14:         super();
  15:         val = initVal ;
  16:     }
  17:     public void up() throws java.rmi.RemoteException
  18:     {
  19:         val ++ ;
  20:     }
  21:     public int getValue() throws java.rmi.RemoteException
  22:     {
  23:         return( val );
  24:     }
  25:     public void reset(int newVal) throws java.rmi.RemoteException
  26:     {
  27:         val = newVal ;
  28:     }
  29: };
  30: 
java.rmi.server.UnicastRemoteObject の代りに java.rmi.activation.Activatable を使うと呼び出された時に起動される。

オブジェクトは、serialize (直列化) されてコピーで渡される。

◆サーバ

[RCounterServer.java]
   1: //
   2: // RCounterServer.java
   3: //
   4: 
   5: import java.rmi.*;
   6: import java.rmi.server.*;
   7: 
   8: public class RCounterServer
   9: {
  10:     public static void main(String argv[])
  11:     {
  12:         if( argv.length != 1 )
  13:         {
  14:             System.err.println("Usage% java RCounterServer rmiregistry-portno");
  15:             System.exit( 1 );
  16:         }
  17:         String rmiregport = argv[0];
  18: 
  19:         if( System.getSecurityManager() == null )
  20:             System.setSecurityManager( new RMISecurityManager() );
  21: 
  22:         try
  23:         {
  24:             IRCounter c1 = new RCounter( 10 );
  25:             String name = "rmi://localhost:"+rmiregport+"/Counter/c1" ;
  26:             Naming.rebind( name,c1 );
  27:         }
  28:         catch (Exception e)
  29:         {
  30:             System.err.println("RCounterServer error:"+e.getMessage());
  31:             e.printStackTrace();
  32:         }
  33:     }
  34: };
Exception を catch したら、最後に System.exit( 1 ) した方がよい。

rmiregistry に登録する時には、次のような URL が使える。

myhost は、自分自身のホスト名。実験では、よく localhost を使う。

port は、rmiregistry が使うポート番号で、デフォルトでは 1099。 大勢で1つのホストを使うとぶつかる。

string は単なる文字列。フラットな名前空間。

◆クライアント

[RCounterClient.java]
   1: //
   2: // RCounterClient.java
   3: //
   4: 
   5: import java.rmi.*;
   6: 
   7: class RCounterClient
   8: {
   9:     public static void main(String argv[])
  10:     {
  11:         if( argv.length != 2 )
  12:         {
  13:             System.err.println("Usage% java RCounterClient hostname rmiregistry-portno");
  14:             System.exit( 1 );
  15:         }
  16:         String hostname = argv[0];
  17:         String rmiregport = argv[1];
  18:             
  19:         IRCounter c1 ;
  20:         try
  21:         {
  22:             if( System.getSecurityManager() == null )
  23:                 System.setSecurityManager( new RMISecurityManager() );
  24:             String name = "rmi://"+hostname+":"+rmiregport+"/Counter/c1";
  25:             c1 = (IRCounter) Naming.lookup( name );
  26:             for( int i=0 ; i<3 ; i++ )
  27:             {
  28:                 c1.up();
  29:                 System.out.println("c1.value=="+c1.getValue());
  30:             }
  31:         }
  32:         catch (Exception e)
  33:         {
  34:             e.printStackTrace();
  35:         }
  36:     }
  37: };

◆コンパイルと実行(サーバ側)

サーバ側では、ウインドウを2枚開く。

一方のウインドウで rmiregistry を起動する。ポート番号は、ぶつからない ように uid を使う。rmiregistry は自動的に終了しないので、実験が終わっ たら ^C (Control-C)で殺す。

% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Makefile [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/IRCounter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/RCounter.java [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make run-rmiregistry [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar IRCounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar RCounter.java
rmic RCounter
rmiregistry 8080
(最後に ^C で止める)
一方のウインドウでサーバを動作させる。サーバもは自動的に終了しないので、 実験が終わったら ^C (Control-C)で殺す。
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Makefile [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/IRCounter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/RCounter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/RCounterServer.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/RCounterServer.policy [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make RCounterServer [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar IRCounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar RCounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar RCounterServer.java
rmic RCounter
% make run-RCounterServer [←]
java -Djava.security.policy=./RCounterServer.policy RCounterServer 8080
(最後に ^C で止める)

◆コンパイルと実行(クライアント側)

クライアント側では、ウインドウを1枚開く。
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Makefile [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/IRCounter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/RCounter.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/RCounterClient.java [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/RCounterClient.policy [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make RCounterClient [←]
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar IRCounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar RCounter.java
javac -classpath .:/略/GLUE-STD.jar:./略/servlet.jar RCounterClient.java
rmic RCounter
% make run-RCounterClient [←]
java -Djava.security.policy=./RCounterClient.policy RCounterClient localhost 8080
c1.value==11
c1.value==12
c1.value==13
% make run-RCounterClient [←]
java -Djava.security.policy=./RCounterClient.policy RCounterClient localhost 8080
c1.value==14
c1.value==15
c1.value==16
% []

◆セキュリティ

RMI では、クライアント、サーバ、相互にアクセス制御を行う必要がある。

Java の標準のセキュリティのポリシー(java コマンドで利用される)は、 jre/lib/security/java.policy に記述されている。

System.setSecurityManager( new RMISecurityManager() ) した時には、 標準よりきつくなる。実験する時には、少し緩めないとつながらない。

以下の例では、localhost と *.tsukuba.ac.jp からのアクセスを許している。

[RCounterServer.policy]

   1: //
   2: // RCounterServer.policy
   3: //
   4: 
   5: grant { 
   6:         permission java.net.SocketPermission "localhost:1024-", "accept,listen,connect,resolve";
   7:         permission java.net.SocketPermission "*.tsukuba.ac.jp:1024-", "accept,listen,connect,resolve";
   8: };
[RCounterClient.policy]
   1: //
   2: // RCounterClient.policy
   3: //
   4: 
   5: grant { 
   6:         permission java.net.SocketPermission "localhost:1024-", "listen,connect,resolve";
   7:         permission java.net.SocketPermission "*.tsukuba.ac.jp:1024-", "listen,connect,resolve";
   8: };
クライアント側では、うまく設定すると、サーバ側に置かれたクラスを動的に http や ftp でロードして実行できる。その時には、 java.rmi.server.codebase プロパティを使う。

◆直列化(serialize)

marshaling/unmarshaling のことを、Java では serialization/deserialization という。日本語では、直列化、または、整列 化。

RMI でオブジェクトを渡す時には、interface Serializable を implements する。

serialize 不要のフィールドには、transient をつける。

serialization は、RMI だけでなく、オブジェクトをファイルに落とす時にも 使える。

ある。

■Ruby言語によるローカルのカウンタ

ローカルで動作するJava版のプログラム Ruby言語で書き直す。

◆ローカル/インタフェース

Ruby (dRuby) には、インタフェースは存在しない。 Ruby の変数には静的な片付けがなされない。 メソッドの名前が一致すれば呼べる。

◆ローカル/オブジェクト

[counter.rb]
   1: #
   2: #       counter.rb
   3: #
   4: 
   5: class Counter
   6:       def initialize(initVal)
   7:           @val = initVal
   8:       end
   9: 
  10:       def up()
  11:           @val += 1
  12:       end
  13: 
  14:       def getValue()
  15:           return @val
  16:       end
  17: 
  18:       def reset(newVal)
  19:           @val = newVal
  20:       end
  21: end
3つの手続きとコンストラクタからなる。

◆ローカル/利用

[counter-local.rb]
   1: #
   2: #       counter-local.rb
   3: #
   4: 
   5: require 'counter.rb'
   6: 
   7: def main(argv)
   8:         c1 = Counter.new( 10 )
   9:         0.upto(3-1) {|i|
  10:             c1.up()
  11:             printf("c1.value==%d\n",c1.getValue())
  12:         }
  13: end
  14: 
  15: main(ARGV)
Java に合わせるために、main() を作成している。

coins での実行例:

% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Makefile [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/counter.rb [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/counter-local.rb [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make run-counter-local.rb [←]
ruby counter-local.rb
c1.value==11
c1.value==12
c1.value==13
% make run-counter-local.rb [←]
ruby counter-local.rb
c1.value==11
c1.value==12
c1.value==13
% []

オブジェクト c1 を、main で作って実行している。実行する度に新しいオブ ジェクトが new される。

■dRubyによるカウンタ

ローカルで動作するプログラムを、dRuby でアクセス可能なオブジェクトとし て動作するように書き直す。

◆サーバ

[counter-server.rb]
   1: #
   2: #       counter-server.rb
   3: #
   4: 
   5: require 'counter.rb'
   6: require 'drb/drb'
   7: 
   8: def main(argv)
   9:         if( argv.length != 1 )
  10:             $stderr.printf("Usage: %% ruby $0 port\n")
  11:             exit( 1 )
  12:         end
  13:         portno = argv[0]
  14:         url = "druby://localhost:" + portno
  15:         c1 = Counter.new( 10 )
  16:         DRb.start_service( url, c1 )
  17:         printf("startup server on %s\n", url)
  18:         printf("Ok.\n")
  19:         sleep()
  20: end
  21: 
  22: main(ARGV)
このプログラムは、1つの引数を取る。 Java 版とは違って、「カウンタ・オブジェクトの名前」がとれない。 ポート番号としては、以下の Makefile では、8080 が指定されている。他の 人とぶつからないように、クライアント側、サーバ側とも書き換えることが望 ましい。

DRb.start_service() で、dRuby のサーバを起動している。その結、果新しい スレッドが作られる。このプログラムは、main() が終了してしまうと全体が終 了してしまうので、sleep() を呼んでmain() が終了しないようにしている。

◆クライアント

[counter-client.rb]
   1: #
   2: #       counter-client.rb
   3: #
   4: 
   5: require 'counter.rb'
   6: require 'drb/drb'
   7: 
   8: def main(argv)
   9:         if( argv.length != 2 )
  10:             $stderr.printf("Usage: %% ruby $0 hostname port\n")
  11:             exit( 1 )
  12:         end
  13:         hostname = argv[0]
  14:         portno   = argv[1]
  15:         url = "druby://" + hostname + ":" + portno
  16:         printf("url is %s\n",url)
  17:         c1 = DRbObject.new_with_uri(url)
  18:         0.upto(3-1) {|i|
  19:             c1.up()
  20:             printf("c1.value==%d\n",c1.getValue())
  21:         }
  22: end
  23: 
  24: main(ARGV)
このプログラムは、つの引数を取る。
hostname
サーバが動いているホストの名前。同じホストで動いている 時には、localhost を与える。
portno
ポート番号。サーバを実行した時と同じもの。
DRbObject.new_with_uri() により、クライアント側のスタブ(プロキシ)が自 動的に生成される。 このオブジェクトは、class Counter のものではない。このオブジェクトにア クセスすると、RPC が行われる。このプログラムは、3回ループを回って、 up() という手続きと getValue() という手続きを呼んでいる。合計、6 回の RPC が行われる。

◆実行(サーバ側)

実行する時には、サーバ側とクライアント側でそれぞれ1つウインドウを開く。

サーバは、は自動的に終了しないので、実験が終わったら ^C (Control-C)で 殺す。

% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Makefile [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/counter.rb [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/counter-server.rb [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make run-counter-server.rb [←]
ruby counter-server.rb 8080
startup server on druby://localhost:8080
Ok.
(最後に ^C で止める)

◆実行(クライアント側)

% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/Makefile [←]
% wget http://www.is.tsukuba.ac.jp/~yas/sie/csys-2009/2010-01-22/ex/counter-client.rb [←]
% emacs Makefile (環境に合わせて Makefile の修正) [←]
% make run-counter-client.rb [←]
ruby counter-client.rb localhost 8080
url is druby://localhost:8080
c1.value==11
c1.value==12
c1.value==13
% make run-counter-client.rb [←]
ruby counter-client.rb localhost 8080
url is druby://localhost:8080
c1.value==14
c1.value==15
c1.value==16
% []
クライアントのプログラムを2回実行しても、同じカウンタが使われている所 に注意する。

練習問題

★問題(601) パブリックなWebサービスの利用

Amazon Web Services、または、その他のパブリック に利用になっている Web サービスのクライアント側のプログラムを記述しなさい。 プログラミング言語としては、Java、Ruby、C言語等の採点者が理解できるもの を用いなさい。

★問題(602) コンテナ

次の何れかのオブジェクトを、XML Web Service、Java RMI、Ruby dRuby、また は、SunRPC を用いて遠隔からアクセスできるようにしなさい。 要素は、整数に限定してもよい。サーバのプログラムは、1つだけつくりなさ い。クライアントとしては、要素を追加するものと要素を取出すものの2つを 作りなさい。

★問題(603) カウンタのインスタンスの追加

カウンタの例(XML Webサービス, Java RMI,および, dRuby) では、カウンタは1つしか作られない。これらのいずれかを修正して、複数の カウンタが使えるようにしなさい。

ヒント: main() では、カウンタそのものではなく、カウンタ・オブジェクト を生成するオブジェクトを追加し、遠隔からアクセス可能にする。新たにカウ ンタ・オブジェクトを生成するたびにそれを遠隔からアクセス可能にする。

dRuby の場合は、名前が付けられないので面倒かもしれない。

★問題(604) カウンタへのメソッドの追加

カウンタの例(XML Webサービス, Java RMI,または, dRuby )に、次のようなメソッドを追加しなさい。

★問題(605) ディレクトリ一覧

次のような機能を持つサーバとクライアントをXML Web サービス、 Java RMI、dRuby、または、SunRPC で実現しなさい。 なお SunRPC のソースには、 demo/dir に類似のプログラムが含まれている。

★問題(606) Java言語以外の言語による XML Web サービス

Web サービスによるカウンタのクライアント、ま たは、サーバをJava言語以外の言語を使って記述しなさい。

★問題(607) 自由課題

Web サービスによるカウンタと同程度の複雑さを持 つインタフェースを定義し、XML Web サービス、Java RMI、dRuby、または、 SunRPC で呼び出せるようにしなさい。ここで同じ程度の複雑さとは、内部に変 化する変数を含むものである。メソッドの数は少なくてもよい。

★問題(608) OMG CORBA

Web サービスによるカウンタ、またはそれと同程度 の複雑さを持つオブジェクトを、OMG CORBA を使って呼び出せるようにしなさ い。

言語は Java でなくてもよい。


Last updated: 2010/01/22 11:44:35
Yasushi Shinjo / <yas@is.tsukuba.ac.jp>