情報システム技術論’08 — ＃ 2
久野靖∗

2008.4.171通信の構造化

1.1

遠隔手続き呼び出し (RPC)

proxyX

Y

サーバ側

Z□ ネットワーク経由の通信は手順が面倒

RPC
stubRPC• 前回やったように「ソケットを用意→接続を張る→送受信 (N 回) →切断」

クライアント側

XYZ• もっとプログラマに書きやすくできないか?
• プログラマにとってより「手慣れた」インタフェー
ス (抽象化) は?

• 「本物」の手続きはサーバの中にありこれがネット□ もちろん、手続き呼び出し (サブルーチンコール) は非越しに呼ばれる常によく使うし、手慣れている。

□ RPC ジェネレータ

• ネットワーク越しの通信も手続き呼び出しと同様に• 上記のスタブ類を用意するのはすごく面倒に思えるが…

考えることができる。
「パラメタを渡して呼ぶ→処理を待つ→結果を受け取る」
手続き呼び出し遠隔手続き呼び出し• 実際には「名前」「パラメタ個数とそれぞれの型」「返値の型」を指定してやれば、自動的に両側のスタブサーバ側を生成してくれるジェネレータ (RPC ジェネレータ、スタブジェネレータ) が用意されている。

送信• パラメタや返値が複雑な型のときも、それを詰め込み (marshal)、取り出し (unmarshal) を行うコード待つ受信が用意される。
□ RPC のバインディング• クライアント側:パラメタ送信→待つ→結果受信• このように RPC では一見手続き呼び出しのようにサーバ上のコードが呼び出せるが、実際には「ネットワー• サーバ側: 「パラメタ受信→処理→結果送信」の無限反復クの向こう」への通信になっている。

□ 実際にはさらに次のような仕掛けが入ることが多い• このため、クライアント側では「どのホスト宛接続する」という情報を供給する必要がある。

• サーバ側に複数の手続きがあり、それを区別して呼• サーバ側では RPC 呼び出し全体を管轄し、指定されたプログラムのスタブへの橋渡しを行うための「RPCび出せる (どれを呼ぶかを名前で指定して呼び出す)• クライアント側/サーバ側に「呼ばれる手続き (プロキシ)」「呼ぶ手続き (スタブ)」が用意されていて、他の部分からは普通の手続きに見える。

サーバ」プログラムを動かしている必要がある。

• FreeBSD、Solaris などでは ONC RPC と呼ばれる RPCシステムが動いていて、「rpcinfo」でサーバ側のプログラム登録状況が見られる。

∗ 筑波大学大学院経営システム科学専攻

1

1.2

RPC のおもちゃを作る

フィールドの値をサーバに書き込んだりする。

• get() と put() のスタブは rpc() という通信用メソッドを呼び出す。そのとき「何のメソッドか」を□ では、前回の例題をちょっと直して、RPC の「おもちゃ」を作ってみる。サーバ内に data という文字列変数が存在していて、その内容を読み出す get() とそこに書き込む put() というメソッドを遠隔呼び出しする。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;import java.net.*;表す情報をくっつける。

• rpc() は前回やったようにしてサーバと通信する (文字列を送信し、文字列を受信する)。
□ 次はサーバ側。こちらも前回とほぼ同様。
import java.net.*;import java.io.*;public class Sample21 extends JFrame {JTextField f1 = new JTextField();
public class Sample21Server {JButton b1 = new JButton("Put");
static String data = "";JButton b2 = new JButton("Get");public static void main(String[] args)
JLabel l1 = new JLabel("start...");
throws Exception {String host;ServerSocket ss = new ServerSocket(4192);
public Sample21(String h) {while(true) {host = h;Socket cs = ss.accept();
setSize(400, 400);
PrintWriter out = new PrintWriter(
setDefaultCloseOperation(EXIT_ON_CLOSE);
cs.getOutputStream(), true);Container c = getContentPane(); c.setLayout(null);
BufferedReader in = new BufferedReader(c.add(f1); f1.setBounds(40, 40, 180, 30);
new InputStreamReader(cs.getInputStream()));
c.add(b1); b1.setBounds(40, 80, 80, 30);
String line = in.readLine();c.add(b2); b2.setBounds(130, 80, 80, 30);
switch(line.charAt(0)) {c.add(l1); l1.setBounds(20, 360, 360, 40);case ’p’: put(line.substring(1)); line = ""; break;b1.addActionListener(new ActionListener() {case ’g’: line = get(); break;public void actionPerformed(ActionEvent evt) {default:put(f1.getText());}}
out.println(line); cs.close();});
if(line.equals("bye")) break;b2.addActionListener(new ActionListener() {}public void actionPerformed(ActionEvent evt) {ss.close();
f1.setText(get());}}
static void put(String s) { data = s; }});
static String get() { return data; }}}private void put(String str) { rpc("p"+str); }private String get() { return rpc("g"); }• 渡されて来た文字列の先頭を見て「どの手続き呼び
private String rpc(String str) {try {出しか」を区分し、各手続きを呼び出す。
Socket cs = new Socket("localhost", 4192);• get() と put() は先に述べたように data を読み書
PrintWriter out = new PrintWriter(cs.getOutputStream(), true);
きする。
BufferedReader in = new BufferedReader(new InputStreamReader(cs.getInputStream()));□ 演習: このプログラムを動かしてみよ。サーバを 1 つout.println(str); str = in.readLine(); cs.close();にして、複数の人が互いに文字列を共有できることを確} catch(Exception ex) { l1.setText("!" + ex); }return str;
認せよ。
}
□ 演習: このプログラム (サーバ 1 つ) を使って、「各自public static void main(String[] args) {new Sample21(args[0]).setVisible(true);が思い描いた数値を合計する」のをやってみよ。何が問}題か検討せよ。
}□ 演習:• 今回からは「java Sample21 localhost」のように上記の問題を解決するような遠隔手続きとはどんなものか考えよ。実際にそれを組み込め。

実行時にサーバのホスト名を指定するようにした (実験がやりやすいように)。

1.3• 窓を作るあたりは同じ。

ORB と CORBA• ボタンは 2 つにし、それぞれのボタンは get() と □ オブジェクト指向と ORBput() でフィールドにサーバから値を読み出したり2

• ここまでは「手続き指向」だったが、オブジェクト指2Java RMIクトのメソッド」と考える方が素直に思えるように

2.1

Java RMI の概要なってきた。

□ RMI(Remote Method Invocation) --- 遠隔オブジェ

向の普及とともに、呼び出される対象は「オブジェ• サーバ側には「複数のオブジェクト」があり、それぞクトに対してメソッド呼び出しを行うような機能全般。

れが固有のデータを持っている。そのオブジェクト□ Java では比較的初期からそのための機能が組み込まれのメソッドを RPC で呼び出すことで、その固有デーている→ Java RMI。

タに間接的にアクセスできる。

• ORB、スタブジェネレータなども一通り揃っている。

• その遠隔呼び出しの橋渡し (バインディング) →ORB(Object Request Broker) と呼ばれるようにな• Java 言語の枠内で揃うようになっているので、CORBA
より簡単。

った。

• データだけでなく、コードも転送可能→大きな特徴。

□ CORBA --- ORB の標準仕様。1990 頃からある。

□ 以下で先の例題をもとにした「文字列記憶サービス」を• ORB と し て は 色々な も の が 作 ら れ た が 、相 互 運
用性のために共通のものを作ろうという話にな用いて RMI の必要事項を説明していく。

り、OMG(Object Management Group) が 作った 標
準仕様が CORBA(Common Object Request Broker

2.2Architecture)

例題:文字列サービス□ [1] インタフェース仕様• CORBA 仕様に従っているなら、どのクライアントとどの ORB を組み合わせても呼び出し利用できる (はず)• Java RMI では CORBA IDL の代わりに Java 言語に備わっているインタフェース定義で遠隔オブジェク• ただし Microsoft はこれにくみせず COM/DCOM を採用 (Windows 用の CORBA ソフトももちろん作られたトの仕様を規定する。

が) 。

• このインタフェース仕様は必ず「extends Remote」を指定する。これにより、このインタフェースは遠• CORBA IDL(Interface Definition Language)--- どんなメソッドがどんなパラメタを持っているか、を指定する標準的な記法として使われるように隔オブジェクトの仕様として使われることが宣言さなった。(スタブジェネレータに食べさせるのが本• こ の イ ン タ フェー ス に 現 れ る メ ソッド は す べ てRemoteException という例外を発生させ得るようれる。

来の目的だったがどちらかというと人間が読む仕様記述の記法として…)に指定する。

• 実際、ネット経由で呼び出すのでネットワークが切断したらエラーになるから、そのような場合を必ず□ CORBA の現在…

• 汎用的すぎて面倒であり遅かった。そしてあまり普及しないうちに Web アプリケーションの時代になっ考慮するべき。(今回は例題を短くするためにさぼっている。)てしまった。

import java.rmi.*;• Web アプリでは遠隔呼び出しに CORBA よりは XML-RPC
や SOAP(Simple Object Access Protocol) など interface Sample13IF extends Remote {public String exec(String s) throws RemoteException;

XML 系の技術を使う。Web アプリ側が接続先を指定できるので ORB はなくても済んでしまう。

}• OMG は今は UML などの方に力を入れている (UML は □ 解説:
メジャーになったのでそれなりに成功している)。

• Remote インタフェースである。

• それはそれとして、現在でも Java などから一応• メソッドは文字列を 1 つ受け取り 1 つ返す exec()のみ。

CORBA は使えるようになっている。

• このメソッドは RemoteException を発生させ得る。
□ [2] サーバ側コード3• コマンド入力のための入力ストリーム用意。

• サ ー バ 側 に 置 か れ た オ ブ ジェク ト は extends
UnicastRemoteObject と 指 定 さ れ て い る 必 要 がある。

• サーバオブジェクトを作る。
• レジストリ (登録サーバ) をポート 4918 で作る。

• サーバ側に置かれるオブジェクトは [1] で宣言したインタフェースを実装する必要がある。

• レジストリにサーバを str1 という名前で登録。
• コマンド入力ループで、bye という行が入力されるまで 1 行入力ごとに現在のサーバの文字列表現を書• このクラス名を指定して Java 付属のスタブジェネ
レータ rmic を実行することでクライアントスタブき出す。

(Java ではスケルトンと呼ぶ)、サーバスタブ (Java□ [3] クライアント側コード

では単にスタブと呼ぶ) が生成される。

• クライアントはこれまでと同じく GUI を持つ。

• 呼び出し方は次の通り (☆のところにクラス名を指定)% /compat/linux/usr/local/Java/j2sdk1.4.2_10/bin/rmic ☆

• 初期設定のところでサーバのリモートオブジェクトを取得• あとはこれに対するメソッド呼び出し→ RMI によりサーバで実行□ サーバ側コードはクラス本体部分と main() に分けて説

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.rmi.*;

明する。まずクラス本体部分から:
import java.rmi.*;import java.rmi.server.*;
import java.rmi.registry.*;import java.io.*;public class Sample13 extends JFrame {Sample13IF srv;
JTextField f1 = new JTextField();
public class Sample13Server
JButton b1 = new JButton("Exec");
extends UnicastRemoteObject implements Sample13IF {JLabel l1 = new JLabel("start...");
String data;
public Sample13() throws Exception {public Sample13Server() throws RemoteException {srv = (Sample13IF)
data = "?";
Naming.lookup("rmi://localhost:4918/str1");}
setSize(400, 400);
public String exec(String s) {setDefaultCloseOperation(EXIT_ON_CLOSE);
data = data + s; return data;
Container c = getContentPane(); c.setLayout(null);}c.add(f1); f1.setBounds(40, 40, 180, 40);
public String toString() {c.add(b1); b1.setBounds(240, 40, 80, 40);
return data;c.add(l1); l1.setBounds(20, 360, 360, 40);}
b1.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent evt) {□ 解説 (サーバクラス):
try {f1.setText(srv.exec(f1.getText()));• extends UnicastRemoteObject の指定必要。
} catch(Exception ex) { l1.setText("!" + ex); }}• implements Sample13IF の指定必要。
});• 変数 data は記憶する文字列を保持。
}
public static void main(String[] args)
public static void main(String[] args)
throws Exception {throws Exception {new Sample13().setVisible(true);
BufferedReader in = new BufferedReader(}new InputStreamReader(System.in));}Sample13Server srv = new Sample13Server();
Registry reg = LocateRegistry.createRegistry(4918); □ 解説:
reg.bind("str1", srv);
System.out.print("command> ");• Naming.lookup() でホスト、ポート、名前を指定→
while(!in.readLine().equals("bye")) {そのホストの RMI レジストリ (先に main() で起動
System.out.println(srv.getData());
したもの) に接続してリモートオブジェクトを取得
System.out.print("command> ");}• ボタン押した時は単に srv.exec() を呼ぶだけでよ
System.exit(0);い (簡単)。
}}□ 演習:

文字列サービスを動かして前の演習と同様に確認してみよ。

□ 解説 (main):4

3

オブジェクトとコードのモビリティ

3.1

モビリティ(可動性) とその内容• コピーするということは、行った先と手元は別物になるということ。

□ モビリティ(mobility) →ネットワークを通じて「××」• それでよい場合もあるが、オブジェクトは勝手に複製されてほしくないという場合もある。
• その場合、オブジェクトは動かずにずっとどこかに存在し、そのオブジェクトを指す参照はあちこちにが移動可能なこと。

• 通常のデータ (テキストとか) が移動するのは当り前渡して、それらの参照からオブジェクトを呼び出しだが…て利用、という考え方もある← CORBA など。

• 「通常」とは?

たとえばポインタは?

→ポインタ• しかしその方法しかないとなると、すべて RMI のようになるため遅いという問題がある。

は「特定のマシンの上でのメモリアドレス→そのまま別のマシンに移動させても「とんでもない番地を• CORBA では言語が様々なのでどうにもならないがJava では「コピー」も「参照」も可能。参照は先に指すポインタ」になるだけ。

• 正しくは「ポインタの指す先のデータ」もコピーする必要がある。

やったのがそれ。ではコピーは…
□ Java の場合は…

転送
A B C

• Serializable インタフェースを implements で指定したクラスのオブジェクト→直列化可能 (ネットA B C

ワーク経由での転送、ファイルへのそのままの格納が可能)

転送?
X Y

• そのときに前記☆のような制御は用意されている。さらに特別な処理をしたい場合はそれぞれのオブジェ?

P Q R

X Y

P Q R

クトにメソッドを用意することで制御可能。

• 直列化可能なクラスのインスタンス変数は基本型 (単純なデータ) または再び直列化可能なクラス型でな□ 構造のあるデータのコピーは必ずしも簡単ではない…

• 共有部分があったらどうするか?

ければならない (送れる必要があるから)。

→ その形を保っ• インスタンス変数に transient というキーワードをてコピーしたい。

• ループがあったらどうするか?
コピーしたい。

指定しておくとその変数は送らない (復元したとき→ その形を保ってP Q R

は null になる)。そのため上記の制約もない。
□ コードモビリティ(モバイルコード)

P Q R

転送

• 受動的なデータだけならただ送ればよいが、オブジェクト指向言語では「データ+メソッド」がオブジェ

P Q R

P Q Rクト。

• このため、オブジェクトのデータを送ったが送った転送先にそのオブジェクトのメソッドのコードがなかった、では済まされない。
X YX Y• 1 つの方法…あらかじめ必要なクラスのコードは用意させる。それがある状態でのみ送ることとする (普X Y通のアプローチ)。
X Y• 別の方法…オブジェクトを送る時、送り先にそのクラスのコードがまだなければコードも送る (Java の• ☆ポインタを順次たどっていくが、既に転送に着手したのと同じものに到達したら、新たに転送する代アプローチ) ← コードモビリティ

わりに「既に転送した何番目と同じ」という情報を □ なぜ Java でこれができるか?
送り、復元時にもそれに応じて復元。

• もともと「アプレットのための言語」→コードをネット経由で転送することを前提として開発された言語+□ コピー vs 参照セマンティクス

環境 ← i-アプリもそう5• 具体的には、Java バイトコードを送ってそれぞれの}
public String toString() { return str; }実行環境の JVM (バーチャルマシン) で実行→バイ}
public static void main(String[] args)
throws Exception {BufferedReader in = new BufferedReader(new InputStreamReader(System.in));Sample14Server srv = new Sample14Server(new MyData());
Registry reg = LocateRegistry.createRegistry(4918);
reg.bind("str1", srv);
System.out.print("command> ");while(!in.readLine().equals("bye")) {System.out.println(srv.toString());
System.out.print("command> ");}System.exit(0);}トコードは CPU 種別などに影響されずすべて共通→
これを送れば OK。

• ただし、「コードが送られる」ことにはセキュリティ上の危うさがあるのでそのための工夫も必要 (今回は詳しくはやらない)。

3.2

例題:文字列サービス改訂版□ [1] インタフェース仕様
import java.rmi.*;import java.io.*;}interface Sample14IF extends Remote {public Data get() throws RemoteException;
public void put(Data d) throws RemoteException;interface Data extends Serializable {public String exec(String s);
}
}

□ 解説:

•• サーバは最初 MyData オブジェクトを与えて初期化する。

• get()、put() はその Date オブジェクトをやりとり。

□ 解説:• toString() では Data オブジェクトの toString()を返す。

• インタフェース Sample14IF の中に Data というイン• クラス MyData はこれまでのサーバと類似しているタフェースを定義している。このインタフェースはが、通信機能はなく、単にデータを保持し計算。
extends Serializable と指定しているので、これを実装するオブジェクトは転送可能。
□ [3] クライアント側コード• Data オ ブ ジェク ト に は こ れ ま で サ ー バ に あった• フィールド、ボタンとも 2 つに増えている。
exec() を持たせる。
• 最初のフィールドには「rmi://sma:4918/str1」な• サーバは今度はこの Data オブジェクトを取り出した
どの URI を与えて Conn ボタンを押すとサーバからり戻したりするようなインタフェースに直した。
Data オブジェクトを取得。その状態で再度 Conn ボ□ [2] サーバ側コード
タンを押すと取得してあった Data オブジェクトを書き戻す。

• Data イ ン タ フェー ス を implements す る ク ラ ス• 2 番目のフィールド/ボタンはこれまでの機能に類似。
ただしサーバが処理を行うのではなく、手元に持っMyData を用意。このオブジェクトがクライアント側に転送され、そこで動作する。て来た Data オブジェクトが処理を実行する。

import java.rmi.*;import java.rmi.server.*;
import java.rmi.registry.*;import java.io.*;import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.rmi.*;public class Sample14Server
extends UnicastRemoteObject implements Sample14IF { public class Sample14 extends JFrame {Sample14IF srv;
Data data;
Sample14IF.Data data;
public Sample14Server(Data d)
JTextField f0 = new JTextField();
throws RemoteException { data = d; }JButton b0 = new JButton("Conn");public Data get() { return data; }JTextField f1 = new JTextField();
public void put(Data d) { data = d; }JButton b1 = new JButton("Exec");
public String toString() { return data.toString(); }JLabel l1 = new JLabel("start...");
static class MyData implements Data {public Sample14() throws Exception {String str = "?";
setSize(400, 400);
public String exec(String s) {setDefaultCloseOperation(EXIT_ON_CLOSE);
str = str + s + "!"; return str;

6

Container c = getContentPane(); c.setLayout(null); 4.1 「モバイルエージェント」
c.add(f0); f0.setBounds(40, 40, 180, 30);c.add(b0); b0.setBounds(240, 40, 80, 30);
□ モバイルエージェント == 自律的に動く「コード+デー
c.add(f1); f1.setBounds(40, 80, 180, 30);タ」
c.add(b1); b1.setBounds(240, 80, 80, 30);c.add(l1); l1.setBounds(20, 360, 360, 30);
• 「持ち主」がエージェントを生成しネットワークにb0.addActionListener(new ActionListener() {放つ
public void actionPerformed(ActionEvent evt) {try {• エージェントはネットワーク上をあちこち移動しな
if(srv == null) { // connect & get
がら仕事をこなす (情報の収集や伝達など)
srv = (Sample14IF)Naming.lookup(f0.getText());
if(srv == null) l1.setText("Connect failed.");• 最後に「持ち主」のところに戻って来て結果を報告
else { l1.setText("OK."); data = srv.get(); }帰還} else { // save
srv.put(data); srv = null; l1.setText("save.");投入}} catch(Exception ex) { l1.setText("!" + ex); }}site});
b1.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent evt) {try {if(data != null)sitesitef1.setText(data.exec(f1.getText()));} catch(Exception ex) { l1.setText("!" + ex); }}• 「はじめてのおつかい」});□ 予め行き先とか決めてあるのなら別に面白くない (最初}public static void main(String[] args)
throws Exception {new Sample14().setVisible(true);}

からそこと通信して情報を取ればいい)• エージェントが「自律的に」動くところがいいので、学習したりして「有用な行動を取る」ように適応し}ていく□ 解説:• このあたりは倉橋先生の専門に近いので興味があれば。

• 部品の配置などはこれまでと同様。
• ボタン b0 の動作では、srv が null であれば f0 に指定された文字列を使って Naming.lookup() を呼び4.2サーバを取得。さらにサーバから Data オブジェクト取得。

□ 1980 年代半ばころに David Gelernter が提唱した通信• srv が null でないならそこに Data オブジェクトをモデル書き込んで srv を null に。

• すべてのノードにまたがった「場」(タプルスペース、TS) がある (ものとする)• ボタン b1 の方はこれまでと同様ただし data に対して実行。
□ 演習:• データはタプルと呼ばれるもので表す。「名前 (値,この例題を動かせ。文字列を加工してもサーバ値, …) 」

側には反映されていなくて、最後に書き戻すと反映され• あるノードで投入したタプルを他のノードで取り出ることを確認すること。
□ 演習:

Linda(タプルスペース)

すことで通信クラス MyData の exec() の中身を変更して自分独自のものにしてみよ。続いて自分のサーバを動かし、動作を確認。OK なら、その rmi:

URI をホワイトボードに書き、他人に使ってみてもらう (また自分も他人のTulple Spaceものを動かしてみる)。

4□ TS に対する操作は次の 3 つ

その他のパラダイム• out 名前 (値, 値, …) --- タプルを TS に投入す

□ 「ソケットによる通信」「RPC」(と RMI) がネットワー

る。投入したタプルは TS 中にずっと残っている

ク通信の 2 大パラダイムだが、それ以外のものも→ 2 つほど紹介7• in 名前 (引数, 引数, …) --- タプルを TS から取 □ 大変スマートでかっこいいので、色々な人が実装したりり出す。引数は「値」または「?変数名」。TS 中にあしている。
るタプルで、名前と値の指定された引数が一致したものを取り出す。そのとき「?変数名」のところには• Sun でも JavaSpaces とかそれをもっと実用的にし変数に対応するタプルの値が取り出される取り出さた Jini などのフレームワークを開発れたタプルは TS に存在しなくなる• しかしどれもいまいち実用にならなかった。いろいろな問題

out count(1) → in count(?x) (x が 1 に)

• rd 名前 (引数, 引数, …) --- in と同様だが、「読み出す」だけなのであてはまったタプルが TS 中から• タプルをどうやって投入箇所から読み出し箇所へ運ぶか?

取り除かれない□ アイデア:• バグありプログラムが山のようにゴミを投入したら?

TS は全ノードにまたがっているので、どこ• 悪意あるプログラムがタプルを盗み見たら?

で投入されたものでもどこででも取り出せる→位置の□ でもまあ、知っておいて損はない面白いおはなし。興味分離□ アイデア:

ある人は「Generative communication in Linda」とTS に入れたものはずっと残っているので受いう論文を読むとよい (ネットで検索すると取れる)

信したい人は後で受け取ることができる→時点の分離□ アイデア:TS から取り出したタプルはなくなるので、「同じものが複数ある」ことを避けられる→整合性
□ 例:

5

時刻サーバ

□ 今回は前回の補足としてステートフルなサーバの実装(スレッドの使用) についてとりあげた。その後、分散オ
ブジェクトモデル、ORB、Java RMI、モビリティ、エー• すべてのノードが正しく時間を知るのは簡単ではない• TS による時刻サーバ

ジェント、Linda などの話題をひととおり取り上げた。

forever doout 時刻 (時刻の値)(次の時刻まで待つ)
in 時刻 (?x)
end

次回から Web アプリの方に進む予定。

• 時刻を知りたい人は「rd 時刻 (?t)」でいつでも時刻を知ることができる
□ 例:

カウンタ

• あちこちのノードでカウントを増やす→最終的に正しいカウント値が得られるようにするのは簡単ではない (RPC とか?)
• TS によるカウンタ
countup:in カウンタ (?x)
x = x + 1
out カウンタ (x)□ 例:

まとめRPC• RPC も TS を使って簡単に実装することができる
サーバ側:
forever doin rpc(?proc, ?arg, ?ret)
手続き proc の処理を arg を用いて実行
out reply(ret, 返値)
end
クライアント側:
id = 自分固有の番号
rpc("手続き名", 引数, id)reply(id, ?ret)8