the-one code 1

[TOC]

ONE程序代码简析


程序执行主流程

主入口函数

程序的入口函数在core包中的DTNSim.java文件中,其具体为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public static void main(String[] args) {
boolean batchMode = false;
int nrofRuns[] = {0,1};
String confFiles[];
int firstConfIndex = 0;
int guiIndex = 0;

/* set US locale to parse decimals in consistent way */
java.util.Locale.setDefault(java.util.Locale.US);

if (args.length > 0) {
if (args[0].equals(BATCH_MODE_FLAG)) {
batchMode = true;
if (args.length == 1) {
firstConfIndex = 1;
}
else {
nrofRuns = parseNrofRuns(args[1]);
firstConfIndex = 2;
}
}
else { /* GUI mode */
try { /* is there a run index for the GUI mode ? */
guiIndex = Integer.parseInt(args[0]);
firstConfIndex = 1;
} catch (NumberFormatException e) {
firstConfIndex = 0;
}
}
confFiles = args;
}
else {
confFiles = new String[] {null};
}

initSettings(confFiles, firstConfIndex);

if (batchMode) {
long startTime = System.currentTimeMillis();
for (int i=nrofRuns[0]; i<nrofRuns[1]; i++) {
print("Run " + (i+1) + "/" + nrofRuns[1]);
Settings.setRunIndex(i);
resetForNextRun();
new DTNSimTextUI().start();
}
double duration = (System.currentTimeMillis() - startTime)/1000.0;
print("---\nAll done in " + String.format("%.2f", duration) + "s");
}
else {
Settings.setRunIndex(guiIndex);
new DTNSimGUI().start();
}
}

整体而言,整个main函数做了如下的工作,包括首先初始化某些参数,然后根据传输的参数,选择模式,然后,调用initSetting()函数进行相应的设置,然后则开始run整个函数,下面主要分析一下initSetting函数和Setting

initSetting函数

此函数需要两个参数,confFilesfirstIndex两个参数,其中第一个是string型,第二个为int类型,根据相应的参数,initSetting函数会调用Setting类中的相应方法,Setting.java主要是读取default_setting.txt中的内容的函数,具体进行分析。

在setting 中的init函数中,调用了defProperties.load(new FileInputStream(DEF_SETTINGS_FILE));。这个是用来读取整个txt文档并进行配置的,是整个ONE工程中一个重要的部分。

load 与 load0

下面具体进行关于load方法的分析,其实质上是调用load0方法(load只是做了一个检测),下面主要分析load0 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
private void load0(LineReader lr) throws IOException {
StringBuilder outBuffer = new StringBuilder();
int limit;
int keyLen;
int valueStart;
boolean hasSep;
boolean precedingBackslash;

while ((limit = lr.readLine()) >= 0) {
keyLen = 0;
valueStart = limit;
hasSep = false;

//System.out.println("line=<" + new String(lineBuf, 0, limit) + ">");
precedingBackslash = false;
while (keyLen < limit) {
char c = lr.lineBuf[keyLen];
//need check if escaped.
if ((c == '=' || c == ':') && !precedingBackslash) {
valueStart = keyLen + 1;
hasSep = true;
break;
} else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {
valueStart = keyLen + 1;
break;
}
if (c == '\\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
keyLen++;
}
while (valueStart < limit) {
char c = lr.lineBuf[valueStart];
if (c != ' ' && c != '\t' && c != '\f') {
if (!hasSep && (c == '=' || c == ':')) {
hasSep = true;
} else {
break;
}
}
valueStart++;
}
String key = loadConvert(lr.lineBuf, 0, keyLen, outBuffer);
String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, outBuffer);
put(key, value);
}
}

整个Properties是从HashTable继承而来的,所以实际上,可以理解为什么ONE 使 用的配置形式是“key = value”。可以看到,判断当前的: 或者=符号来分割key 和 value。每一行进行相应处理,利用loadConvert()方法来进行从参数到具体setting 的转换。

关于内容的保存,其实质上用的是stack(在Setting中声明如下)

1
2
private Stack<String> oldNamespaces;
private Stack<String> secondaryNamespaces;

使用stack保存???为什么要用stack,不是很懂

关于Setting中的反射机制
1
2
3
4
5
6
7
8
	public Object createIntializedObject(String className) {
Class<?>[] argsClass = {Settings.class};
Object[] args = {this};

return loadObject(className, argsClass, args);
}

Class<?> objClass = getClass(className);//通过getClass来创建相应的类对象,获得相应的反射

方法createIntializeObjectcreateObject 利用反射机制,将相应的参数传出,利用反射机制动态创建配置文件给出的className对象

start()

在进行完初始化的操作之后(init_setting),main函数会调用setRunIndex 和 start() 两个函数来进行后续的处理,即开始整个程序的具体运行,有两种不同模式,分别为:DTNSimTextUI 和 DTNSimGUI

整个函数只是调用了两个函数initModel()runSim(),两个函数的作用都还很简单,分别是初始化仿真模型和进行仿真,碧昂且runSim是一个虚函数,是一个abstract,所以实际上需要针对不同的场景做实现。

initModel

初始化模型这一函数会调用SimScenario.getInstance()这一函数来进行整个模拟场景和节点属性的初始化。

实际上就是new 一个SimScenario 类,具体而言,对于SimScenario类,其构造函数的一些关键部分有:

1
2
3
4
5
Settings s = new Settings(SCENARIO_NS);//"Scenario"
createHosts();

this.world = new World(hosts, worldSizeX, worldSizeY, updateInterval,updateListeners, simulateConnections,
eqHandler.getEventQueues());

可以看到,其实际上调用了createHosts() 和 World() 的构造函数来完成初始化场景和节点的功能,而其中,所有的属性信息都是通过配置文件并利用Setting.java来完成的。

关于DTNHost:它是整个ONE系统的核心,连接了节点移动模型、通信通道以及路由方式这三大重要模块,并负责这几个模块间的通信任务(利用给出的comBus)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static int nextAddress = 0;
private int address;

private Coord location; // where is the host
private Coord destination; // where is it going

private MessageRouter router; //路由
private MovementModel movement; // 移动
private Path path;//路径
private double speed;
private double nextTimeToMove;
private String name;
private List<MessageListener> msgListeners;
private List<MovementListener> movListeners;
private List<NetworkInterface> net;//网络接口
private ModuleCommunicationBus comBus;
createHosts()

对于createHosts这一函数的调用,实际上是创建了相应的interface 和 movementModel 以及相应的router,故而实际上其就是一个整体的节点移动模型通信模型和路由方式的创建。

World()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public World(List<DTNHost> hosts, int sizeX, int sizeY,
double updateInterval, List<UpdateListener> updateListeners,
boolean simulateConnections, List<EventQueue> eventQueues) {
this.hosts = hosts;
this.sizeX = sizeX;
this.sizeY = sizeY;
this.updateInterval = updateInterval;
this.updateListeners = updateListeners;
this.simulateConnections = simulateConnections;
this.eventQueues = eventQueues;

this.simClock = SimClock.getInstance();
this.scheduledUpdates = new ScheduledUpdatesQueue();
this.isCancelled = false;

setNextEventQueue();
initSettings();
}

整个world 的构造函数,可以看到,实际上是规定了相应的包括相应的事件队列(event queue) 和 监听器等(listener)。

runSim

runSim 函数实际上是DTNSimGUI中的runSim函数,其调用startGUI() 函数和后续的world.update()函数,实际上,整个world.update()函数是根据时间驱动的,具体为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while (simTime < endTime && !simCancelled){
if (guiControls.isPaused()) {
wait(10); // release CPU resources when paused
}
else {
try {
world.update();
} catch (AssertionError e) {
// handles both assertion errors and SimErrors
processAssertionError(e);
}
simTime = SimClock.getTime();
}
this.update(false);
}

可以看到,当没有设置pause的时候,会根据时间进行world.update()函数的调用,调用之后,更新当前时间simTime。

在整个world.update()的函数中,包括了这样两个主要的函数,即moveHosts() 和updateHosts()。

moveHosts():

1
2
3
4
5
private void moveHosts(double timeIncrement) {
for (int i=0,n = hosts.size(); i<n; i++) {
DTNHost host = hosts.get(i);
host.move(timeIncrement);
}

也就是每一个时间增量进行一个结点的移动。

updateHosts()函数是一个关键的函数,其调用了hosts.get(i).update(simulateConnections);,其在default_settings.txt中设定为:Scenario.simulateConnections = true

整个update 为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void update(boolean simulateConnections) {
if (!isRadioActive()) {
// Make sure inactive nodes don't have connections
tearDownAllConnections();
return;
}

if (simulateConnections) {
for (NetworkInterface i : net) {
i.update();
}
}
this.router.update();
}

也就是会对每一个网络接口和每一个router(路由)进行更新。

总结

通过前面的分析,我们大概能知道一个从main()函数方法入口进入并进行整个项目调用的流程,但是我们忽略了很多细节,包括movement , interface 以及router,还有之间相互交互的信息等。

从整体而言,整个项目的执行过程即为:通过Setting 根据default_setting.txt 文件中读取相应的内容并进行初始化设置,然后会初始化整个模型,包括了模型的节点的移动模型,通信模型,路由方式等。之后开始具体运行,在运行过程中,其主要是通过update进行更新,更新的时间会提前设定。