一、ONE平台路由模块学习
1、Epidemic路由
DirectDelivery
路由是一个极端,即从不复制消息,只有碰到目的节点,才交付消息。Epidemic是另一个极端,采用泛洪(Flooding)机制,只要有机会,就将消息传递给邻居节点。当节点相遇时(如A遇见B),A将其
summary vector
传递给B,B可以求得两节点消息队列的差集,发给A,如此,A便可知道传递哪些消息给B,即A有但B没有的消息。 示意图如下:
1.1 Epidemic路由算法的Update()源码如下:
@Override
public void update() {
super.update();
if (isTransferring() || !canStartTransfer()) {
return; // transferring, don't try other connections yet
}
// Try first the messages that can be delivered to final recipient
//请先尝试可以传递给目的节点的消息
if (exchangeDeliverableMessages() != null) {
return; // started a transfer, don't try others (yet)
}
// then try any/all message to any/all connection
// 尝试将此路由器承载的所有消息发送到此节点的所有邻居节点
this.tryAllMessagesToAllConnections();
}
EpidemicRouter
在DirectDiliveryRouter
基础上添加this.tryAllMessagesToAllConnections()
。
1.2 tryAllMessagesToAllConnections
tryAllMessagesToAllConnections
遍历所有connections
(相当于遍历邻居节点),遍历节点A缓冲区的消息,若有消息能传输,则返回。相关源代码及注释如下:
//ActiveRouter.java
protected Connection tryAllMessagesToAllConnections() {
List<Connection> connections = getConnections(); //取得所有邻居节点
if (connections.size() == 0 || this.getNrofMessages() == 0) {
return null;
}
List<Message> messages = new ArrayList<Message>(this.getMessageCollection()); //取得缓冲区所有消息
this.sortByQueueMode(messages);
return tryMessagesToConnections(messages, connections);
}
//tryMessagesToConnections(messages, connections);
protected Connection tryMessagesToConnections(List<Message> messages, List<Connection> connections) {
for (int i=0, n=connections.size(); i<n; i++) {
Connection con = connections.get(i);
Message started = tryAllMessages(con, messages);
if (started != null) {
return con;
}
}
return null;
}
//tryAllMessages(con, messages); 只要有一个消息能传递,就返回
protected Message tryAllMessages(Connection con, List<Message> messages) {
for (Message m : messages) {
int retVal = startTransfer(m, con);
if (retVal == RCV_OK) { //accepted a message, don't try others
return m;
} else if (retVal > 0) { //系统定义,只有TRY_LATER_BUSY大于0,即为1
return null; // should try later -> don't bother trying others
}
}
return null; // no message was accepted
}
2、SprayAndWait路由
相对于Epidemic,SprayAndWait降低消息在网络中的份数,即对复制次数做一些限制。正如其名,SprayAndWait包含两个阶段(假设每个消息初始化为L copies
,即网络中含有某消息最多L
份):
- Spray:当碰到一个节点时,决定多少份给该节点。最简单的分发策略是一份一份地发;还有另一种分发策略是每次将一半
copies
给遇到的节点(Binary Spray and Wait),当节点只有一份消息时,就退化成DirectDelivery了 - Wait:只要
copies
大于1,就将消息分给碰到的节点,直到1。当节点只有一份的时候,就等待碰到目的节点(想想DirectDelivery)
减半分发(Binary Spray and Wait)是最优的(the minimum expected delay)。确切地说,节点A
有n copies
,遇到节点B
,此时将n/2 copies
(上取整)给B
,节点A
留下n/2 copies
(下取整)。
2.1 设置文件
SprayAndWait需要事先知道消息的份数以及分发策略,相关设置字段如下:
//_settings.txt
Group.router = SprayAndWaitRouter
SprayAndWaitRouter.nrofCopies = 10 //消息的份数
SprayAndWaitRouter.binaryMode = true //true为一半一半地分,false为一份一份地分
//SprayAndWaitRouter.java
//identifier for the initial number of copies setting
public static final String NROF_COPIES = "nrofCopies";
//identifier for the binary-mode setting
public static final String BINARY_MODE = "binaryMode";
//SprayAndWait router's settings name space
public static final String SPRAYANDWAIT_NS = "SprayAndWaitRouter";
public SprayAndWaitRouter(Settings s) {
super(s);
Settings snwSettings = new Settings(SPRAYANDWAIT_NS);
initialNrofCopies = snwSettings.getInt(NROF_COPIES);
isBinary = snwSettings.getBoolean( BINARY_MODE);
}
2.2 增加消息字段
每个消息需要一个字段保存份数copies
,因此,需要为Message
添加新的字段。之前的做法是直接修改Message
类,今天看了SprayAndWaitRouter.java
的源码,可以重写createNewMessage
,在原来基础上利用函数msg.addProperty
添加字段。源代码如下:
/**
* 每个消息需要一个字段保存份数copies,
* 因此,需要为Message添加新的字段。我之前的做法是直接修改Message类,
* 今天看了SprayAndWaitRouter.java的源码,
* 原来还可以这样:重写createNewMessage,在原来基础上利用函数msg.addProperty添加字段。
*/
@Override
public boolean createNewMessage(Message msg) {
makeRoomForNewMessage(msg.getSize());
msg.setTtl(this.msgTtl);
/**
* addProperty(MSG_COUNT_PROPERTY, new Integer(initialNrofCopies))
* 为此消息添加一个通用属性。键可以是任何字符串,
* 但它应该是这样的,以便其他类不会意外使用相同的值。
* 该值可以是任何对象,但最好只存储不可变的对象,
* 因为当复制消息时,只会生成属性的浅副本。
*/
msg.addProperty(MSG_COUNT_PROPERTY, new Integer(initialNrofCopies));
addToMessages(msg, true);
return true;
}
取得和更新该字段值的方法如下:
Integer nrofCopies = (Integer)m.getProperty(MSG_COUNT_PROPERTY); //取值
msg.updateProperty(MSG_COUNT_PROPERTY, nrofCopies); //更新
2.3 update
SprayAndWait的update
函数很简单,类似于Epidemic的tryAllMessagesToAllConnections
,但更严格,即消息队列过滤掉那些copies
为1
的消息。源代码如下:
@Override
public void update() {
super.update();
if (!canStartTransfer() || isTransferring()) {
return; // nothing to transfer or is currently transferring
}
/* try messages that could be delivered to final recipient */
if (exchangeDeliverableMessages() != null) {
return;
}
//获取缓冲区有效消息集合,即copies大于1的消息
/* create a list of SAWMessages that have copies left to distribute */
@SuppressWarnings(value = "unchecked")
List<Message> copiesLeft = sortByQueueMode(getMessagesWithCopiesLeft());
if (copiesLeft.size() > 0) {
/* try to send those messages */
// this.tryMessagesToConnections(copiesLeft, getConnections());
this.tryMessagesToConnections(copiesLeft, getConnections());
}
}
getMessagesWithCopiesLeft()函数在getMessageCollection()
的基础上,过滤掉那些copies
小于1
的消息,源代码如下:
/**
* Creates and returns a list of messages this router is currently
* carrying and still has copies left to distribute (nrof copies > 1).
* 创建并返回此路由器当前承载的消息列表,并且仍有副本要分发
* getMessagesWithCopiesLeft在getMessageCollection()的基础上,过滤掉那些copies小于1的消息
* @return A list of messages that have copies left
*/
protected List<Message> getMessagesWithCopiesLeft() {
List<Message> list = new ArrayList<Message>();
for (Message m : getMessageCollection()) {
Integer nrofCopies = (Integer)m.getProperty(MSG_COUNT_PROPERTY);
assert nrofCopies != null : "SnW message " + m + " didn't have " +
"nrof copies property!";
if (nrofCopies > 1) {
list.add(m);
}
}
//返回的是消息副本数大于1的消息列表
return list;
}
2.4 更新消息的份数
假设A
发送消息m
给B
,那么传输完毕后,A
和B
的缓冲区都有m
,都需要对m
更新copies
。值得注意的是,更新发送端和接收端都由A
发起,源代码如下:
//ActiveRouter.java
public void update() {
super.update();
...
if (con.isMessageTransferred()) { //消息是否传输完毕,即是否过了(1.0*m.getSize())/this.speed没
if (con.getMessage() != null) {
transferDone(con); //更新发送端
con.finalizeTransfer(); //更新接收端
}
}
...
}
//Connection.java 更新接收端
public void finalizeTransfer() {
this.bytesTransferred += msgOnFly.getSize();
getOtherNode(msgFromNode).messageTransferred(this.msgOnFly.getId(), msgFromNode); //更新接收端
clearMsgOnFly();
}
2.4.1 更新发送端
/**
* 在传输完成前调用
* 重写了父类的transferDone 用来更新发送端
* Called just before a transfer is finalized (by
* {@link ActiveRouter#update()}).
* Reduces the number of copies we have left for a message.
* In binary Spray and Wait, sending host is left with floor(n/2) copies,
* but in standard mode, nrof copies left is reduced by one.
*/
@Override
protected void transferDone(Connection con) { //更新发送端
//步骤1:取得消息的copies
Integer nrofCopies;
String msgId = con.getMessage().getId();
/* get this router's copy of the message */
Message msg = getMessage(msgId);
if (msg == null) { // message has been dropped from the buffer after..
return; // ..start of transfer -> no need to reduce amount of copies
}
/* reduce the amount of copies left */
nrofCopies = (Integer)msg.getProperty(MSG_COUNT_PROPERTY);
//步骤2:更新消息的copies
if (isBinary) {
nrofCopies /= 2;
}
else {
nrofCopies--;
}
//更新
msg.updateProperty(MSG_COUNT_PROPERTY, nrofCopies);
}
2.4.2 更新接收端
//更新接收端 重写了父类的messageTransferred用来更新接收端
@Override
public Message messageTransferred(String id, DTNHost from) {
Message msg = super.messageTransferred(id, from);
Integer nrofCopies = (Integer)msg.getProperty(MSG_COUNT_PROPERTY); //获取消息的副本数
assert nrofCopies != null : "Not a SnW message: " + msg;
if (isBinary) {
/* in binary S'n'W the receiving node gets ceil(n/2) copies */
nrofCopies = (int)Math.ceil(nrofCopies/2.0);
}
else {
/* in standard S'n'W the receiving node gets only single copy */
nrofCopies = 1;
}
msg.updateProperty(MSG_COUNT_PROPERTY, nrofCopies); //更新
return msg;
}
3、Prophet算法
Prophet利用节点间之前的相遇情况为每个节点对求得投递预测值delivery predictabilities,若转发节点的delivery predictabilities高于发送节点,那么就将消息复制转发。
Prophet就是用节点间之前相遇的历史信息,动态更新链接的投递可能值(
delivery predictabilities
)。求delivery predictabilities
包含3部分,公式如下:
(1)encounter
节点间相遇越频繁说明delivery predictability
越高,Pinit
是一个初始化常数,在[0,1]
间取值。
(2)age
如果两个节点间有一段时间没相遇了,那么delivery predictability
需要降低。Gamma
是指aging constant
,在[0,1)
间取值,k
指距离上次更新的time units
数。
(3)transitive
考虑节点间的传递性,如节点A
经常遇到节点B
,B
经常遇见C
,那么连接(a,c)
的delivery predictability
也应该更高。β
是放大常数,在[0,1]
间取值。
3.1 常数及设置文件
由上述分析可知,Prophet涉及到三个常数和一个变量:Pinit, Gamma, β, k
。三个常数如下:
//ProphetRouter.java
public static final double P_INIT = 0.75; //delivery predictability initialization constant
public static final double DEFAULT_BETA = 0.25; //delivery predictability transitivity scaling constant default value
public static final double GAMMA = 0.98; //delivery predictability aging constant
private double beta; //可以通过设置文件设置,如ProphetRouter.beta = 0.25
3.2 更新delivery predictabilities
首先需要存储每个节点对的delivery predictabilities
,用HashMap存储。每个节点的私有变量preds
存储本节点与其他节点的delivery predictabilities
,如(a, b), (a, c), (a, …)
。源代码如下:
//ProphetRouter.java
private Map<DTNHost, Double> preds; //delivery predictabilities
private void initPreds() {
this.preds = new HashMap<DTNHost, Double>();
}
3.2.1 何时更新
对于
encounter
和transitive
,当链接建立时更新;对于age
,每隔secondsInTimeUnit
更新一次。值得注意的是,更新age
比较被动。updateDeliveryPredFor
和updateTransitivePreds
都会调用getPredFor
,取得节点的delivery predictabilities
,getPredFor
函数会调用ageDeliveryPreds
更新因age
带来的影响。源代码如下:
//ProphetRouter.java
@Override
public void changedConnection(Connection con) {
super.changedConnection(con);
if (con.isUp()) {
DTNHost otherHost = con.getOtherNode(getHost());
updateDeliveryPredFor(otherHost);
updateTransitivePreds(otherHost);
}
}
public double getPredFor(DTNHost host) {
ageDeliveryPreds(); //make sure preds are updated before getting
if (preds.containsKey(host)) {
return preds.get(host);
} else {
return 0;
}
}
3.2.2 因encounter更新
假设节点A
与节点B
相遇(即连接建立),轮到节点A
做update
,更新的是节点A
上的preds
的(a, b)
的delivery predictabilities
。源代码如下:
//ProphetRouter.java
private void updateDeliveryPredFor(DTNHost host) {
double oldValue = getPredFor(host); //取值之前需要更新age
double newValue = oldValue + (1 - oldValue) * P_INIT;
preds.put(host, newValue);
}
public double getPredFor(DTNHost host) {
ageDeliveryPreds(); //make sure preds are updated before getting
if (preds.containsKey(host)) {
return preds.get(host);
} else {
return 0;
}
}
3.2.3 因age更新
每隔secondsInTimeUnit
,降低所有节点的delivery predictabilities
,源代码如下:
/**
* Ages all entries in the delivery predictions.
* 对投递预测中的所有条目进行老化
* <CODE>P(a,b) = P(a,b)_old * (GAMMA ^ k)</CODE>, where k is number of
* time units that have elapsed since the last time the metric was aged.
* 其中k是自度量上次老化以来经过的时间单位数。
* @see #SECONDS_IN_UNIT_S
*/
private void ageDeliveryPreds() {
double timeDiff = (SimClock.getTime() - this.lastAgeUpdate) /
secondsInTimeUnit;
if (timeDiff == 0) { //如果没超过secondsInTimeUnit,就不更新
return;
}
//所有的节点都乘以GAMMA ^ k
double mult = Math.pow(GAMMA, timeDiff);
for (Map.Entry<DTNHost, Double> e : preds.entrySet()) {
e.setValue(e.getValue()*mult);
}
this.lastAgeUpdate = SimClock.getTime();
}
3.2.4 因transitive更新
/**
* Updates transitive (A->B->C) delivery predictions.
* <CODE>P(a,c) = P(a,c)_old + (1 - P(a,c)_old) * P(a,b) * P(b,c) * BETA
* </CODE>
* @param host The B host who we just met
*/
//updateTransitivePreds(otherHost); 假设a--b,节点a做update,那么下述的host是指b
private void updateTransitivePreds(DTNHost host) {
MessageRouter otherRouter = host.getRouter();
assert otherRouter instanceof ProphetRouter : "PRoPHET only works " +
" with other routers of same type";
double pForHost = getPredFor(host); // P(a,b)
Map<DTNHost, Double> othersPreds =
((ProphetRouter)otherRouter).getDeliveryPreds();
for (Map.Entry<DTNHost, Double> e : othersPreds.entrySet()) {
if (e.getKey() == getHost()) { //此时getHost()是指节点a
continue; // don't add yourself
}
double pOld = getPredFor(e.getKey()); //P(a,c)_old, 实为节点a的getPredFor,相当于this.getPredFor
double pNew = pOld + ( 1 - pOld) * pForHost * e.getValue() * beta; //e.getValue为P(b, c)
preds.put(e.getKey(), pNew);
}
}
3.3 update
ProphetRouter的
update
在DirectDelivery基础上,调用tryOtherMessages
函数,Prophet只将消息发送给delivery predictabilities
更高的节点。举例说明,节点A
遇到节点B
,节点A
是否要将消息m
(其目的节点为C
)复制转发给节点B
,答案是若P(b, c)
大于P(a, c)
,那么就将消息m
复制转发给B
。源代码如下:
@Override
public void update() {
super.update();
if (!canStartTransfer() ||isTransferring()) {
return; // nothing to transfer or is currently transferring
}
// try messages that could be delivered to final recipient
if (exchangeDeliverableMessages() != null) {
return;
}
tryOtherMessages();
}
/**
* Tries to send all other messages to all connected hosts ordered by
* their delivery probability 尝试将所有其他消息发送到所有连接的主机,按其传递概率排序
* @return The return value of {@link #tryMessagesForConnected(List)}
*/
private Tuple<Message, Connection> tryOtherMessages() {
List<Tuple<Message, Connection>> messages =
new ArrayList<Tuple<Message, Connection>>();
Collection<Message> msgCollection = getMessageCollection();
/* for all connected hosts collect all messages that have a higher
probability of delivery by the other host 对于所有连接的主机,收集其他主机传递概率较高的所有消息 */
for (Connection con : getConnections()) {
DTNHost other = con.getOtherNode(getHost());
ProphetRouter othRouter = (ProphetRouter)other.getRouter();
if (othRouter.isTransferring()) {
continue; // skip hosts that are transferring
}
for (Message m : msgCollection) {
if (othRouter.hasMessage(m.getId())) {
continue; // skip messages that the other one has
}
//核心代码
if (othRouter.getPredFor(m.getTo()) > getPredFor(m.getTo())) { //邻居节点到目的节点的概率 大于 本节点到邻居节点的概率
// the other node has higher probability of delivery
messages.add(new Tuple<Message, Connection>(m,con));
}
}
}
if (messages.size() == 0) {
return null;
}
// sort the message-connection tuples
//通过连接另一侧的主机按元组的传递概率对元组进行排序
Collections.sort(messages, new TupleComparator());
return tryMessagesForConnected(messages); // try to send messages
}