当前位置:文档之家› JADE指南 学习教程 很好的资料

JADE指南 学习教程 很好的资料

JADE 程序员指南

最近更新:2005年11月22日。JADE3.3

作者:Fabio Bellifemine, Giovanni Caire, Tiziana Trucco (TILAB,原来的CSELT)

Giovanni Rimassa (帕尔马大学)

目录

1 简介 4

2 JADE 特点 6

3 使用JADE创建多AGENT系统7

3.1 Agent平台7

3.1.1 FIPA-Agent-Management本体8

3.1.1.1 本体的基本概念8

3.1.2 简化的API来访问DF和AMS服务9

3.1.2.1 DF服务9

3.1.2.2 AMS服务9

3.2 Agent类10

3.2.1 Agent生命周期10

3.2.1.1 启动agent执行11

3.2.1.2 停止agent执行11

3.2.2 交互agent的通信11

3.2.2.1访问私有消息队列. 12

3.2.3 具有图形用户界面(GUI)的Agent 12

3.2.3.1 Java GUI并行模式12

3.2.3.2 对应一个GUI事件执行一条ACL消息交换13

3.2.3.3 当一条ACL消息被接收时修改GUI 14

3.2.3.4 支持构建在JADE里能够使用agents的GUI 15

3.2.4 具有参数的agent和如何启动agent 18

3.3 Agent通信语言(ACL) 消息18

3.3.1 支持响应一个消息18

3.3.2 支持Java序列化以及发送字节序列18

3.3.3 ACL编码器19

3.3.4 消息模板类19

3.4 agent任务. 实现Agent行为20

3.4.1 Behaviour类22

3.4.2 SimpleBehaviour类23

3.4.3 OneShotBehaviour类23

3.4.4 CyclicBehaviour类23

3.4.5 CompositeBehaviour类23

3.4.6 SequentialBehaviour类23

3.4.7 ParallelBehaviour类24

3.4.8 FSMBehaviour类24

3.4.9 SenderBehaviour类

3.4.10 ReceiverBehaviour类

3.4.11 WakerBehaviour类24

3.4.12 范例24

3.4.13 在一个专用的Java线程里执行行为27 3.5 交互协议28

3.5.1 AchieveRE(Achieve R ational E ffect) 29

3.5.1.1 AchieveREInitiator 30

3.5.1.2 SimpleAchieveREInitiator 30

3.5.1.3 AchieveREResponder31

3.5.1.4 SimpleAchiveREResponder31

3.5.1.5 使用这两个通用的类来实现一个具体的FIPA协议的范例31

3.5.2 FIPA-Contract-Net 32

3.5.2.1 ContractNetInitiator 32

3.5.2.2 ContractNetResponder 33

3.5.3 FIPA-Propose 33

3.5.3.1 ProposeInitiator 33

3.5.3.2 ProposeResponder 34

3.5.4 FIPA- Subscribe 34

3.5.

4.1 Subscription Initiator 35

3.5.

4.2 Subscription Responder 36

3.5.

4.2.1 Subscription 36

3.5.

4.2.2 Subscription管理器36

3.5.5 交互协议的一般状态36

3.5.5.1 HandlerSelector类36

3.5.5.2 MsgReceiver类37 3.6 应用程序定义的内容语言和本体37 3.7 支持Agent可移动性37

3.7.1 支持agent可移动性的JADE API 37

3.7.2 JADE可移动性本体. 38

3.7.3 为agent移动性访问AMS 39

3.8 从外部Java应用程序使用JADE 41

4 一个AGENT系统范例43

5 缩写语列表44

1 简介

这篇程序员指南是管理员指南和HTML文档的补充,HTML文档在jade/doc目录下。如果这篇指南HTML文档有冲突,以HTML文档为准,因为HTML文档更新的更快。

JADE(Java Agent Development Framework)是一套软件开发框架,目的在于开发多agent系统以及遵循FIPA标准的智能agent应用程序。它包括两个主要部分:一个遵循FIPA的agent平台和一个开发Java agent的软件包。JADE是完全用Java 编写的,要使用这个框架,agent程序员应该遵守这篇程序员指南描述的规范,用Java编写他/她的agents。

这篇指南假设读者已经熟悉FIPA标准1,至少熟悉Agent管理规范(FIPA no.23),Agent通信语言以及ACL消息结构(FIPA no.61)。

JADE是用Java语言编写的,是由各种Java包组成的,这些软件包为应用程序员提供了现成的函数和抽象接口,独立的应用程序。选择Java作为编程语言是因为它具有很多有吸引力的特点,尤其是适合在分布式异类环境下的面向对象编程;其中的一些特点是对象序列化,API映射和远程方法调用(RMI)。

JADE主要由如下包组成。

jade.core实现了系统的核心。它包括必须由应用程序员继承的Agent类;除此之外,Behaviour类是包含在jade.core.behaviours子包里。行为实现了一个agent的任务或者意图。他们是逻辑上的活动单元,能够以各种方式组成来完成复杂执行模式,并且可以并行执行。应用程序员通过编写行为和使它们相互连接的agent执行路径来定义agent操作。

https://www.doczj.com/doc/be14293619.html,ng.acl子包可以根据FIPA标准规范来处理Agent通信语言。

jade.content包含了一些类来支持用户定义的本体和内容语言。有一个单独的指南来描述如何使用JADE来支持消息内容。特别地,https://www.doczj.com/doc/be14293619.html,ng.sl 包含了SL编码2,既有解码器也有编码器。

jade.domain包含了由FIPA标准定义的描述Agent管理实体的所有Java类,尤其是AMS和DF agents,它们提供生命周期,白页服务,黄页服务。

jade.domain.FIPAAgentManagement子包包含了FIPA-Agent-Management本体和所有描述它的概念的类。jade.domain.JADEAgentManagement包含了为Agent管理的JADE扩展(例如为了监测消息,控制agents的生命周期等等),包括本体和所有描述它的概念的类。jade.domain.introspection包含了用于在JADE工具(例如监测器和检查器)与JADE内核之间交互域的概念。jade.domain.mobility包含了全部用于通信有关移动性的概念。

jade.gui包含了一套通用的创建图形用户界面(GUIs)以显示和编辑AgentID,Agent描述,ACL消息(ACLMessages)的类。

jade.mtp包含了一个每个消息传输协议都应该实现的Java接口,这样就可以容易地与JADE框架,这些协议的实现集成。

jade.proto包含了一些用来构造标准交互协议(即fipa-request, fipa-query, fipa-contract-net, fipa-subscribe以及其他一些由FIPA定义的协议)的类,以及一些帮1详见https://www.doczj.com/doc/be14293619.html,/

2参考FIPA no.8文档有关SL内容语言的规范

助应用程序员创建他们自己协议的类。

FIPA包包含了为基于IIOP消息传输由FIPA定义的IDL模块。

最后,jade.wrapper包提供了JADE高层函数的封装,这些函数允许把JADE 作为一个库使用,外部的Java应用程序可以启动JADE agents和agent容器。(见3.8节)。

JADE和某些简化平台管理和应用程序开发工具绑定在一起。每个工具都包含在jade.tools里一个独立的子包。通常,可使用如下工具:

●远程管理Agent(Remote Management Agent,简写RMA),作为一个平台管

理和控制的图形控制台。RMA的首个实例能够以命令行选项(“-gui”)开始,但是可以启动超过一个GUI。JADE通过在多RMAs中简单地多点传送事件给它们来维护一致性。而且,RMA控制台可以启动其他JADE工具。

●虚拟Agent(Dummy Agent)是一个监测和调试工具,由图形用户界面和内

在JADE agent组成。使用GUI,就可以构成ACL消息,然后把它们发送给其他agents;还可以显示全部已发送或者已接收的ACL消息列表,这些消息以时间戳信息结束,可以允许agent对话记录和排演。

●监测器是一个可以在ACL消息传递时截取它们并使用类似UML顺序图的符

号图形化地显示它们的agent。它在调试你的agent时非常有用,可以观察它

们如何交换ACL消息。

●检查器是一个允许监测一个agent生命周期,它所交换的ACL消息以及执行时

行为的agent。

●DF GUI是一个完全的图形用户界面,被JADE的默认目录服务器(DF,

Directory Facilitator)以及用户可能需要的每个其他的DF所使用。以这样一种方式,用户可能创建一个黄页的域或者子域的复杂网络。GUI允许以一种简单和直观的方式控制一个DF的知识库,把一个DF与其他DF联合起来,以及远程控制(注册/注销/修改/搜索)上层DF和下层DF(实现了域和子域的

网络)的知识库。

●日志管理Agent是一个可以设置运行日志信息的agent,例如在日志层次,针

对JADE和应用程序具体指明的使用Java日志的类。

●套接字代理Agent(SocketProxyAgent)是一个简单的agent,做为一个在JADE

平台和平常的TCP/IP连接之间的双向网关。遍历了JADE所拥有的传输服务的ACL信息被转化成简单的ASCII字符串,通过套接字连接被发送出去。

Viceversa,ACL消息经过TCP/IP连接,通过隧道技术传给JADE平台。这个agent非常有用,例如,要处理网络防火墙或者向平台交互提供在网络浏览器中的Java applets。

JADE TM是由CSELT3注册的商标。

3从2001年3月起,公司的名字改为TILab

2 JADE特点

如下列出的是JADE提供给agent程序员的特点:

●分布式agent平台。agent平台可以分为几个主机(可以通过RMI把他们连接起

来4)。由于只有一个Java应用程序,因此在每台主机上只运行一个Java虚拟

机。Agents作为Java线程被执行,存活在为agent执行提供运行时支持的Agent

容器里。

●从一个远程主机使用图形用户界面来管理多个agent和agent容器。

●开发基于JADE的多agents应用程序的调试工具。

●内部平台agent灵活性,包括状态和agent代码(有需要时)的转换。

●支持通过行为模型多agent活动并行同时执行。JADE以一种非抢占式的模式

安排agent行为。

●遵循FIPA标准的Agent平台,它包括Agent管理系统(AMS,Agent Management

System),DF(Directory Facilitator)和Agent通信信道(ACC,Agent

Communication Channel)。这三个组件都是在agent平台启动时自动加载的。

●很多遵循FIPA标准的DF都可以在运行时启动,以实现多域应用程序,其中

一个域是一个逻辑的agent集合,这些agent服务通过一个普通的服务器广播

出去。每个DF继承了一个GUI以及所有由FIPA定义的标准功能(即注册,注

销,修改和搜索agent描述的能力;与一个DF网络联合的能力)。

●在相同的agent平台里有效地传输ACL消息。实际上,消息传输时被当作Java

对象,而不是字符串,这样做是为了避免排列与反排列的过程。当跨越平台

边界时,消息自动地转化成适应FIPA标准的语法,编码和传输协议,或者从

这些语法,编码和传输协议中转化成消息。这些转化对那些只需处理Java对

象的agent实现者来说是透明的。

●具有FIPA交互协议的库可供使用。

●在AMS帮助下,可以实现agent的自动注册和注销。

●符合FIPA标准的命名服务:在启动时agent可以从平台中得到它们的全球唯

一标识符(GUID,Globally Unique Identifier)。

●支持应用程序定义的内容语言和本体。

●内部处理接口,可以使外部应用程序启动独立的agent

4当使用附加软件LEAP时,这个条件不再成立

3 使用JADE创建多Agent系统

这一章描述了一些支持开发多agent系统的JADE类。JADE保证了句法符合FIPA规范,可能的话还有语义也符合FIPA规范。

3.1 Agent平台

由FIPA定义的标准agent平台模型,如下图所示:

图 1 一个FIPA标准Agent平台的参考结构

Agent管理系统(AMS)是一个针对访问和使用Agent平台进行监督控制的agent。在一个平台上只有一个AMS存在。AMS提供了白页和生命周期服务,维护着一个agent ID的目录(AID,directory of agent identifiers)和agent状态。每个agent必须向AMS注册以获取一个有效的AID。

目录服务器(DF)是一个在平台中提供默认黄页服务的agent。

消息传输系统,也称作Agent通信信道(ACC,Agent Communication Channel)是一个控制了平台中全部消息交换(包括发送至远程平台以及从远程平台发送到本机)的软件组件。

JADE完全遵守了上图的参考结构,当一个JADE平台启动时,AMS和DF立即被创建,ACC模块被设置为允许消息通信。Agent平台可以分为几个主机。由于只有一个Java应用程序,因此在每台主机上只运行一个Java虚拟机(JVM)。每个JVM是一个为agent执行提供一个完全运行时环境的agent的基本容器,并允许多个agent同时在一台主机上执行。主容器,或称为前端(front-end),是AMS和DF驻留以及由JADE 内部使用的RMI注册被创建的容器。其他的agent容器连接到主容器,并为任何JADE agent集合的执行提供一个完全的运行时环境。

图 2 分布在多个容器的JADE Agent平台

根据FIPA规范,DF和AMS agent使用FIPA-SL0内容语言,

fipa-agent-management本体,fipa-request交互协议进行通信。JADE为所有这些组件提供了对应的实现:

●SL-0内容语言由https://www.doczj.com/doc/be14293619.html,ng.sl.SLCodec类实现。通过使用

getContentManager().registerLanguage(SLCodec(0))方法,使用这种语言的自动能力可以被添加到任意agent。

●本体的概念(除了Agent ID是由jade.core.AID实现的之外)是由在

jade.domain.FIPAAgentManagement包里的类实现的。

FIPAManagementOntology类定义了所有本体常量符号的词汇。通过使

getContentManager().registerOntology(FIPAManagementOnt

ology.getInstance())方法,使用这种本体的自动能力可以被添加到

任意agent。

●最后,fipa-request交互协议作为可供使用的行为由jade.proto包实

现。

3.1.1 FIPA-Agent-Management本体

每个实现了fipa-agent-management本体概念的类都是一个简单的属性集合,根据基于描述FIPA fipa-agent-management本体概念的模型的框架,具有公共的方法来读取和编写它们。使用如下的规则。对于每个命名为attrName,类型为arrtType类的属性,有两种情况:

1)属性类型是一个单一的值;这种情况,可以使用attrType.getAttrName()和void setAttrName(attrType a)的方法来读取和设置它,其中每次调用setAttrName()都会覆盖上一次属性的值。

2)属性类型是一个集合或者是一串值的序列;这种情况,有一个void

addAttrName(attrType a)方法插入一个新值,一个void

clearAllAttrName()方法删除全部的值(列表变为空)。读取动作是通过一

个getAllAttrName()的方法来完成的,这个方法返回一个Iterator对象,可

以让程序员遍历这个List并为其中的元素选择恰当的类型。

参考HTML文档中对这些类和接口的完全列表。

3.1.1.1 基本本体概念

jade.content.onto.basic包包含了一系列类,这些类一般是每个本体的

一部分,例如Action,TrueProposition,Result,…,BasicOntology可

以被添加至任何用户定义的本体,在3.6节会有说明。

注意到Action类应该被用来描述动作。它有一些方法来设置/获取动作的AID (即执行这个动作的agent)和动作本身(例如,注册/注销/修改)。

3.1.2 简化的API来访问DF和AMS服务

至今所描述的JADE特点允许在FIPA系统的agent与用户定义的agent之间完

全的交互操作,这种操作是通过简单地发送和接受标准定义的消息来完成的。

然而,因为那些交互操作已经被完全地标准化,而且因为它们是通用的,因此

如下的类可以用一个简化的接口成功地完成这个工作。

Agent类实现了两个方法来获取默认DF的AID和平台的AMS的AID:getDefaultDF()和

getAMS()。

3.1.2.1 DF服务

jade.domain.DFService实现了一些静态方法来与一个标准的FIPA DF服

务(即一个黄页agent)进行通信。

它包括了一些请求注册,注销,修改和在一个DF中搜索动作的方法。每个方法

都有一个需要全部必需参数的版本,同时还有一个子集,缺省参数会以默认值进行

赋值。

注意到,这些方法会阻塞每个agent的活动,直到这个动作被成功地执行或者

一个jade.domain.FIPAException异常被抛出(例如,DF接收了一条failure

的消息),就是说,直到对话结束。

有些情况,以一种非阻塞的方式执行这些任务会更加方便。在这种情况下,一

个jade.proto.AchieveREInitiator或者

jade.proto.SubscriptionInitiator(见3.4.12)应该被用来与createRequestMessage(),createSubscriptionMessage(),decodeDone(),decodeResult()和decodeNotification()这些方法连接,它们促进了发往DF或者从DF接收消息的解码和准备。如下的代码段说明了一个agent向默认DF预定的情况。

DFAgentDescription template = // fill the template

AID df = getDefaultDF();

ACLMessage subs = DFService.createSubscriptionMessage(this, df,

template, null)

Behaviour b = new SubscriptionInitiator(this, subs) { protected void handleInform(ACLMessage inform) { try {

DFAgentDescription[] dfds =

DFService.decodeNotification(inform.getContent() );

// do something

}

catch (FIPAException fe) {

fe.printStackTrace();

}

}

};

addBehaviour(b);

3.1.2.2 AMS服务

这个类是DFService的双重类,访问由标准FIPA提供的服务。AMS agent和它的接口完全对应DF服务的接口。

注意到JADE在调用setup()方法之前,在takeDown()方法返回值之后自动地分别以默认的AMS调用注册和注销方法;因此作为一个通常的程序员无需去调用它们。

然而,在某种情况下,一个程序员可能需要调用它的方法。举出一些例子:当一个agent准备注册到一个远程agent平台的AMS时候,或者当一个agent准备修改它的描述,把一个私有地址添加至它的地址集合,…

3.2 Agent类

Agent类为用户定义的agent描述了一个通用的基类。因此,从程序员的角度来看,一个JADE agent就是一个用户定义的继承了Agent类的Java类的简单实例。这意味着继承了实现在agent平台上基本的交互(注册,配置,远程管理…)以及可以被调用来实现自定义的agent行为(如,发送/接受消息,使用标准的交互协议,注册到多个域上,…)。

一个agent的计算模型是多任务的,其中任务(或者行为)是同时执行的。每个由agent提供的函数/服务都应作为一个或者多个行为来实现(参考3.4节,行为的实现)。一个调度程序自动管理着行为的调用,这个调度程序是在Agent基类的内部,对程序员来说是透明的。

3.2.1 Agent的生命周期

图 3 FIPA定义的Agent生命周期

根据FIPA规范里所说的Agent平台生命周期,一个JADE agent可以处于上图其中一个状态;图3描述的细节如下:

●初始状态(INITIATED):生成了一个Agent对象,但是还没有把自己注册

到AMS上,既没有名字,也没有地址,不能与其他agent通信。

●活动状态(ACTIVE):Agent对象注册到AMS上,有一个正式的名字和地

址,具有全部JADE的特点。

●悬挂状态(SUSPENDED):当前Agent对象停止运行。内部的线程处于挂

起状态,没有执行agent行为。

●等待状态(WAITING):Agent对象处于阻塞状态,在等待一些条件。内

部的线程在Java监视器上处于休眠状态,只要满足一定条件(尤其是接收到

消息)就会停止休眠。

●删除状态(DELETED):Agent已经明确是废弃了。内部线程终止了Agent

执行,Agent不再注册到AMS上了。

●转变状态(TRANSIT):当一个移动的agent变化到一个新位置时,它会进

入这个状态。系统会缓存那些要被发送到新位置的消息。

Agent类提供了一些公共方法实现在各种状态之间的转变;这些方法从FIPA规范里面的有限状态自动机的转换中获得一个相对应的名字。例如,doWait()方法把agent从活动状态放到等待状态,doSuspend()方法把agent从活动状态或者等待状态放到悬挂状态,…参考HTML文档中关于Agent类,有一个完整的doXXX()方法列表。

注意到仅当一个agent处于活动状态时,才允许执行它的行为(即任务)。注意,假如任意一个行为调用了doWait()方法,那么不仅仅是这个调用了doWait()方法的行为,而是整个agent以及它的活动都处于阻塞状态。对比block()方法,它是

Behaviour类的一部分,作用是暂停一个agent的行为(详情见3.4节,行为的使用)。

3.2.1.1 启动agent执行

JADE框架根据如下步骤控制一个新agent的产生:执行了agent的构造器,赋予agent一个ID(详情见HTML文档中jade.core.AID类),注册到AMS上,处于活动状态,最终执行setup()方法。根据FIPA规范,一个agent ID具有如下属性:

●一个全球唯一名字。JADE默认地把本地名字串联起来以组成这个名字——

即在命令行上提供agent名字——加上‘@’符号,加上本地agent平台的ID

——即<主机名>‘:’‘/’‘JADE’;在平台的

名字已经在命令行中指明的情况下,agent名字就由本地名字加上‘@’符

号再加上指明的平台名字构成。

●一个地址集合。每个agent都继承了它的本地agent平台的传输地址;

●一个解决器集合,即agent注册的白页服务。

因此setup()方法是任何应用程序定义的agent活动的起始点。程序员必须实现setup()方

法才可以初始化agent。当执行了setup()方法,agent就已经注册到AMS上,它的Agent平台状态是活动状态。程序员应该使用如下的初始化步骤:

●(可选的)有需要的时候可以修改注册到AMS上的数据(见3.1.2);

●(可选的)设置agent的描述以及它所提供的服务,有需要的时候把agent注

册到一个或者多个域上,即DFs(见3.1.2);

●(如果有需要)使用addBehaviour()方法把任务添加至已经就绪的任务队

列中。一旦setup()方法结束,就调用这些行为;

setup()方法应该至少把一个行为添加至agent中。在setup()方法结束的地方,JADE 自动地执行在就绪任务队列中的第一个行为,然后通过使用一个循环的非抢占式的调度程序转到队列中的其他行为。Agent类的addBehaviour(Behaviour)和removeBehaviour(Behaviour)方法可以用来管理任务队列。

3.2.1.2 停止agent执行

任何行为都可以调用Agent.doDelete()方法来停止agent执行。

当agent准备转到删除状态,即它将要被销毁,就要执行Agent.takeDown()方法。程序员可以通过重载takeDown()方法来实现任何必须的清除行动。当调用这个方法时,agent仍然是注册到AMS上的,因此可以发送消息到其他的agent,但是只要在takeDown()方法完成之后,agent就会被注销,它的线程也会被销毁。这个方法的目的是完成应用程序指定的清除动作,例如注销DF的agent。

3.2.2 交互agent的通信

Agent类也为需要交互agent的通信提供了一套方法。根据FIPA标准,agent 是通过异步消息传输来通信的,其中ACLMessage类的对象是交换的载体。见3.3节ACLMessage类的描述。也可以使用一些由FIPA定义的交互协议,这些协议作为可供使用的能够为agent动作而被调用的行为;它们是jade.proto包的一部分。

Agent.send()方法可以发送一个ACLMessage。receiver栏的值存储了接

收agent的ID。这个方法的调用与agent所处的地点完全无关,即无论它是本地的还是远程的,它是一个会选择最合适的地址和传输机制的平台。

3.2.2.1 访问私有消息队列

平台把所有由agent接收的消息放到agent的私有队列。默认的(从JADE2.5以后)队列大小是没有限制的,然而,在有限资源的情况下,可以通过setQueueSize()方法来改变这个默认值。

已经实现了多个访问模式来从私有队列中获得消息:

●可以以一种阻塞(使用blockingReceive()方法)或者非阻塞(使用

receive()方法)的方式访问消息队列。使用阻塞方式时要十分小心,

因为它会导致全部agent活动,特别是agent的全部行为暂停。当请求消

息不在队列中的时候,非阻塞方式立即返回空(null);

●可以扩充这两种方法的模式匹配能力,其中要传递一个描述请求

ACLMessage模式的参数。见3.3.4节,描述了MessageTemplate类;

●阻塞访问有一个超时参数。它是一个长整形参数,描述了为等待请求消息,

agent活动保持阻塞状态的最大毫秒数。如果在消息到达之前,超过了这

个超时参数,方法就会返回空(null);

●ReceiverBehaviour和SenderBehaviour这两个行为可以用来实现需

要接收或者发送消息的agent任务。

3.2.3 具有图形用户界面(GUI)的Agent

一个由多Agent系统构成的应用程序仍需要与用户进行交互。所以,通常至少需要为应用程序里的一些agent提供一个GUI。尽管这样可以阻止agent的自主性和普通用户接口的反应之间的不匹配,但是还会产生一些问题。当使用JADE时,JADE agent的每个agent线程的并行模式必须与Swing并行模式一起工作。

3.2.3.1 Java GUI并行模式

在一个Java虚拟机中,有一个单一的线程,称为事件发报机线程(Event Dispatcher Thread),它的任务是持续地从系统事件队列(是

java.awt.EventQueue类的实例)中选择事件对象(即java.awt.AWTEvent 类的实例)。然后事件发报机线程在其他事件中调用各种注册到源事件的监听器。重要的是全部的事件监听器都是以一个单一的控制线程(事件发报机)来执行的;这就遵守一个著名的规则,一个事件监听器的执行时间应该足够短(少于0.1秒),以保证接口可以做出响应。Swing的一个非常重要的特点是用模型/视图系统来管理GUI更新。当一个Swing控件具有一些状态(例如一个JCheckBox有一个选择标志,一个JList包含一些元素等等),这些状态都会保留在一个模型对象中(在DefaultButtonModel,ListModel等等这些类中)。这些模型对象提供了一些命令来修改这些状态(例如,选择或者不选择复选框,向list列表增加或者删除元素等等),内置通知机制的Swing会更新GUI的外观来反映状态的改变。所以一个JCheckBox 对象会在两种情况下改变它的外观:

●接收到一个从用户发出的事件(例如一个MouseClick事件)。

程序的其他部分修改了联合了JCheckBox的模型对象。

正如在Java指南中描述的那样(JFC/Swing追踪,线程和Swing部分),Swing框

架并不是安全线程的,所以任何更新GUI元素的代码都必须以事件发报机线程来执行;因此修改一个模型对象会触发一个GUI的更新,它遵循上述的原则,模型对象只能被事件发报机线程操作。Swing框架提供了一个简单但通用的方法来把一些用户定义的代码传递给事件发报机线程:SwingUtilities类有两个静态方法可以接收一个Runnable对象,用一个RunnableEvent封装它,然后把它放进系统事件队列。invokeLater()方法把一个Runnable对象放进系统事件队列,然后立即返回(就像一个异步交互线程调用),然而invokeAndWait()方法是把一个Runnable对象放进系统事件队列然后阻塞,直到事件发报机线程已经处理完RunnableEvent(就好像一个同步交互线程调用)。此外,invokeAndWait()方法可以捕获Runnable对象抛出的异常。

3.2.3.2 对应一个GUI事件执行一条ACL消息交换

当一个agent给出了一个GUI,常常由于一个用户的动作(例如,用户点击了一个按钮)要求agent发送一条消息。按钮的ActionListener将会在事件发报机里运行,但是Agent.send()方法应该在agent线程里被调用。所以:在事件监听器里,向完成必需通信的agent增加一个新的行为。如果执行的通信只是一个简单的消息发送操作,就可以使用SenderBehaviour类,事件处理器就会包含以下一行:

myAgent.addBehaviour(new SenderBehaviour(msgToSend));

如果通信操作是接收一条消息,就可以用同样的方式使用ReceiverBehaviour类:myAgent.addBehaviour(new ReceiverBehaviour(msgToRecv));

更普遍的是,当用户作用于GUI时,启动一些复杂的对话(例如,一个完整的遵循交互协议的交互操作)。解决的办法是再一次向agent添加一个新的行为;这个行为继承了为交互协议或者将会成为一个自定义的复杂行为而预先定义的JADE行为。如下的代码段是从JADE RMA管理agent中抽出来的。当用户想要创建一个新的agent 时,他或她操作在RMA GUI(通过菜单栏,工具栏或者快捷菜单)上,执行一个StartNewAgentAction对象,这就会调用rma类的newAgent()方法。这个方法的实现如下:

public void newAgent(String agentName, String className, Object

arg[], String containerName) { // Create a suitable content object for the ACL message ...

// Set the :ontology slot of the message

requestMsg.setOntology(https://www.doczj.com/doc/be14293619.html,);

// Fill the message content with a List l, containing the content object

fillContent(requestMsg, l);

addBehaviour(new AMSClientBehaviour("CreateAgent", requestMsg));

}

AMSClientBehaviour类是rma类的一个私有内部类,它继承了FipaRequestInitiatorBehaviour类,在AMS agent里做为fipa-request 的交互协议。这样,addBehaviour()的调用和具体行为类的添加就完全被封装到rma类里面了。各种RMA GUI的类(主要是动作类)保留着一个到RMA agent

的参考模型,使用它来调用方法,如newAgent()。注意到,例如newAgent()这样的方法并不是真正属于agent,因为它们没有以任何方式访问agent的状态。所以,设计它们的目的是为了从外部(一个不同的执行线程)调用:下面,这些方法称之为外部方法。

一般地,一个外部的软件组件维护一个agent的直接对象参考模型,这并不是一个好事。因为这个组件可以略过异步消息传送层,直接调用agent的任何公共方法(不止是外部的方法),把一个自主的agent转变成一个服务者对象,服务于它的调用者。一个较好的解决办法是把所有的外部方法聚集到一个接口,这个接口由agent类实现。然后,一个接口的对象参考模型就会被传给GUI,这样只有从事件处理器中才可以调用外部方法。下面的伪代码说明了这种方法:

interface RMAExternal {

void newAgent(String agentName, String className, Object

arg[], String containerName);

void suspendAgent(AID name);

void resumeAgent(AID name);

void killAgent(AID name);

void killContainer(String name);

void shutDownPlatform();

}

class MainWindow extends JFrame {

private RMAExternal myRMA;

public MainWindow(RMAExternal anRMA) {

myRMA = anRMA;

}

// ...

}

class rma extends Agent implements RMAExternal { private MainWindow myGUI;

protected void setup() {

myGUI = new MainWindow(this);//Parameter 'this' typed as

//RMAExternal

// ...

}

}

上述表明,GUI只能调用RMA agent的外部方法。

3.2.3.3 当一条ACL消息被接收时修改GUI

一个agent可以从其他agent中通过ACL消息接收信息:FIPA通信动作inform就是为了这个目的。如果一个agent有一个GUI,它也许需要经常修改GUI的外观来把新

信息传达给它的用户。根据模型/视图模型,新信息是用来修改一些模型对象,Swing 会自动更新GUI。Agent.receive()读取消息的操作在agent线程里执行,但是任何针对Swing模型对象的修改都必须从事件发报机线程里完成。所以:在agent行为里,要把全部对GUI模型对象的访问封装到一个Runnable对象里,使用SwingUtilities.invokeXXX()方法把Runnable对象提交给事件发报机线程。

例如,当在JADE平台上创建一个新agent时,AMS发送inform消息到所有活动的RMA agent;每个agent都要更新Agent树(AgentTree),增加一个描述新agent的节点。rma类有一个AMSListener(内部私有)类的行为,这个类不断地从AMS中接收inform消息,以及把这些消息发送到合适的内部事件处理器(这是一个在ACL消息上的基本的简单分布式事件系统)。对应于创建agent(agent-born)事件的处理器的代码如下:

AID agent = ab.getAgent();

myGUI.addAgent(container, agent);

}

MainWindow类的addAgent()方法如下:

public void addAgent(final String containerName, final AID agentID) {

// Add an agent to the specified container

Runnable addIt = new Runnable() {

public void run() {

String agentName = agentID.getName();

AgentTree.Node node =

tree.treeAgent.createNewNode(agentName, 1);

Iterator add = agentID.getAllAddresses();

String agentAddresses = "";

while(add.hasNext())

agentAddresses = agentAddresses + add.next() + " ";

tree.treeAgent.addAgentNode((AgentTree.AgentNode)no de,

containerName, agentName, agentAddresses,

"FIPAAGENT");

}

};

SwingUtilities.invokeLater(addIt);

}

从以上代码可以看到,所有到agent树的访问都被封装到一个Runnable对象里,通过使用SwingUtilities.invokeLater()方法在事件发报机线程里执行提交的Runnable对象。整个Runnable对象的创建和提交过程都包含在MainWindow类的addAgent()方法里,所以rma agent没有直接处理Swing的调用(甚至不需要导入Swing相关的类)。

如果我们把整个MainWindow类看作是一个活动的对象,这个对象的线程是事件发报机线程,那么addAgent()方法明显是一个外部方法,这种方法准确地反映了上述使用的技术。然而,既然GUI没有被看成是一个自主的软件组件,那么是否选择使用外部方法只与软件结构有关,没有特别的概念上的意义。

3.2.3.4 支持构建在JADE里能够使用agents的GUI

因为使用一个具有GUI的agent是非常普遍的,为了这个特殊的目的,JADE包含了jade.gui.GuiAgent类。这个类是jade.core.Agent类的的简单继承:在启动时(即当setup()方法被执行时),它会举一个ad-hoc行为的例子,这个行为管理一系列可以被其他线程接收的jade.gui.GuiEvent事件对象。当然,这个行为对那些只需要实现与每个事件相关的应用程序指定的代码的程序员来说是不可见的。细节上,必须完成如下操作。

一个想要向agent通知一个事件的线程(尤其是GUI)应该创建一个jade.gui.GuiEvent类型的对象,然后把它作为参数传递给jade.gui.GuiAgent对象的postGuiEvent()方法调用。在postGuiEvent ()方法被调用后,agent会唤醒它全部活动的行为,尤其是上述提及的会使agent 线程执行onGuiEvent()方法的行为。注意到一个GuiEvent对象有两个强制属性(即事件源和一个识别事件类型的整数)和一个可选的参数列表5,这些参数可以被添加到事件对象。

因此,一个想要从其他线程(尤其是GUI)接收事件的agent应该定义它准备接收事件的类型,然后实现onGuiEvent()方法。一般地说,这个方法有一个大的switch语句,为每一种事件类型设有一个case情况。分布式JADE的mobile 例子是这个特性的一个很好示例。

为了更深入地阐述先前的概念,下面给出了有关MobileAgent例子的一些引起注意的代码段。

MobileAgent.java文件

public class MobileAgent extends GuiAgent {

……

// These constants are used by the Gui to post Events to the Agent

public static final int EXIT = 1000;

public static final int MOVE_EVENT = 1001;

public static final int STOP_EVENT = 1002;

public static final int CONTINUE_EVENT = 1003;

public static final int REFRESH_EVENT = 1004;

public static final int CLONE_EVENT = 1005;

……

5每个参数的类型都必须继承https://www.doczj.com/doc/be14293619.html,ng.Object;因此初始对象(例如int)首先要被转换成合适的对象类型(例如https://www.doczj.com/doc/be14293619.html,ng.Integer)

public void setup() {

……

// creates and shows the GUI

gui = new MobileAgentGui(this);

gui.setVisible(true);

……

}

……

// AGENT OPERATIONS FOLLOWING GUI EVENTS

protected void onGuiEvent(GuiEvent ev)

{

switch(ev.getType())

{

case EXIT:

gui.dispose();

gui = null;

doDelete();

break;

case MOVE_EVENT:

Iterator moveParameters = ev.getAllParameter();

nextSite =(Location)moveParameters.next();

doMove(nextSite);

break;

case CLONE_EVENT:

Iterator cloneParameters = ev.getAllParameter();

nextSite =(Location)cloneParameters.next();

doClone(nextSite,"clone"+cnt+"of"+getName());

break;

case STOP_EVENT:

stopCounter();

break;

case CONTINUE_EVENT:

continueCounter();

break;

case REFRESH_EVENT:

addBehaviour(new

GetAvailableLocationsBehaviour(this));

break;

}

}

}

MobileAgentGui.java文件

package examples.mobile;

public class MobileAgentGui extends JFrame implements ActionListener

{

private MobileAgent myAgent;

……

// Constructor

MobileAgentGui(MobileAgent a)

{

super();

myAgent = a;

……

JButton pauseButton = new JButton("STOP COUNTER");

pauseButton.addActionListener(this);

JButton continueButton = new JButton("CONTINUE COUNTER");

continueButton.addActionListener(this);

JButton b = new JButton(REFRESHLABEL);

b.addActionListener(this);

b = new JButton(MOVELABEL);

b.addActionListener(this);

b = new JButton(CLONELABEL);

b.addActionListener(this);

b = new JButton(EXITLABEL);

b.addActionListener(this);

……

}

……

public void actionPerformed(ActionEvent e)

{

String command = e.getActionCommand();

// MOVE

if (command.equalsIgnoreCase(MOVELABEL)) {

Location dest;

int sel = availableSiteList.getSelectedRow();

if (sel >= 0)

dest = availableSiteListModel.getElementAt(sel);

else

dest = availableSiteListModel.getElementAt(0);

GuiEvent ev = new

GuiEvent((Object)this,myAgent.MOVE_EVENT);

ev.addParameter(dest);

myAgent.postGuiEvent(ev);

}

// CLONE

else if (command.equalsIgnoreCase(CLONELABEL)) { Location dest;

int sel = availableSiteList.getSelectedRow();

if (sel >= 0)

dest = availableSiteListModel.getElementAt(sel);

else

dest = availableSiteListModel.getElementAt(0);

GuiEvent ev = new

GuiEvent((Object)this,myAgent.CLONE_EVENT);

ev.addParameter(dest);

myAgent.postGuiEvent(ev);

}

// EXIT

else if (command.equalsIgnoreCase(EXITLABEL)) {

GuiEvent ev = new GuiEvent(null,myAgent.EXIT);

myAgent.postGuiEvent(ev);

}

else if (command.equalsIgnoreCase(PAUSELABEL)) { GuiEvent ev = new GuiEvent(null,myAgent.STOP_EVENT);

myAgent.postGuiEvent(ev);

}

else if (command.equalsIgnoreCase(CONTINUELABEL)){ GuiEvent ev = new

GuiEvent(null,myAgent.CONTINUE_EVENT);

myAgent.postGuiEvent(ev);

}

else if (command.equalsIgnoreCase(REFRESHLABEL)) { GuiEvent ev = new

GuiEvent(null,myAgent.REFRESH_EVENT);

myAgent.postGuiEvent(ev);

}

}

……

}

3.2.4 具有参数的agent和如何启动agent

可以把一些参数传给agent,可以通过调用Object[] getArguments()方法检索到它们。注意到这些参数只是暂时的,它们既没有与agent一起移动,也没有和agent一起被复制。

有三种方法启动agent:

●可以使用在管理员指南中描述的语法在命令行上指定agent。在圆括号里

的参数可以传给每个agent。这是最普遍的,也是最符合agent自主性理

论需要的形式。

●管理员可以使用RMA(Remote Monitoring Agent)GUI来启动一个

agent,这在管理员指南中有说明。在圆括号里的参数可以传给每个

agent。

相关主题
文本预览
相关文档 最新文档