本エージェントシステムを構成する4つの要素を以下に示します.
要素 | 説明 |
---|---|
メッセージ | エージェント間で送受信されるデータ |
エージェント | 互いにメッセージを交換し処理を進めるプログラム |
TAGサーバ | エージェントを動作させるPCで起動しておくプログラム |
Web UI | TAGサーバが提供するユーザインタフェース |
[_p:'inform', answer:'工大太郎', _f:'user']
[_p:'inform', data:[
[onsen:'湯野川温泉', type:'単純泉', city:'川内町'],
[onsen:'湯坂温泉', type:'単純硫化水素泉', city:'大畑町'],
[onsen:'大間温泉', type:'食塩泉', city:'大間町']
], _f:'ODB']
パフォーマティブが「request」で始まるメッセージを「リクエストメッセージ」と呼びます.以下はリクエストメッセージの例です.
[_p:'request', action:'shutdown']
[_p:'request-information', point:'max']
注意:必須知識ではありません.自分で独自のプロトコルを実装する場合には便利です.
メッセージにはMapに記述される内容の他に次の属性があります.これらはStringで表現されます.
UUIDは次のように取得できます.
String uuid = send('Simple', [_p:'test'])
String uuid = reply(msg, [_p:'sorry'])
String uuid = send('Simple', [_p:'test'])
String uuid = reply(msg, [_p:'sorry'])
void loop(Map msg) {
:
String uuid = getUUID(msg) // UUID
String replyID = getReplyID(msg) // replyID
:
}
send(['Agent1', 'Agent2', 'Agent3'], [_p:'inform'])
→Agent1~3に届くメッセージのUUIDは全て同じ
request(['Agent1', 'Agent2', 'Agent3'], [_p:'request-information'])
→Agent1~3に届くメッセージのUUIDは全て異なる
エージェントプログラムはエージェント名.groovyというファイルに記述します.スクリプト言語Groovyを用います.コンパイルは不要です.文字コードはUnicode(UTF-8 BOMなし),改行コードはCR+LFです(CRだけ/LFだけでも大丈夫かも).記述内容を示します.
import TAG
class エージェント名 extends TAG {
void setup() { ... }
void loop(Map msg) { ... }
}
import TAG
の文は不要です.setup()
には初期化処理を書きます.データの初期化などを行います.loop()
にはメッセージが届いたときの処理を書きます.setup()
やloop()
の外側には変数やメソッドの定義を書きます.エージェントは次のように動作します.
setup()
が呼び出されます.ここでエージェントの初期化を行います.loop()
が呼び出されます.loop()内の処理の終了後,再びメッセージが届くまで停止します.エージェントの起動方法は2種類あります.いずれもTAGサーバの起動が必要です.
TAGサーバによる起動 | groovyコマンドによる起動 |
---|---|
TAGサーバ起動時に起動 | TAGサーバ起動後、端末から起動 |
./agent/に保存(スクリプトでもOK) | 任意の場所に保存 |
WebUIによる再起動可 | WebUIからは再起動不可 |
WebUIで編集可 | WebUIでは編集不可 |
stdout/errはWeb UIに接続 | stdout/errは端末に接続 |
※groovyコマンドによる起動は動作確認が不十分です
組み込みメソッドとしてクラスTAGに定義されているメソッドは次の通りです.
メッセージを送信します.送信したメッセージのUUIDを返します.送信後は次の行に進みます(返信待ちしない).
String send(String agent, Map msg) // agentにメッセージmsgを送信する
String send(List agents, Map msg) // 複数のagentsにメッセージmsgを送信する(全てのUUIDは同一)
用例:
String uuid = send('ODB_aomori', [_p:'inform', point:50])
String uuid = send(['ODB_aomori', 'ODB_iwate'], [_p:'inform', point:50])
send()
ではなくreply()
で返信します.request()
を使う方が無難です.send()
でも送信することができますが,相手は必ず返信してきますのでwaitAny()
などの対応が必要となるからです.メッセージを返信します.送信したメッセージのUUIDを返します.送信後は次の行に進みます(返信待ちしない).送信したメッセージのreplyIDがメッセージmのUUIDとなるところがsend()
との違いです(send()
で送信した場合はreplyIDはnull).
String reply(Map m, Map msg) // mへの返信メッセージmsgを送信する (msgのreplyIDは,mのUUID)
用例:
String uuid = reply(m, [_p:'inform', point:50])
send()
ではなくreply()
で返信します.request()
を使う方が無難です.reply()
でも送信することができますが,相手は必ず返信してきますのでwaitAny()
などの対応が必要となるからです.loop()
を抜けると例外が上がります.reply()
あるいはrequest()
で返信する必要があります.reply()
で返信しても問題はないので,全ての返信はreply()
で行うと吉です 😃リクエストメッセージを送信します.送信したリクエストメッセージに対する返信メッセージ(あるいは返信メッセージを要素とするList)を返します.返信が届くまで待ちます(次の行に進まない).
Map request(String agent, Map msg) // agentへのリクエストメッセージmsgを送信し,返信が届くまで待つ
List request(List agents, Map msg) // 複数のagentsへのリクエストメッセージを送信し,全ての返信が届くまで待つ※
Map request(Map m, Map msg) // mに対する返信としてリクエストメッセージを送信し,返信が届くまで待つ
用例:
Map m = request('ODB_aomori', [_p:'request-information', point:'max'])
Map m = request(['ODB_aomori', 'ODB_iwate'], [_p:'request-information', point:'max'])
Map m = request(msg, [_p:'request_information', point:max])
request()
で送信するメッセージのパフォーマティブは"request"で始める必要があります.それ以外で始まる場合,例外が上がります.loop()
を抜けると例外が上がります.reply()
あるいはrequest()
で返信する必要があります.複数のエージェントを同時に起動するときに,起動用スクリプト内で使います.
※以下は未実装の仕様
以下は通常のプログラムでは使わなくても済む特殊なメソッドです.TAG.loop()を使わずに自分でTAG.run()メソッドを記述する場合や,Javaプログラム内部にメッセージ待ちループを作る場合,あるいは独自のプロトコルを作る場合に有用です.
メッセージサーバ内にある全てのメッセージを取得し,ローカルバッファに取り込みます.新たに取り込んだメッセージの数を返します.
int fetch()
指定されたUUIDのメッセージの到着を待ちます(次の行に進まない).到着したメッセージを返します.
TagMessage waitAny(List uuids) // uuidsに含まれるreplyIDのメッセージの到着を待つ.最初に到着したものを返す.
List waitAll(List uuids) // uuidsに含まれる全てのreplyIDのメッセージの到着を待つ.到着した全てのものを返す.
waitAll()
で待っているときにリクエストメッセージが到着した場合,例外が上がります.TAGをJavaプログラムに組み込むことで,そのJavaプログラムがメッセージ送受信可能になります.※実装中
エージェントを動作させるPCで起動しておくプログラムです.メッセージサーバ機能とエージェントサーバ機能の2つの機能を兼任します.メッセージサーバ機能により名前空間が規定されます.設定ファイルtag.ini
で動作設定します.
tag.ini
内のmsgServerPort
とmsgServerHost
で指定します.初回起動時に以下のようなtag.ini
が生成されます.これらはエージェントが動作するのに必要なサーバ情報です.サーバ情報はエージェント起動時にエージェントにセットされます.
#property file for TAG
#Sat Jan 20 23:40:22 JST 2018
msgServerPort=8080
msgServerHost=localhost
containerUUID=1b4dcf13-11ac-4c34-8c7f-09a65b801488
containerName=noname1
エントリ | 意味 |
---|---|
msgServerPort | TAGサーバ配下のエージェントが使うメッセージサーバのポート番号 |
msgServerHost | TAGサーバ配下のエージェントが使うメッセージサーバのホスト名 |
containerUUID | TAGサーバのUUID(自動的に生成されます) |
containerName | TAGサーバの名前(自動的に生成されます) |
異なるPC上のエージェントが通信するためには,メッセージサーバ機能の共用が必要です.tag.ini
内のmsgServerHost
とmsgServerPort
を書き換えることで,他のTAGサーバから提供されるメッセージサーバ機能を使うことができます.
TAGサーバは自前のメッセージサーバ機能を使います.TAGサーバの初回起動時に生成されるtag.ini
で設定される運用方法です.
tag.iniの項目 | PC |
---|---|
msgServerHost | localhost |
PC1で動作しているTAGサーバのメッセージサーバ機能を共有する例です.PC2のtag.ini
にPC1
を記述することで,エージェント3,4 はエージェント1,2と通信することができます.PC2からPC1にネットワーク接続できる場合に適用できます.3台以上で共有することもできます.
tag.iniの項目 | PC1 | PC2 |
---|---|---|
msgServerHost | localhost | PC1 |
PC1とPC2が直接つながらない場合,双方からアクセス可能な場所にあるPC0をメッセージサーバとして共有することができます.図ではPC0のエージェントサーバ機能は使っていませんが,使うこともできます.
tag.iniの項目 | PC0 | PC1 | PC2 |
---|---|---|---|
msgServerHost | localhost | PC0 | PC0 |
TAGサーバは,同一PC上で動くエージェントを管理するエージェントサーバ機能と,メッセージをバッファリングするメッセージサーバ機能として動作します.この2つの機能により名前空間が規定されます.
TAGサーバは,自分自身,あるいは他のTAGサーバをメッセージサーバとして利用します.
エージェント名 | TAGサーバ名 | TAGサーバのUUID | メッセージサーバ名 |
---|---|---|---|
TAGサーバで ユニーク |
メッセージサーバで ユニーク |
UUIDなので ユニーク |
IPアドレス:ポート |
ユーザが決定 | ユーザが決定 | 初回起動時に ランダムに決定 |
IPアドレス:ポート |
クラス名,あるいは コンストラクタの 引数として記述 |
tag.ini 内のcontainerName に記述 |
自動生成されるので ユーザは記述しない |
tag.ini 内のmsgServerHost msgServerPort に記述 |
WebUIは必ずContainerを通す(ajaxクロスドメイン回避)
エージェントも。localhost:8080とのみfetch/sendする
エージェント間