以下如果未特殊声明,都在JADE管理器中运行,然后再Eclipse控制台中查看!

JADE行为类

在前面的例子中,Agent所作的工作都定义在了setup方法中,实际上它具有的行为和执行的动作都应该定义在Behavious类中,我们可以对Behavior类生成实例,然后将任务或者动作代码放在对Behavious类中的action方法中,action方法是必须要有的。Behavious类还有很多子类,分别对应着不同类型的Behaviour,包括SimpleBehaviour,SequencesBehaviour, ParallelBehavior, CyclicBehavior等。

一个Agent的行为表示它能够执行的任务,通过继承jade.core.behaviours.Behaviour来实现。然后在Agent类中通过addBehaviour()方法将行为加入进来。当一个Agent启动(通过setup()方法后,行为可以在任何时间加入进来。

要定义Behaviour必须实现其action()方法,它定义了Agent的执行时的实际动作,而done()方法指名了一个行为是否已执行完毕,是否要从行为池中删除。

一个Agent可以并发执行多个behaviour。每个Agent线程启动后执行的过程如下:

SimpleBehaviour简单行为

下面的例子中我们不在setup()中打印信息,而是把它放在一个简单行为中:

在jadetest包里面新建一个java类命名为HelloWorldBehaviour,输入下面代码。配置中把变量改为-gui sb:jadetest.SimpleBehaviour,运行

packagejadetest;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月14日 下午5:20:11*/

importjade.core.Agent;importjade.core.Location;importjade.core.behaviours.SimpleBehaviour;public class HelloWorldBehaviours extendsAgent {public voidsetup() {

SimpleBehaviour hello_behaviour= new SimpleBehaviour(this) {boolean finished = false;//覆盖 Behaviour 类的action这一抽象方法

public voidaction() {

System.out.println("HelloWorldBehaviour run: Hello World!");

System.out.println("-----About Me:-----");

System.out.println("My local name is:" +getLocalName());

System.out.println("My globally unique name is:" +getName());

System.out.println("-----About Here:-----");

Location l=here();

System.out.println("I am running in a location called:" +l.getName());

System.out.println("Which is identified uniquely as:" +l.getID());

System.out.println("And is contactable at:" +l.getAddress());

System.out.println("Using the protocol:" +l.getProtocol());

finished= true;

}//done()在父类中也是一个抽象方法

public booleandone() {returnfinished;

}

};

addBehaviour(hello_behaviour);

}//setup()

}

然后使用智能体管理器添加智能体并运行:

输出结果为:

HelloWorldBehaviour run: Hello World!

-----About Me:-----

My local name is:hwb

My globally unique name is:hwb@172.17.98.217:1099/JADE

-----About Here:-----

I am running in a location called:Main-Container

Which is identified uniquely as:Main-Container@172.17.98.217

And is contactable at:172.17.98.217

Using the protocol:jicp

这个程序相较于前面的例子多了action, done两个函数,它们分别执行自己的操作。HelloWorldBehaviours类加载时定义一个简单行为,这个简单行为执行的操作由action,done来实现。然后,再通过加载语句(addBehaviour(hello_behaviour))执行这个简单行为

简单行为和循环行为(CyclicBehaviour)组合的例子

一个Agent中可以加入各种Behaviour构成CompositeBehaviour。

在Eclipse的jade工程中编写下列程序,过程如前所描述。

packagejadetest;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月14日 下午8:46:29*/

importjade.core.Agent;importjade.core.behaviours.Behaviour;importjade.core.behaviours.CyclicBehaviour;importjade.core.behaviours.OneShotBehaviour;public class SimpleAgent extendsAgent {private class FourStepBehaviour extendsBehaviour {//SimpleBehaviour hello_behaviour = new SimpleBehaviour(this){//这两条语句有很大不同吗?上面的定义一个新的类,下面的修改了标准行为的执行内容

private int step = 1;public voidaction() {switch(step) {case 1:

System.out.println("Operation 1");break;case 2:

System.out.println("Operation 2. Adding one-shot behaviour");

myAgent.addBehaviour(newOneShotBehaviour(myAgent) {//增加了一个新的动作,即快照

public voidaction() {

System.out.println("One-shot");

}

});//myAgent.addbehaviour

break;case 3:

System.out.println("Operation 3");break;case 4:

System.out.println("Operation 4");break;

}//switch

step++;

}//action

public booleandone() {return step == 5; //判断语句

}public intonEnd() {

myAgent.doDelete();

System.out.println("Finished!");return super.onEnd();

}

}//class FourStepBehaviour

/**Creates a new instance of SimpleAgent*/

protected voidsetup() {

System.out.println("Agent " + getLocalName() + " started.");

addBehaviour(new CyclicBehaviour(this) { //增加了一个循环行为

public voidaction() {

System.out.println("Cycling");

}

});// //增加的循环行为//Add the generic behaviour

addBehaviour(newFourStepBehaviour());

}// //setup()

}

输出结果:

Agent sa started.

Cycling

Operation 1

Cycling

Operation 2. Adding one-shot behaviour

Cycling

Operation 3

One-shot

Cycling

Operation 4

Finished!

注意每个Agent内部都有一个ParallelBehaviour,我们如果加入多个behaviour到Agent中,他们会并行执行。behaviours加入到队列的顺序就是他们执行的次序。最后,behaviours可以动态的加入到Agent以及CompositeBehaviour。

总结:每个主体的执行都是从setup() 开始顺序执行的。主体可以执行自定义的行为,如上例中的hello_behaviour ,FourStepBehaviour,也可以执行标准化的行为如OneShotBehaviour。Agent通讯:ACL(Agent communication language)

JADE的Agent之间进行通信使用的acl语言遵循fipa acl规范。一个acl消息通常包含这些参数:sender:消息的发送者,用Agent标志AID表示; receivers,接受Agent消息的Agent可以是多个;Reply-to,应收到回应的接受者;Performative:标志发送消息的目的,即发送者想要通过发送消息干什么,通常有这样一些常值:REQUEST, INFORM, ACCEPT_PROPOSAL, REJECT_PROPOSAL, PROPOSE;Content,消息的内容;内容语言,比如内容的编码格式;ontology,双方都能够理解的消息内容的概念说明和语义描述。

简单实例

发送者:

packagejadetest;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月14日 下午9:00:25*/

importjade.core.AID;importjade.core.Agent;importjade.core.behaviours.Behaviour;importjade.lang.acl.ACLMessage;public class SimpleSender extendsAgent {protected voidsetup() {

addBehaviour(new Behaviour(this) {private boolean finished = false;public voidaction() {

System.out.println(getLocalName()+ ": about to inform bob hello");//we sleep here to give bob a chance to start.

doWait(5000);

AID r= new AID();//作用是什么?表示消息的发送者

r.setLocalName("bob");

ACLMessage msg= newACLMessage(ACLMessage.INFORM);//set performative

msg.setSender(getAID());

msg.addReceiver(r);

msg.setContent("Hello_BOB");

send(msg);

System.out.println(getLocalName()+ ": Send hello to bob");

System.out.println("the content is:" + msg.getContent()); //finished = true;

doWait(5000);

doDelete();

}//action

public booleandone() {returnfinished;

}

});//addbehavior

}//setup

}//Agent

这段代码的主要执行过程为:构建一个AID,以此来指出该消息的目的Agent。这里我们指定目的为一个本地的Agent,名字为bob。建立一个ACL消息标志其performative为INFORM。设定Sender为自身,指定接收者为bob。然后发送消息内容。打印相关信息。

接收者:他的名字必须为bob

packagejadetest;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月14日 下午9:01:36*/

importjade.core.Agent;importjade.core.behaviours.SimpleBehaviour;importjade.lang.acl.ACLMessage;public class SimpleReceiver extendsAgent {class DoSimpleReceiveBehaviour extendsSimpleBehaviour {private boolean finished = false;publicDoSimpleReceiveBehaviour(Agent Agent) {super(Agent);

}public voidaction() {

ACLMessage msg=receive();if (msg != null) {

System.out.println(getLocalName()+ ": received the following message : ");

System.out.println(msg.toString());

finished= true;

myAgent.doDelete();

}else{

System.out.println(getLocalName()+ ":No message received, Blocking the behaviour till one is");

block();

}

}//action

public booleandone() {returnfinished;

}

}//DoSimpleReceiveBehaviour

protected voidsetup() {

DoSimpleReceiveBehaviour behaviour= new DoSimpleReceiveBehaviour(this);

addBehaviour(behaviour);

}

}//Agent

接收者的代码流程为:添加一个简单行为,这一行为检查现在是否有受到消息,若没有,则执行block()方法组织目前的behaviour执行,直到有新的消息到达。

复杂实例

FIPA定义了一组交互协议,包括FIPA-request, FIPA-query, FIPA-request-when, FIPA-contract-net, FIPA-Iterater-net, FIPA-Auction-English, FIPA-Auction-Dutch.其中:

REQUEST-INFORM:A请求B做一些工作,B可以同意或拒绝。如果B同意,则会去完成并告诉A该工作已经完成。等等。

Query:A想知道一些事情,B可以同意或不同意,并将B的回应告诉A。

Propose:在给定一些precondition的条件下,提出一个proposal去执行某些动作。

在Eclipse中创建文件夹ips:其代码文件有两个,分别为

packageips;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月14日 下午9:03:05*/

importjade.core.AID;importjade.core.Agent;importjade.domain.FIPANames.InteractionProtocol;importjade.lang.acl.ACLMessage;importjade.proto.SimpleAchieveREInitiator;public class SimpleRequestInitiator extendsAgent {static class MarriageProposer extendsSimpleAchieveREInitiator {protectedMarriageProposer(Agent Agent, ACLMessage msg) {super(Agent, msg);

}protected voidhandleAgree(ACLMessage msg) {

System.out.println(myAgent.getLocalName()+ ": 吼吼! " +msg.getSender().getLocalName()+ " 已经同意嫁给我了, I'm so excited!");

}protected voidhandleRefuse(ACLMessage msg) {

System.out.println(myAgent.getLocalName()+ ": Oh no! "

+ msg.getSender().getLocalName() + " 拒绝了我, i feel sad.");

}protected voidhandleInform(ACLMessage msg) {

System.out.println(myAgent.getLocalName()+ ":" +msg.getSender().getLocalName()+ " has informed me of the status of my request." + " They said : "

+msg.getContent());

}protected voidhandleNotUnderstood(ACLMessage msg) {

System.out.println(myAgent.getLocalName()+ ":" +msg.getSender().getLocalName()+ " has indicated that they didn't understand.");

}protected voidhandleOutOfSequence(ACLMessage msg) {

System.out.println(myAgent.getLocalName()+ ":" +msg.getSender().getLocalName()+ "has sent me a message which I wasn't" + " expecting in this conversation");

}

}protected voidsetup() {

System.out.println(getLocalName()+ ": about to propose marriage to bob ");

doWait(5000); //wait for bob to be started.

ACLMessage msg = newACLMessage(ACLMessage.REQUEST);

AID to= newAID();

to.setLocalName("bob");

msg.setSender(getAID());

msg.addReceiver(to);

msg.setContent("Marry Me!");

msg.setProtocol(InteractionProtocol.FIPA_REQUEST);

addBehaviour(new MarriageProposer(this, msg));

}

}

还有:

packageips;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月14日 下午9:03:57*/

importjade.core.AID;importjade.core.Agent;importjade.domain.FIPANames.InteractionProtocol;importjade.lang.acl.ACLMessage;importjade.proto.SimpleAchieveREResponder;public class SimpleRequestResponder extendsAgent {static class MarriageResponder extendsSimpleAchieveREResponder {publicMarriageResponder(Agent Agent) {super(Agent, createMessageTemplate(InteractionProtocol.FIPA_REQUEST));

}protectedACLMessage prepareResponse(ACLMessage msg) {

ACLMessage response=msg.createReply();if (msg.getContent() != null && msg.getContent().equals("Marry Me!")) {

System.out.println(myAgent.getLocalName()+ ":" +msg.getSender().getLocalName()+ " has asked me to marry him!");

AID sender;

sender=msg.getSender();if (sender.getLocalName().equals("baz")) {

response.setPerformative(ACLMessage.AGREE);

System.out.println(myAgent.getLocalName()+ ":I'm going to agree.");

}else{

response.setPerformative(ACLMessage.REFUSE);

System.out.println(myAgent.getLocalName()+ ":I'm going to turn him down.");

}

}else{

response.setPerformative(ACLMessage.NOT_UNDERSTOOD);

System.out.println(myAgent.getLocalName()+ ":I didn't understand what "

+ msg.getSender().getLocalName() + " just said to me.");

}returnresponse;

}protectedACLMessage prepareResultNotification(ACLMessage inmsg, ACLMessage outmsg) {//what they have asked is now complete (or if it failed)

ACLMessage msg =inmsg.createReply();

msg.setPerformative(ACLMessage.INFORM);

msg.setContent("I Do!");returnmsg;

}

}protected voidsetup() {

System.out.println(getLocalName()+ ": I wonder if anybody wants to marry me?");

addBehaviour(new MarriageResponder(this));

}//}

按照以前记载,先打开GUI管理器,主类依然为jade.Boot,参数为-gui,GUI管理打开之后先建立一个名为bob的Agent,对应的类为ips.SimpleRequestResponder,然后再建立一个名为baz的Agent,对应的类为ips.SimpleRequestInitiator,记住顺序不能变,以下是输出结果:

bob: I wonder if anybody wants to marry me?

baz: about to propose marriage to bob

bob:baz has asked me to marry him!

bob:I'm going to agree.

baz: 吼吼! bob 已经同意嫁给我了, I'm so excited!

baz:bob has informed me of the status of my request. They said : I Do!

上例中,应用了SimpleAchieveREInitiator和SimpleAchieveREResponder两个基类,适用于两个Agent之间的交互。可以看出发起者对于不同的回应有不同的执行动作。

技巧:从AMS中获取所有Agent的AID。

packageips;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月16日 下午1:52:33*/

importjade.core.Agent;importjade.domain.AMSService;importjade.domain.FIPAAgentManagement.AMSAgentDescription;importjade.domain.FIPAAgentManagement.SearchConstraints;importjade.lang.acl.ACLMessage;public class AMSDump extendsAgent {protected voidsetup() {

AMSAgentDescription[] Agents= null;try{

SearchConstraints c= newSearchConstraints();

c.setMaxResults(new Long(-1));

Agents= AMSService.search(this, newAMSAgentDescription(), c);

}catch(Exception e) {

System.out.println("Problem searching AMS: " +e);

e.printStackTrace();

}

ACLMessage msg= newACLMessage(ACLMessage.INFORM);

msg.setContent("Ping");for (int i = 0; i < Agents.length; i++) {if(Agents[i].getName().equals(getAID())) {//如果不是自己则加入到接收者数组中

continue;

}

msg.addReceiver(Agents[i].getName());

}

}

}

ACL高级特性之消息模板:

MessageTemplate class 利用MessageTemplate可以针对ACLMessage的每个属性设置模式,以达到过滤消息的目的。为了可以构建更复杂的匹配规则,多个模式也可以进行and,or,not运算。最有用的一些规则或方法包括:通信行为匹配,发送者匹配,会话ID匹配。

比如MatchPerformative( performative ) 是通信行为的匹配。

这里 performative 可能是:

ACLMessage.INFORM

ACLMessage.PROPOSE

ACLMessage.AGREE

还有发送者匹配MatchSender( AID ),会话匹配MatchConversationID( String ),通信协议匹配MatchProtocol( String ) ,本体匹配MatchOntology( String)。

比如:MessageTemplate mt = MessageTemplate.and(

MessageTemplate.MatchPerformative( ACLMessage.INFORM ),

MessageTemplate.MatchSender( new AID( "a1",                                                         AID.ISLOCALNAME))) ;

相当于建立了一个模板,表示消息规则为INFORM行为并且发送者为“a1”。

接收过程如下:ACLMessage msg = receive( mt );

if (msg != null) { ... handle message }

block();

示例:

packagejadePrime.acl;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月14日 下午9:07:11*/

importjade.core.AID;importjade.core.Agent;importjade.core.behaviours.CyclicBehaviour;importjade.lang.acl.ACLMessage;importjade.lang.acl.MessageTemplate;public class Template extendsAgent {

MessageTemplate mt1=MessageTemplate.and(MessageTemplate.MatchPerformative(ACLMessage.INFORM),

MessageTemplate.MatchSender(new AID("a1", AID.ISLOCALNAME)));protected voidsetup() {//Send messages to "a1" and "a2"

ACLMessage msg= newACLMessage(ACLMessage.INFORM);

msg.setContent("Ping");for (int i = 1; i <= 2; i++)

msg.addReceiver(new AID("a" +i, AID.ISLOCALNAME));

send(msg);//Set-up Behaviour 1

addBehaviour(new CyclicBehaviour(this) {public voidaction() {

System.out.print("Behaviour ONE: ");

ACLMessage msg=receive(mt1);if (msg != null)

System.out.println("gets " + msg.getPerformative() + " from "

+ msg.getSender().getLocalName() + "=" +msg.getContent());elseSystem.out.println("gets NULL");

block();

}

});//Set-up Behaviour 2

addBehaviour(new CyclicBehaviour(this) {public voidaction() {

System.out.print("Behaviour TWO: ");

ACLMessage msg=receive();if (msg != null)

System.out.println("gets " + msg.getPerformative() + " from "

+ msg.getSender().getLocalName() + "=" +msg.getContent());elseSystem.out.println("gets NULL");

block();

}

});

}

}

packagejadePrime.acl;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月14日 下午9:10:07*/

importjade.core.Agent;importjade.core.behaviours.CyclicBehaviour;importjade.lang.acl.ACLMessage;public class Responder extendsAgent {protected voidsetup() {

addBehaviour(new CyclicBehaviour(this) {public voidaction() {

ACLMessage msg=receive();if (msg != null) {

ACLMessage reply=msg.createReply();

reply.setPerformative(ACLMessage.INFORM);

reply.setContent(" Gossip.....");

send(reply);

reply.setPerformative(ACLMessage.PROPOSE);

reply.setContent(" Really sexy stuff... cheap! ");

send(reply);

}

block();

}

});

}

}

输出结果:

Behaviour ONE: gets NULL

Behaviour TWO: gets 6 from ams=( (action ( agent-identifier :name template@192.168.23.1:1099/JADE  :addresses (sequence http://jacksile:7778/acc )) (ACLMessage) ) (MTS-error ( agent-identifier :name a1@192.168.23.1:1099/JADE ) (internal-error "Agent not found: getContainerID() failed to find agent a1@192.168.23.1:1099/JADE")) )

Behaviour ONE: gets NULL

Base64:发送消息为java序列化对象

在JADE中,支持Agents之间通信的消息内容使用序列化的java对象,对本地应用,特别是所有Agent都用java实现的情况下也是很有用的.

看实例:一个ObjectSender负责发送一个Person对象,ObjectReceiver负责接收后打印出接收到的内容。

源文件:Person.java

packageexamples.Base64;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午12:34:47*/

importjava.io.Serializable;importjava.util.Date;public class Person implementsSerializable {

String name;

String surname;

Date birthdate;intage;

Person(String n, String s, Date d,inta) {

name=n;

surname=s;

birthdate=d;

age=a;

}publicString toString() {return (name + " " + surname + " born on " + birthdate.toString() + " age = " +age);

}

}

文件2:ObjectSender.java

packageexamples.Base64;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午12:35:47*/

importjade.core.AID;importjade.core.Agent;importjade.lang.acl.ACLMessage;importjava.io.IOException;importjava.util.Date;public class ObjectSender extendsAgent {protected voidsetup() {try{

ACLMessage msg= newACLMessage(ACLMessage.INFORM);

msg.setContent("Ping");

AID personR= new AID("personR", AID.ISLOCALNAME);

msg.addReceiver(personR);

Person p= new Person("Name1", "Surname1", new Date(), 1);

msg.setContentObject(p);

msg.setLanguage("JavaSerialization");

send(msg);

System.out.println(getLocalName()+ " sent 1st msg " +msg);

send(msg);

}catch(IOException e) {

e.printStackTrace();

}

doDelete();//kill itself because it has completed its task.

}

}

文件三:ObjectReceiver.java

packageexamples.Base64;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午12:37:17*/

importjade.core.Agent;importjade.core.behaviours.CyclicBehaviour;importjade.lang.acl.ACLMessage;importjade.lang.acl.UnreadableException;public class ObjectReceiver extendsAgent {protected voidsetup() {

addBehaviour(new CyclicBehaviour(this) {public voidaction() {try{

ACLMessage msg=blockingReceive();

System.out.println(getLocalName()+ " rx msg" +msg);if ("JavaSerialization".equals(msg.getLanguage())) {

Person p=(Person) msg.getContentObject();

System.out.println(getLocalName()+ " 获取Java对象: ");

System.out.println(p.getClass().getName());

System.out.println(p.toString());

}

}catch(UnreadableException e3) {

System.err.println(getLocalName()+ " catched exception " +e3.getMessage());

}

block();

}

});

}

}

外部应用程序调用Agent

JADE2.3以后的版本都提供了in-process接口来实现外部应用程序对Agent的调用。

我们可以通过jade.core.Runtime.instance()来获得jade运行时的一个单独得实例。有两种方法可以用来创建一个jade主容器和一个jade远程容器。主要调用过程如下:

先看简单的例子:

packageinprocess;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午12:48:37*/

importjade.core.Agent;importjade.core.behaviours.SimpleBehaviour;/***

*@authorAdministrator*/

public class CustomAgent extendsAgent {public voidsetup() {

SimpleBehaviour helloBehaviour= new SimpleBehaviour(this) {boolean finished = false;public voidaction() {

System.out.println("Hello World Behaviour run: 你好,世界!");

System.out.println("-----我是:-----");

System.out.println("我的本地名称是:" +getLocalName());

System.out.println("我全局唯一的标志名称为:" +getName());

finished= true;

}public booleandone() {returnfinished;

}

};

addBehaviour(helloBehaviour);

}

}

以上是要调用的Agent类

packageinprocess;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午12:49:43*/

importjade.core.Profile;importjade.core.ProfileImpl;importjade.core.Runtime;importjade.wrapper.AgentContainer;importjade.wrapper.AgentController;public classInprocessTest {public static voidmain(String args[]) {try{

Runtime rt=Runtime.instance();

rt.setCloseVM(true);

Profile pMain= new ProfileImpl(null, 8888, null);

System.out.println("Launching a whole in-process platform..." +pMain);

AgentContainer mc=rt.createMainContainer(pMain);//set now the default Profile to start a container

ProfileImpl pContainer = new ProfileImpl(null, 8888, null);

System.out.println("Launching the Agent container ..." +pContainer);

AgentController custom= mc.createNewAgent("custom", "inprocess.CustomAgent", null);

custom.start();

}catch(Exception e) {

e.printStackTrace();

}

}

}//以上是调用者,在Eclipse中调试通过。

再看一个例子,也是main函数中动态创建Agent的例子。可以通过先创建另一个容器,然后再这个容器中创建Aent。比如:

public static voidmain(String[] args) {//TODO code application logic here

Runtime rt =Runtime.instance();

rt.setCloseVM(true);

ContainerController cc= rt.createAgentContainer(new ProfileImpl(false));

AgentController pingAgent= null;try{//create Agent and start it

(cc.createNewAgent("hello", "jadeclient.HelloWorldAgent", new Object[0])).start();

}catch(Exception e){}

}

可以写如下测试程序,这里用Eclipse平台,一个HelloWorldAgent,他将被动态创建并向主容器中的server发送一个消息:

packagejadeclient;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午12:50:50*/

importjade.core.AID;importjade.core.Agent;importjade.core.behaviours.SimpleBehaviour;importjade.lang.acl.ACLMessage;public class HelloWorldAgent extendsAgent {public voidsetup() {

addBehaviour(new SimpleBehaviour(this) {boolean finished = false;

AID server= new AID("server", AID.ISLOCALNAME);public voidaction() {

System.out.println("我的本地名称是:" +getLocalName());

ACLMessage msg= newACLMessage(ACLMessage.INFORM);

msg.setContent("消息内容");

msg.addReceiver(server);

send(msg);

System.out.println("已经往主容器中的server发送信息#");

block(1000);

finished= true;

}public booleandone() {returnfinished;

}

});

};

}

而server角色是收到消息后打印出来。

/** ReceiverAgent.java

* To change this template, choose Tools | Template Manager

* and open the template in the editor.*/

packagejadeclient;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午12:51:57*/

importjade.core.Agent;importjade.core.behaviours.CyclicBehaviour;importjade.lang.acl.ACLMessage;/***

*@authoradmin*/

public class ReceiverAgent extendsAgent {/**Creates a new instance of ReceiverAgent*/

//把接收到的信息打印出来

protected voidsetup() {

addBehaviour(new CyclicBehaviour(this) {public voidaction() {

ACLMessage msg=receive();if (msg != null)

System.out

.println("收到信息 " + myAgent.getLocalName() + "

block();

}

});

}

}

所有类写完之后,这里运行的时候先打开GUI管理器,然后GUI界面下创建一个名叫server的Agent,其类为jadeclient. ReceiverAgent,然后再创建一个名叫hello的Agent,其类为jadeclient.HelloWorldAgent.,记住,顺序不能变

在GUI界面管理器显示如下,在Eclipse平台中下面显示的是打印出的信息,可见在同一主机的不同容器中Agent通信与在同一容器中通信并无二样。

看一个稍微复杂的例子:

packageexamples.inprocess;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午12:53:06*/

importjade.core.Profile;importjade.core.ProfileImpl;importjade.core.Runtime;importjade.core.behaviours.CyclicBehaviour;importjade.wrapper.AgentContainer;importjade.wrapper.AgentController;public classInProcessTest {//Simple class behaving as a Condition Variable

public static classCondVar {private boolean value = false;synchronized void waitOn() throwsInterruptedException {while (!value) {

wait();

}

}synchronized voidsignal() {

value= true;

notifyAll();

}

}//End of CondVar class//This class is a custom Agent, accepting an Object through the//object-to-Agent communication channel, and displying it on the//standard output.

public static class CustomAgent extendsjade.core.Agent {public voidsetup() {//Accept objects through the object-to-Agent communication//channel, with a maximum size of 10 queued objects

setEnabledO2ACommunication(true, 10);//Notify blocked threads that the Agent is ready and that//object-to-Agent communication is enabled

Object[] args =getArguments();if (args.length > 0) {

CondVar latch= (CondVar) args[0];

latch.signal();

}//Add a suitable cyclic behaviour...

addBehaviour(newCyclicBehaviour() {public voidaction() {//Retrieve the first object in the queue and print it on//the standard output

Object obj =getO2AObject();if (obj != null) {

System.out.println("Got an object from the queue: [" + obj + "]");

}elseblock();

}

});

}public voidtakeDown() {//Disables the object-to-Agent communication channel, thus//waking up all waiting threads

setEnabledO2ACommunication(false, 0);

}

}//End of CustomAgent class

public static voidmain(String args[]) {try{

Runtime rt= Runtime.instance();//获取jade运行时//Exit the JVM when there are no more containers around

rt.setCloseVM(true);//看运行参数中是否有-container

if (args.length > 0) {if (args[0].equalsIgnoreCase("-container")) {//创建一个默认的profile

Profile p = new ProfileImpl(false);//p.setParameter(Profile.MAIN, "false");//Create a new non-main container, connecting to the default//main container (i.e. on this host, port 1099)

System.out.println("Launching the Agent container ..." +p);

AgentContainer ac=rt.createAgentContainer(p);//创建一个新的Agent

AgentController dummy = ac.createNewAgent("inProcess","jade.tools.DummyAgent.DummyAgent", new Object[0]);//启动它

System.out.println("Starting up a DummyAgent...");

dummy.start();//等10秒

Thread.sleep(10000);//杀死这个Agent

System.out.println("Killing DummyAgent...");

dummy.kill();//在同一虚拟机上创建另一个容器,NB,//NB. 两个容器不能共享同一个 Profile对象!!! -->//所以需再创建一个profile对象

p = new ProfileImpl(false);//p.putProperty(Profile.MAIN, "false");

AgentContainer another =rt.createAgentContainer(p);//用两个参数创建一个移动agnet

Object[] arguments = new Object[2];

arguments[0] = "Hello World!";

arguments[1] =dummy;

AgentController mobile= another.createNewAgent("Johnny","examples.mobile.MobileAgent", arguments);

mobile.start();return;

}

}//在8888端口运行一个完整的平台t//create a default Profile

Profile pMain = new ProfileImpl(null, 8888, null);

System.out.println("Launching a whole in-process platform..." +pMain);

AgentContainer mc=rt.createMainContainer(pMain);//使用默认的profile启动一个容器

ProfileImpl pContainer = new ProfileImpl(null, 8888, null);

System.out.println("Launching the Agent container ..." +pContainer);

AgentContainer cont=rt.createAgentContainer(pContainer);

System.out.println("Launching the Agent container after ..." +pContainer);

System.out.println("Launching the rma Agent on the main container ...");

AgentController rma= mc.createNewAgent("rma", "jade.tools.rma.rma", new Object[0]);

rma.start();//Launch a custom Agent, taking an object via the//object-to-Agent communication channel. Notice how an Object//is passed to the Agent, to achieve a startup synchronization://this Object is used as a POSIX 'condvar' or a Win32//'EventSemaphore' object...

CondVar startUpLatch= newCondVar();

AgentController custom= mc.createNewAgent("customAgent", CustomAgent.class.getName(),newObject[] { startUpLatch });

custom.start();//Wait until the Agent starts up and notifies the Object

try{

startUpLatch.waitOn();

}catch(InterruptedException ie) {

ie.printStackTrace();

}//Put an object in the queue, asynchronously

System.out.println("Inserting an object, asynchronously...");

custom.putO2AObject("Message 1", AgentController.ASYNC);

System.out.println("Inserted.");//Put an object in the queue, synchronously

System.out.println("Inserting an object, synchronously...");

custom.putO2AObject(mc, AgentController.SYNC);

System.out.println("Inserted.");

}catch(Exception e) {

e.printStackTrace();

}

}

}

现在看看servlet能不能调用这个,在Eclipse中建立一个web工程,添加jade库,建立一个servlet在servlet中进行调用Agent,看能不能输出结果。

其中主要文件如下

文件名:StartServlet.java:

packageexamples;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午12:57:37*/

importjade.core.Profile;importjade.core.ProfileImpl;importjade.core.Runtime;importjade.wrapper.AgentContainer;importjade.wrapper.AgentController;importjava.io.IOException;importjava.io.PrintWriter;importjavax.servlet.ServletException;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;public class StartServlet extendsHttpServlet {protected voidprocessRequest(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException {

response.setContentType("text/html;charset=UTF-8");

PrintWriter out=response.getWriter();

out.println("");

out.println("

");

out.println("

Servlet StartServlet");

out.println("");

out.println("

");

out.println("

Servlet StartServlet at " + request.getContextPath() + "

");

out.println("你好");try{

Runtime rt=Runtime.instance();

rt.setCloseVM(true);

Profile pMain= new ProfileImpl(null, 8888, null);

AgentContainer mc=rt.createMainContainer(pMain);//set now the default Profile to start a container

ProfileImpl pContainer = new ProfileImpl(null, 8888, null);

out.println("运行Agent容器 ..." +pContainer);

AgentController rma= mc.createNewAgent("rma", "jade.tools.rma.rma", null);

rma.start();

AgentController custom= mc.createNewAgent("custom", "examples.CustomAgent", null);

custom.start();

out.println("我已经启动了一个小Agent");

}catch(Exception e) {

e.printStackTrace();

}

out.println("");

out.println("");

out.close();

}protected voiddoGet(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException {

processRequest(request, response);

}protected voiddoPost(HttpServletRequest request, HttpServletResponse response)throwsServletException, IOException {

processRequest(request, response);

}publicString getServletInfo() {return "Short description";

}

}

CustomAgent.java:

packageexamples;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午1:03:40*/

importjade.core.Agent;importjade.core.behaviours.SimpleBehaviour;public class CustomAgent extendsAgent {public voidsetup() {

SimpleBehaviour helloBehaviour= new SimpleBehaviour(this) {boolean finished = false;public voidaction() {

System.out.println("Hello World Behaviour run: 你好,世界!");

System.out.println("-----我是:-----");

System.out.println("我的本地名称是:" +getLocalName());

System.out.println("我全局唯一的标志名称为:" +getName());

finished= true;try{

Thread.sleep(40000);

}catch(java.lang.InterruptedException e) {

e.printStackTrace();

}

System.out.println("已经过了40秒钟");//这里是为了测试关掉IE之后控制台上还会不会输出信息。

}public booleandone() {returnfinished;

}

};

addBehaviour(helloBehaviour);

}

}

web.xml:

jade

StartServlet

examples.StartServlet

StartServlet

/StartServlet

30

index.html

index.htm

index.jsp

default.html

default.htm

default.jsp

Servlet StartServlet at /jade

你好 运行Agent容器 ...(Profile main=true local-host=59.73.87.114 port=8888 services=jade.core.mobility.AgentMobilityService;jade.core.event.NotificationService host=59.73.87.114 local-port=8888 mtps=[jade.mtp.http.MessageTransportProtocol] jvm=j2se) 我已经启动了一个小Agent

在Tomcat控制台上输出信息:

Hello World Behaviour run: 你好,世界!

-----我是:-----

我的本地名称是:custom

我全局唯一的标志名称为:custom@deepin-84e2a07b:8888/JADE

已经过了40秒钟

同时图形化JADE容器运行,其中“已经过了40秒钟”是在浏览器关闭之后显示,说明Agent容器及Agent启动后一直运行着。

奇怪的是,我每次退出主容器,总显示

Exception while removing reference: java.lang.InterruptedException

java.lang.InterruptedException

at java.lang.Object.wait(Native Method)

at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:116)

at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:132)

at sun.java2d.Disposer.run(Disposer.java:107)

at java.lang.Thread.run(Thread.java:595)

AWT blocker activation interrupted: //后面这个是JADE GUI的bug么?

java.lang.InterruptedException

at java.lang.Object.wait(Native Method)

at java.lang.Object.wait(Object.java:474)

at sun.awt.AWTAutoShutdown.activateBlockerThread(AWTAutoShutdown.java:309)

at sun.awt.AWTAutoShutdown.setToolkitBusy(AWTAutoShutdown.java:226)

at sun.awt.AWTAutoShutdown.notifyToolkitThreadBusy(AWTAutoShutdown.java:118)

at sun.awt.windows.WToolkit.eventLoop(Native Method)

at sun.awt.windows.WToolkit.run(WToolkit.java:269)

at java.lang.Thread.run(Thread.java:595)

不知道什么问题?

一个Agent如何以程序的方式生成另一个Agent:

可以通过以下方式进行创建:

String name = "Alice";

AgentContainer c=getContainerController();try{

AgentController a= c.createNewAgent( name, "Pong", null);

a.start();

}catch (Exception e){}

其中,createNewAgent方法的第一个参数是要创建的Agent的名称,第二个是Agent的类名,实际使用时要包括他所在的命名空间(包名),第三个参数是要传入的参数的名称。

例子:

packagejadePrime.acl;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午1:08:17*/

/*** Program which creates another Agent and sends

------------ it some messages

comm2.java

**/

importjade.core.AID;importjade.core.Agent;importjade.core.behaviours.SimpleBehaviour;importjade.lang.acl.ACLMessage;importjade.wrapper.AgentContainer;importjade.wrapper.AgentController;public class Comm2 extendsAgent {

String name= "Alice";

AID alice= newAID(name, AID.ISLOCALNAME);protected voidsetup() {

AgentContainer c=getContainerController();

System.out.println("find container!");try{

AgentController a= c.createNewAgent(name, "jadePrime.acl.Pong", null);

a.start();

System.out.println("++++pong has created:" +alice);

}catch(Exception e) {

System.out.println("Create Agent Error!");

addBehaviour(new SimpleBehaviour(this) {int n = 0;public voidaction() {

ACLMessage msg= newACLMessage(ACLMessage.INFORM);

msg.setContent("Message #" +n);

msg.addReceiver(alice);

System.out.println("+++ Sending: " +n);

send(msg);

block(1000);

}public booleandone() {return ++n > 3;

}

});

}

}

}

当我们创建一个Comm2的Agent时,在控制台上会打印出创建alice成功的消息。

如何收集当前AMS中Agent列表

packagejadePrime.manage;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午1:09:53*/

importjade.core.AID;importjade.core.Agent;importjade.domain.AMSService;importjade.domain.FIPAAgentManagement.AMSAgentDescription;importjade.domain.FIPAAgentManagement.SearchConstraints;public class AMSDump extendsAgent {protected voidsetup() {

AMSAgentDescription[] Agents= null;try{

SearchConstraints c= newSearchConstraints();

c.setMaxResults(new Long(-1));

Agents= AMSService.search(this, newAMSAgentDescription(), c);

}catch(Exception e) {

System.out.println("Problem searching AMS: " +e);

e.printStackTrace();

}

AID myID=getAID();for (int i = 0; i < Agents.length; i++) {

AID AgentID=Agents[i].getName();

System.out.println((AgentID.equals(myID)? "*** " : " ") + i + ": "

+AgentID.getName());

}

doDelete();//System.exit(0);

}

}//end class AMSDump

JADE中如何使用DF(Directory Facilitator)Agent提供的黄页服务。

这个比较好理解,DF相当于一个目录服务器,每个提供服务的Agent可以向DF注册其服务,然后,其他的Agent可以从DF中查询该类服务,也可以订阅这类服务,如果是后者,那么一旦这类服务被注册到DF中,则订阅方就可以收到。例程如下:

文件一, DFRegisterAgent.java 服务提供方

packageyellowpages;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午1:11:14*/

importjade.core.Agent;importjade.domain.DFService;importjade.domain.FIPAException;importjade.domain.FIPANames;importjade.domain.FIPAAgentManagement.DFAgentDescription;importjade.domain.FIPAAgentManagement.Property;importjade.domain.FIPAAgentManagement.ServiceDescription;public class DFRegisterAgent extendsAgent {protected voidsetup() {

String serviceName= "unknown";//从命令行的参数中读取服务的名称,默认为unknown

Object[] args =getArguments();if (args != null && args.length > 0) {

serviceName= (String) args[0];

}//注册服务

System.out.println("Agent " + getLocalName() + " registering service \"" +serviceName+ "\" of type \"weather-forecast\"");try{//必要的几个步骤

DFAgentDescription dfd = newDFAgentDescription();

dfd.setName(getAID());

ServiceDescription sd= newServiceDescription();

sd.setName(serviceName);

sd.setType("weather-forecast");//设置服务类型为天气预报

sd.addOntologies("weather-forecast-ontology");

sd.addLanguages(FIPANames.ContentLanguage.FIPA_SL);//使用服务的一方必须遵循的规范,和具有的本体知识

sd.addProperties(new Property("country", "Italy"));

dfd.addServices(sd);

DFService.register(this, dfd);

}catch(FIPAException fe) {

fe.printStackTrace();

}

}

}

文件二.

DFSubscribeAgent.java订阅方

packageyellowpages;/*** @function

*@authorJacksile E-mail:tufujietec@foxmail.com

* @date 2016年1月15日 下午1:12:26*/

importjade.core.AID;importjade.core.Agent;importjade.domain.DFService;importjade.domain.FIPAException;importjade.domain.FIPAAgentManagement.DFAgentDescription;importjade.domain.FIPAAgentManagement.Property;importjade.domain.FIPAAgentManagement.SearchConstraints;importjade.domain.FIPAAgentManagement.ServiceDescription;importjade.lang.acl.ACLMessage;importjade.proto.SubscriptionInitiator;importjade.util.leap.Iterator;public class DFSubscribeAgent extendsAgent {protected voidsetup() {//Build the description used as template for the subscription

DFAgentDescription template = newDFAgentDescription();

ServiceDescription templateSd= newServiceDescription();

templateSd.setType("weather-forecast");

templateSd.addProperties(new Property("country", "Italy"));

template.addServices(templateSd);

SearchConstraints sc= newSearchConstraints();//最多能接受10个结果

sc.setMaxResults(new Long(10));

addBehaviour(new SubscriptionInitiator(this, DFService.createSubscriptionMessage(this,

getDefaultDF(), template, sc)) {protected voidhandleInform(ACLMessage inform) {

System.out.println("Agent " + getLocalName() + ": Notification received from DF");try{

DFAgentDescription[] results=DFService

.decodeNotification(inform.getContent());if (results.length > 0) {for (int i = 0; i < results.length; ++i) {

DFAgentDescription dfd=results[i];

AID provider=dfd.getName();//同一个Agent可能提供很多服务,我们只对天气预报感兴趣

Iterator it =dfd.getAllServices();while(it.hasNext()) {

ServiceDescription sd=(ServiceDescription) it.next();if (sd.getType().equals("weather-forecast")) {

System.out.println("Weather-forecast service for Italy found:");

System.out.println("- Service \"" +sd.getName()+ "\" provided by Agent " +provider.getName());

}

}

}

}

System.out.println();

}catch(FIPAException fe) {

fe.printStackTrace();

}

}

});

}

}

这样,编译后,我们为了体现发布服务和订阅服务时间上的一致关系,我们从命令行来运行这些类。假设已编译成功,文件标志在某目录下yellowpages\ DFSubscribeAgent.class 和yellowpages\ DFRegisterAgent.class,我们在yellowpages目录下运行命令

java jade.Boot –gui service1: yellowpages.DFRegisterAgent

系统运行主容器,同时控制台上显示Agent service1 registering service “noname” of type "weather-forecast"

然后我们在图形界面下,start new Agent,AgentName:subscriber ClassName:yellowpages.DFSubscribeAgent 添加后,控制台上会显示相关启动后的信息,但不会显示收到服务的消息。然后再start new Agent,AgentName:service2 ClassName:yellowpages.DFRegisterAgent 参数设为:rainny,这是控制台上显示注册服务成功的消息和订阅到服务的消息。显示如下:

JADE中使用本体:

Agent之间进行通信要有相同的语言,词汇和协议。在JADE中,Agents之间通信的方式可以有三种:1.使用字符串表达信息的内容,是最基本的方式.当消息内容是原子数据时,这种方式非常便利.2.消息内容使用序列化的java对象,对本地应用,特别是所有Agent都用java实现的情况下也是很有用的.3.为了jade能够以FIPA格式队消息进行编码和解码,扩展预先定义好的类,亦即使用本体的方式。这种方式允许jade的Agent可以同其他异质Agent系统进行互操作。这三种方式下对应设置和获取内容的方法对应如下:

现在有一些已经存在的language来描述ontologies,如DAML+OIL和OWL,JADE并不直接支持这些ontologies,而是将ontologies编码为java类(比如protégé可以将本体直接导出为java类)。

本体提供了Agent交互的语义支持,但必须与内容语言结合使用,后者是Agent交互的语法支持。JADE支持的三种内容语言包括:FIPA-SL类似lisp的语言族;Leap-encoding应用于嵌入式开发;JavaCodec,JADE特定的内容语言。

jade java_JADE提升篇相关推荐

  1. MySQL数据库提升篇-----触发器

    day06 MySQL数据库提升篇-----触发器 一.触发器概述: 触发器是什么?能有什么作用?能给我们带来什么样的操作数据库的方法这都在这里学一一的告诉大家.触发器原理类似于扳机的原理,当你一点击 ...

  2. 经典SQL语句大全(提升篇)

    提升篇 1.说明:复制表(只复制结构,源表名:a 新表名:b) (Access可用) 法一:select * into b from a where 1<>1(仅用于SQlServer) ...

  3. 你所需要的java基础篇和提升篇大总结

    好好学java 致力于java知识分享 关注 精彩内容 你所需要的java全套视频教程 你所需要的java电子图书 你所需要的大数据视频教程 你所需要的java练习项目 如 / 诗 近半个月,每天一篇 ...

  4. Java提升篇-事务隔离级别和传播机制

    转载自 Java提升篇-事务隔离级别和传播机制 问题的提出 为了保证并发操作数据的正确性及一致性,SQL规范于1992年提出了数据库事务隔离级别. 事务隔离级别分类 事务隔离级别由低往高可分为以下几类 ...

  5. (50)FPGA面试技能提升篇(版本控制工具Clearcase、Git)

    1.1 FPGA面试技能提升篇50(版本控制工具Clearcase.Git) 1.1.1 本节目录 1)本节目录: 2)本节引言: 3)FPGA简介: 4)FPGA面试技能提升篇50(版本控制工具Cl ...

  6. (49)FPGA面试技能提升篇(仿真加速平台Palladium、Zebu、Veloce)

    1.1 FPGA面试技能提升篇49(仿真加速平台Palladium.Zebu.Veloce) 1.1.1 本节目录 1)本节目录: 2)本节引言: 3)FPGA简介: 4)FPGA面试技能提升篇49( ...

  7. (48)FPGA面试技能提升篇(数字采样、射频存储、频谱搬移)

    1.1 FPGA面试技能提升篇48(数字采样.射频存储.频谱搬移) 1.1.1 本节目录 1)本节目录: 2)本节引言: 3)FPGA简介: 4)FPGA面试技能提升篇48(数字采样.射频存储.频谱搬 ...

  8. (47)FPGA面试技能提升篇(Aurora协议/接口)

    1.1 FPGA面试技能提升篇47(Aurora协议/接口) 1.1.1 本节目录 1)本节目录: 2)本节引言: 3)FPGA简介: 4)FPGA面试技能提升篇47(Aurora协议): 5)结束语 ...

  9. (45)FPGA面试技能提升篇(Hyperlink接口)

    1.1 FPGA面试技能提升篇45(Hyperlink接口) 1.1.1 本节目录 1)本节目录: 2)本节引言: 3)FPGA简介: 4)FPGA面试技能提升篇45(Hyperlink接口): 5) ...

最新文章

  1. 终于有人解救Python人了!
  2. 华北计算机研究无人机,飞马D200无人机在华北某环境地调项目中的应用案例
  3. eclipse中output folder和deployment assembly用法有什么不同?
  4. 修改注册表实现程序开机自启动
  5. 适用于Java EE / Jakarta EE开发人员的Micronaut
  6. 1.7 编程基础之字符串 25 最长最短单词 python
  7. Linux:rsync error: remote command not found (code 127) at io.c(226) [sender=3.1.2
  8. Visio中图进行任意区域进行颜色填充
  9. Python字典(二)
  10. 哪些人适合做前端开发?HTML5前端发展前景怎么样?
  11. SQL5 查找所有员工的last_name和first_name以及对应部门编号
  12. 如何编写简洁的代码?
  13. 学习python的第二周 第一天
  14. 计算机网络情景分析 写在开始
  15. 软件开发流程中的测试及测试方法简介
  16. 三无电子烟与即将到来的监管时代
  17. 字节跳动 Go RPC 框架 KiteX 性能优化实践
  18. win10彻底关闭休眠状态(1909以上版本)
  19. 防止记录丢失,保存QQ聊天记录文件的方法
  20. bak文件导入到mysql,如何将SQL Server .bak文件导入MySQL?

热门文章

  1. 【自动驾驶轨迹规划之最优控制】
  2. 什么是java引用?
  3. IOS逆向--使用IDA的Patch更改汇编或二进制码并写入项目和deb重新打包
  4. GO语言-第一节-初识Go语言
  5. 【漏洞笔记】Host头攻击
  6. js给浏览器添加历史记录
  7. java中修饰符总结
  8. 简单两步 vscode连接雷电模拟器
  9. python创意turtle作品-[专题]在Python教学中应用turtle创意编程实践
  10. 大学计算机专业容易挂科的内容,大学最“烧脑”的3类专业,很容易挂科,但就业前景一片大好!...