移动Agent目前已经从理论探索进入到实用阶段,涌现出了一系列较为成熟的开发平台和执行环境。理论上移动Agent可以用任何语言编写(如C/C++、Java、Perl、Tcl和Python等),并可在任何机器上运行,但考虑到移动Agent本身需要对不同的软硬件环境进行支持,所以最好还是选择在一个解释性的、独立于具体语言的平台上开发移动Agent。Java是目前开发移动Agent的一门理想语言,因为经过编译后的Java二进制代码可以在任何具有Java解释器的系统上运行,具有很好的跨平台特性。
移动Agent技术虽然已经研究了很多年,但直到1996年才出现了真正实用的移动Agent系统,目前使用的移动Agent系统大致可以分为三类:一类是基于传统解释语言的,一类是基于Java语言的,另一类则是基于CORBA平台的。下面介绍几个典型的移动Agent系统,它们代表了当今移动Agent技术的基本方向和潮流:
- General Magic公司的Odysses
作为移动Agent系统专用语言的最早尝试,General Magic公司开发的Telescript曾经在过去的几年里被广泛采用。Telescript是一种面向对象的解释性语言,用它编写的移动Agent在通信时可以采用两种方式:若在同一场所运行,Agent间可以相互调用彼此的方法;若在不同场所运行,Agent间需要建立连接,互相传递可计算的移动对象。Telescript在开始出现时还是一个比较成功的移动Agent开发平台,其安全性和健壮性都比较好,执行效率也很高,Telescriipt中的三个基本概念(agent、place和go)对移动Agent做了一个很精辟的阐述:代理自主移动(agent go place)。
随着Java的迅速崛起及其跨平台特性的逐步完善,Telescript的优势慢慢消失,General Magic公司开始改变其策略,开发了一个完全用Java实现的移动Agent系统Odyssey,它能够支持Java RMI,Microsoft DCOM,以及CORBA IIOP。Odyssey继承了Telescript中的许多特性,是目前被广泛使用的一个移动Agent开发平台。
- IBM公司的Aglet
Aglet是最早基于Java的移动Agent开发平台之一,Aglet的名字来源于Agent和Applet,可以简单地将其看成具有Agent行为的Applet对象。Aglet以线程的形式产生于一台机器,需要时可以随时暂停正在执行的工作,并将整个Aglet分派到另一台机器上,然后继续执行尚未完成的任务。从概念上讲,一个Aglet就是一个移动Java对象,它支持自动运行的思想,可以从一个基于Aglet的主机移动到其它支持Aglet的主机上。
Aglet构造了一个简单而全面的移动Agent编程框架,为移动Agent之间的通信提供了动态而有效的交互机制,同时还具备一整套详细而易用的安全机制,这一切使得移动Agent的开发变得相对简单起来。
- Recursion公司的Voyager
Voyager可以看成是一个增强了的对象请求代理(ORB),同其它移动Agent系统相比,Voyager与Java语言的结合更加紧密,既可用于开发移动Agent系统,也可用于创建传统的分布式系统。Voyager是一个纯Java分布式计算平台,可用来迅速生成高性能分布式应用程序,是代表当前技术水平的一个优秀的移动Agent开发平台。
Java是目前开发移动Agent的最佳语言,Recursion公司开发的Voyager是一个高效的移动Agent支持平台,可以用来迅速、便捷地开发基于移动Agent的高性能分布式应用程序,下面辅以具体的实例介绍如何在Voyager平台上利用Java语言来开发移动Agent,所有的例子均在Voyager 4.6和JDK 1.4.1下调试通过,操作系统使用的是Red Hat Linux 9。
安装好Voyager软件包后,可以从命令行方式启动Voyager服务器,接受来自Voyager程序的移动Agent对象和消息。例如,要在端口8000上启动Voyager服务器,可以使用如下命令:
[gary@gary gary]$ voyager 8000 |
如果一切正常,将显示如下信息:
voyager orb professional 4.6, copyright recursion software 1997-2003 |
在命令行方式下终止Voyager服务器,可以按Ctrl+C键。
启动和终止Voyager的另一个方法是调用Voyager提供的API。要在Java程序中启动Voyager,可以调用Voyager.startup()函数,以下是启动Voyager的一些常用方法:
Voyager.startup() // 启动Voyager Voyager.startup(8000) // 在端口8000上启动Voyager Voyager.startup("//gary:8000") // 在主机gary的8000端口启动Voyager |
在Java程序中终止Voyager的方法是调用Voyager.shutdown()函数。
在借助Voyager平台开发移动Agent应用程序时,通常需要遵循以下几个基本步骤:
- 定义远程接口
- 实现远程接口
- 开发使用远程接口的客户程序
- 启动Voyager服务器
- 运行Voyager客户机
假设要开发一个很简单的Voyager服务程序,用于实现两个整数的相加,Voyager客户通过派遣一个Agent到服务端,完成对所携带的两个整数的相加,并将计算结果由移动Agent带回到客户机。下面借助Voyager提供的开发平台来实现这一移动Agent应用程序,从中不难得出Voyager应用程序的基本框架。
步骤一、定义远程接口
Voyager的远程接口与Java的RMI类似,由一系列声明构成。Java语言提供的接口机制使分布式计算变得可能,许多分布式计算模型(包括移动Agent)都利用接口来简化分布式应用程序开发的复杂度。Voyager中的远程对象由移动Agent来表征,正是由于Agent具有与远程对象相同的接口,才使得一个接口类型的变量有可能通过Agent来引用远程对象。
Voyager中的接口名一般以字母I开头,建议在使用Voyager开发移动Agent应用程序时尽量遵循这一规则。在本例中用到的远程接口名为ICalculator,该接口中只定义了一个名为add的方法,其功能是完成两个整数的相加,并返回计算结果,如例1所示:
例1. ICalculator.java /* * @(#) ICalculator.java */ // 远程计算接口 public interface ICalculator { int add(int a, int b); } |
步骤二、实现远程接口
使用Voyager开发移动Agent应用程序的第二步是实现上面定义的远程接口,在本例中就是要编写一个实现了ICalculator接口的类Calculator,如例2所示:
例2. Calculator.java /* * @(#) Calculator.java */ import java.io.*; public class Calculator implements ICalculator, Serializable { public Calculator() { System.out.println("Constuct calculator object."); } // 远程计算接口的实现 public int add(int a, int b) { int result; result = a + b; return result; } } |
除了实现ICalculator接口外,Calculator类还实现了Serializable接口,这是因为Voyager同RMI一样要实现对象的迁移,需要借助Java语言中对象的序列化机制。
步骤三、开发客户机
在定义并实现远程接口之后,接下去要做的就是开发使用远程接口的Voyager客户程序。例3是一个简单的Voyager客户机实现代码:
例3. CalcClient.java /* * @(#) CalcClient.java */ import com.objectspace.voyager.*; public class CalcClient { public static void main(String args[]) { ICalculator calcObj; int a = 1, b = 3; int result; try { // 启动Voyager Voyager.startup(); // 创建远程计算对象 calcObj = (ICalculator) Factory.create("Calculator", "//gary:8000"); // 向远程对象发送消息 result = calcObj.add(a, b); System.out.println(a + " + " + b + " = " + result); // 关闭Voyager Voyager.shutdown(); } catch(Exception exception) { System.err.println( exception ); } } } |
客户端的代码比较简单,值得注意的是如何在Java程序中启动Voyager作为客户机,以及如何使用Factory.create()来创建远程对象。
通过调用Factory类的create()方法,可以在特定位置上创建对象,该方法返回新建对象的远程代理,如果该代理目前还不存在则被自动创建。create()方法有多种形式,分别用来创建本地对象和远程对象。例如,要在本地程序中创建一个名为Calculator类的实例,可以调用如下的代码:
ICalculator calcObj = (ICalculator) Factory.create("Calculator"); |
与之对应,如果想在一个远端Voyager程序中创建一个名为Calculator类的实例,假设该程序运行在主机gary的8000端口上,则可以调用如下的代码:
ICalculator calcObj = (ICalculator) Factory.create("Calculator", "//gary:8000"); |
步骤四、启动Voyager服务器
要对开发的Voyager应用程序进行测试,首先需要在服务器端以命令行方式启动Voyager,根据本例中客户程序的具体要求,监听端口应该设为8000:
[gary@gary gary]$ voyager 8000 |
步骤五、运行Voyager客户机
一切准备就绪,现在可以在客户端执行下面的命令来启动Voyager客户机程序了:
[maggie@maggie maggie]$ java CalcClient |
如果客户机运行正常,Calculator构造函数中的输出信息将显示在Voyager服务器上,而计算结果则将显示在Voyager客户机上。
可移动性是移动Agent区别于其它软件Agent的本质特征,在Voyager中移动对象到新的位置非常简单,只需要遵循下面两个步骤:
- 调用Mobility.of()方法得到可移动对象;
- 调用IMobility接口中的moveTo()方法移动对象;
在调用moveTo()方法移动Voyager对象时,可移动对象将按以下步骤顺序执行任务:
- 对象中正在处理的消息将完成,但新发送给对象的所有消息都将暂停执行;
- 对象及其状态都将使用Java的序列化机制复制到新位置,如果无法完成序列化或者网络出现故障,则抛出异常;
- 对象的新地址在原位置处被存储,存储的新地址不会被垃圾回收器当成对象引用;
- 旧对象被销毁;
- 在旧对象上挂起的消息重新执行;
- 当消息通过代理传送给对象的旧地址时,将向代理抛出一个包含对象新地址的异常。代理捕获该异常后绑定新的对象地址,重新向对象的新地址发送消息;
- 如果对象移动成功,moveTo()方法将返回;如果对象移动失败,则异常将被抛出,同时对象恢复到原状态;
下面以一个简单的问候程序来演示在Voyager中移动对象,该程序将创建一个对象,然后移动到其它机器上输出问候信息。按照Voyager应用程序基本框架的要求,首先定义远程接口IMessage,如例4所示:
例4. IMessage.java /* * @(#) IMessage.java */ // 远程接口 public interface IMessage { void showMessage(String msg); } |
接着实现IMessage接口,如例5所示:
例5. Message.java /* * @(#) Message.java */ import java.io.*; // 远程接口实现 public class Message implements IMessage, Serializable { public void showMessage(String msg) { System.out.println(msg); } } |
同样,Message类除了实现IMessage接口外,还实现了Serializable接口,这是因为对象移动同样需要用到Java的序列化机制。Voyager中的每个可移动对象都必须实现Serializable接口,否则在移动过程中将产生异常。
接下去就能够编写相应的Voyager客户机代码来实现对象的移动了,如例6所示:
例6. Action.java /* * @(#) Action.java */ import com.objectspace.voyager.*; import com.objectspace.voyager.mobility.*; public class Action { public static void main(String argv[]) { try { // 在9000端口启动Voyager Voyager.startup("9000"); IMessage message = (IMessage) Factory.create("Message"); IMobility mob = Mobility.of(message); // 移动Agent到远程计算机 mob.moveTo("//gary:7000"); message.showMessage("Hello World!"); // 关闭Voyager Voyager.shutdown(); } catch (Exception e) { System.err.println(e); } } } |
现在可以对Voyager对象的移动性进行测试了,首先在主机gary的7000端口上启动Voyager服务器:
[gary@gary gary]$ voyager 7000 |
然后在另一台主机上运行Voyager客户机:
[maggie@maggie maggie]$ java Action |
Action类首先在本机的9000端口上启动Voyager,然后调用Factory.create()方法创建名为Message的类实例,接着用Mobility.of()方法将其转变成可移动对象,并调用moveTo()方法将其移动到主机gary的 7000端口上去。
对象成功移动之后,整个运行环境将随之发生变化,输出信息此时自然也就显示在远程主机上。在Voyager中,对象的移动对客户机来讲是透明的,但客户机对移动对象的引用始终都是有效的,无论该对象是位于本地主机、远程主机或者正处于移动过程之中。
使用Voyager可以很容易地开发移动Agent应用程序,Voyaer中的移动代理由一个或多个可移动对象组成,在Voyager中开发移动Agent需要遵循下面两个步骤:
- 调用Agent.of()方法将Voyager对象转变成移动Agent;
- 调用IAgent接口中定义的方法实施移动Agent行为;
在利用Voyager开发移动Agent应用时,经常需要用到IAgent接口中定义的如下方法:
- moveTo() 将Agent移动到指定位置。
- setAutonomous() 设置移动Agent的自主状态,当移动Agent处于自主状态时,即使此时没有任何指向移动Agent的引用存在,垃圾收集程序也不会将其收回。移动Agent默认的自主状态为true,当移动Agent完成任务并希望被收回,可以调用setAutonomous(false)。
- isAutonomous() 返回移动Agent当前的自主状态。
- getHome() 返回移动代理当前所处位置的URL。
下面通过一个简单的例子来演示如何使用Voyager开发移动Agent应用程序,服务器端仍使用两整数相加的程序,同时开发一个加法器移动Agent,它可以自主移动到远程服务器上执行相加操作。首先依然是定义两个整数相加的远程接口ICalculator,如例7所示:
例7. ICalculator.java /* * @(#) ICalculator.java */ // 远程接口 public interface ICalculator { int add(int a, int b); } |
ICalculator接口中只有一个名为add的方法,该接口的具体实现Calculator如例8所示:
例8. Calculator.java /* * @(#) Calculator.java */ import java.io.*; public class Calculator implements ICalculator, Serializable { public Calculator() { System.out.println("Constuct calculator object."); } // 实现远程计算接口 public int add(int a, int b) { int result; result = a + b; return result; } } |
现在需要一个能移动到远程计算机上完成计算过程的移动Agent,先来编写相应的接口IAdder,如例9所示:
例9. IAdder.java /* * @(#) IAdder.java */ // 移动Agent的接口 public interface IAdder { void setOP1(int op); void setOP2(int op); void work(ICalculator calc); void atMachine(ICalculator calc); } |
接口IAdder中定义了名为work和atMachine的两个方法,它们均使用ICalculator接口作为参数,例10是IAdder接口的具体实现:
例10. Adder.java /* * @(#) Adder.java */ import java.io.*; import com.objectspace.voyager.*; import com.objectspace.voyager.agent.*; // 移动Agent接口的实现 public class Adder implements IAdder, Serializable { private int op1; private int op2; private int result; public Adder() { System.out.println("Construct an adder."); } public void finalize() { System.out.println("Finalize adder."); } // 设置第一个操作数 public void setOP1(int op) { op1 = op; } // 设置第二个操作数 public void setOP2(int op) { op2 = op; } // 实现Agent的移动 public void work(ICalculator calc) { try { Agent.of(this).moveTo(calc, "atMachine"); } catch (Exception e) { System.err.println(e); } } // 完成移动Agent的计算 public void atMachine(ICalculator calc) { // 输出Agent来自的主机名 System.out.println("At remote machine, home=" + Agent.of(this).getHome()); System.out.println("Remote compute: " + op1 + " + " + op2 + " = " + compute(calc)); // 设置自主状态 Agent.of(this).setAutonomous(false); } // 计算方法 public int compute(ICalculator calc) { return calc.add(op1, op2); } } |
因为要使用Java的序列化机制来实现对象的移动,因此Adder除了实现IAdder接口外,还实现了Serializable接口。Adder类的核心是work()方法,在与远程主机上的Voyager服务程序建立起通信后,Addre类将移动到远端服务器上。
当移动Agent成功到达Voyager服务器后,atMachine()方法将在远端服务器上被执行,完成用户指派的计算任务。移动Agent在完成任务后调用setAutonomous()方法对自主状态进行设置,从而使得Adder对象可以被垃圾收集程序收回。
要测试移动代理Adder,需要编写一个主程序来生成本地和远程主机上的对象,如例11所示:
例11. MyAgent.java /* * @(#) MyAgent.java */ import java.awt.event.*; import javax.swing.*; import java.awt.*; import com.objectspace.voyager.*; // 移动Agent的用户界面 public class MyAgent extends JFrame implements ActionListener { private ICalculator calc; private IAdder adder; private JButton btnStartup = new JButton("Startup"); private JButton btnCompute = new JButton("Compute"); private JButton btnShutdown = new JButton("Shutdown"); private JTextArea edtStatus = new JTextArea(10, 20); private JTextField edtOP1 = new JTextField(10); private JLabel lblPlus = new JLabel(" + "); private JTextField edtOP2 = new JTextField(10); public MyAgent() { super("Mobile Agent demo"); JPanel pane = new JPanel(); JPanel opane = new JPanel(); GridBagConstraints constraints = new GridBagConstraints(); GridBagLayout layout = new GridBagLayout(); JScrollPane scroll = new JScrollPane(edtStatus, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS); // 按钮的事件响应函数 btnStartup.addActionListener(this); btnCompute.addActionListener(this); btnShutdown.addActionListener(this); // 界面布局 pane.setLayout(layout); // 输入框 opane = new JPanel(); opane.add(edtOP1); opane.add(lblPlus); opane.add(edtOP2); buildConstraints(constraints, 0, 0, 1, 1, 100, 100); pane.add(opane); layout.setConstraints(opane, constraints); // 状态区域 buildConstraints(constraints, 0, 1, 1, 5, 100, 100); edtStatus.setEditable(false); edtStatus.append("Agent Status:\n"); pane.add(scroll); layout.setConstraints(scroll, constraints); // 开始按钮 buildConstraints(constraints, 1, 1, 1, 1, 100, 100); layout.setConstraints(btnStartup, constraints); pane.add(btnStartup); // 计算按钮 buildConstraints(constraints, 1, 2, 1, 1, 100, 100); layout.setConstraints(btnCompute, constraints); btnCompute.setEnabled(false); pane.add(btnCompute); // 关闭按钮 buildConstraints(constraints, 1, 3, 1, 1, 100, 100); layout.setConstraints(btnShutdown, constraints); btnShutdown.setEnabled(false); pane.add(btnShutdown); setContentPane(pane); pack(); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); } public void actionPerformed(ActionEvent evt) { Object source = evt.getSource(); if (source == btnStartup) { try { // 启动Voyager Voyager.startup(); edtStatus.append("\nStartup Voyager OK!\n"); btnStartup.setEnabled(false); btnCompute.setEnabled(true); btnShutdown.setEnabled(true); } catch (Exception e) { edtStatus.append("\nStartup Voyager FAIL!\n"); edtStatus.append(e.toString()); } } else if (source == btnCompute) { try { int op1 = Integer.parseInt(edtOP1.getText()); int op2 = Integer.parseInt(edtOP2.getText()); // 创建远程计算对象 calc = (ICalculator) Factory.create("Calculator", "//gary:7000"); // 创建可移动计算对象 adder = (IAdder)Factory.create("Adder"); adder.setOP1(op1); adder.setOP2(op2); // 实现Agent的移动,完成计算 adder.work(calc); edtStatus.append("\nRemote Compute OK!\n"); } catch (Exception e) { edtStatus.append("\nRemote Compute FAIL!\n"); edtStatus.append(e.toString()); } } else if (source == btnShutdown) { try { 关闭Voyager Voyager.shutdown(); edtStatus.append("\nShutdown Voyager OK!\n"); btnStartup.setEnabled(true); btnCompute.setEnabled(false); btnShutdown.setEnabled(false); } catch (Exception e) { edtStatus.append("\nShutdown Voyager FAIL!\n"); edtStatus.append(e.toString()); } } } public void buildConstraints(GridBagConstraints gbc, int gx, int gy, int gw, int gh, int wx, int wy) { gbc.gridx = gx; gbc.gridy = gy; gbc.gridwidth = gw; gbc.gridheight = gh; gbc.weightx = wx; gbc.weighty = wy; gbc.fill = GridBagConstraints.BOTH; } public static void main(String argv[]) { JFrame frame = new MyAgent(); } } |
MyAgent
类在本地主机上启动Voyager后,在名为gary的主机上运行的Voyager服务器中创建Calculator类,并在本地主机创建Adder类,最后调用Adder类中的work()方法来完成计算任务。
要运行这个例子,首先在主机gary上执行下面的命令启动Voyager服务器:
[gary@gary gary]$ voyager 7000 |
接着在另一台计算上执行如下命令运行Voyager客户机:
[maggie@maggie maggie]$ java MyAgent MyAgent运行时的界面如图6所示: |
图6. MyAgent用户界面
![](https://i-blog.csdnimg.cn/blog_migrate/7c3472a982b8fc838bb88ad9cdcbd519.png)
在运行MyAgent时,首先单击Startup按钮启动Voyager,接着在窗口顶部的输入框中填入参与计算的两个整数后,单击Compute按钮就可以将一个移动Agent派遣出去,如果要关闭Voyager,可以单击Shutdown按钮。
在上面的例子中,Adder类是一个移动Agent,它通过网络移动到主机gary上完成最终的计算任务,计算结果将在远端服务器上显示出来。
近年来,移动Agent技术无论在理论研究还是在实际应用中都取得了很大的进步,人们开始对Agent有了一个较为清晰的认识,并随之产生了一些成熟的移动Agent开发平台,Voyager就是其中的一个典型代表,它的出现极大地推动了移动Agent的应用范围。21世纪是人类社会全面实现信息化的时代,随着计算机网络和人工智能技术的进一步发展,移动Agent必将揭开信息革命的新篇章。
- 在Recursion公司的网站上可以找到与Voyager相关的一些资料,以及最新的Voyager软件包, http://www.recursionsw.com。
- Aglet是IBM公司提供的一个移动Agent开发框架,完全基于Java语言实现,并且开放源代码,在网站 http://www.aglet.org上可以找到Aglet的一些有用资料。