package net.tomp2p.p2p;
import java.util.List;
import java.util.NavigableMap;
import java.util.concurrent.atomic.AtomicInteger;
import net.tomp2p.futures.BaseFutureAdapter;
import net.tomp2p.futures.FutureChannelCreator;
import net.tomp2p.futures.FutureResponse;
import net.tomp2p.message.Message;
import net.tomp2p.p2p.builder.BroadcastBuilder;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number640;
import net.tomp2p.peers.PeerAddress;
import net.tomp2p.peers.PeerMap;
import net.tomp2p.storage.Data;
import net.tomp2p.utils.ConcurrentCacheMap;
import net.tomp2p.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* As seen in http://www.hiradastechnika.hu/data/upload/file/2009/2009%20I/
* Pages_from_HT0901a_2.pdf
*
* @author Thomas Bocek
*
*/
public class StructuredBroadcastHandler implements BroadcastHandler {
private static final Logger LOG = LoggerFactory
.getLogger(StructuredBroadcastHandler.class);
// redundancy, if set to 1, we should get exactly the number of messages as the number of peers
private static final int FROM_EACH_BAG = 3;
private static final AtomicInteger broadcastCounter = new AtomicInteger(0);
private static final AtomicInteger messageCounter = new AtomicInteger(0);
private final ConcurrentCacheMap<Number160, Boolean> cache = new ConcurrentCacheMap<Number160, Boolean>();
private volatile Peer peer;
public StructuredBroadcastHandler init(final Peer peer) {
this.peer = peer;
return this;
}
/**
* Used in JUnit tests only.
*
* @return Return the number of peer in the debug set
*/
public int broadcastCounter() {
return broadcastCounter.get();
}
public int messageCounter() {
return messageCounter.get();
}
@Override
public StructuredBroadcastHandler receive(final Message message) {
if (peer == null) {
throw new RuntimeException(
"Init never called. This should be done by the PeerBuilder");
}
final Number160 messageKey = message.key(0);
final NavigableMap<Number640, Data> dataMap;
if (message.dataMap(0) != null) {
dataMap = message.dataMap(0).dataMap();
} else {
dataMap = null;
}
final int hopCount = message.intAt(0);
final int bucketNr = message.intAt(1);
LOG.debug("I {} received a message", peer.peerID());
if (twiceSeen(messageKey)) {
LOG.debug("already forwarded this message in {}", peer.peerID());
return this;
}
LOG.debug("got broadcast map {} from {}", dataMap, peer.peerID());
broadcastCounter.incrementAndGet();
if (hopCount < peer.peerBean().peerMap().nrFilledBags()) {
if (hopCount == 0) {
LOG.debug("zero hop");
firstPeer(messageKey, dataMap, hopCount, message.isUdp());
} else {
LOG.debug("more hop");
otherPeer(message.sender().peerId(), messageKey, dataMap,
hopCount, message.isUdp(), bucketNr);
}
} else {
LOG.debug("max hop reached in {}", peer.peerID());
}
LOG.debug("done");
return this;
}
/**
* If a message is seen for the second time, then we don't want to send this
* message again. The cache has a size of 1024 entries and the objects have
* a default lifetime of 60s.
*
* @param messageKey
* The key of the message
* @return True if this message was send withing the last 60 seconds.
*/
private boolean twiceSeen(final Number160 messageKey) {
Boolean isInCache = cache.putIfAbsent(messageKey, Boolean.TRUE);
if (isInCache != null) {
// ttl refresh
cache.put(messageKey, Boolean.TRUE);
return true;
}
return false;
}
/**
* The first peer is the initiator. This peer that wants to start the
* broadcast will send it to all its neighbors. Since this peer has an
* interest in sending, it should also work more than the other peers.
*
* @param messageKey
* The key of the message
* @param dataMap
* The data map to send around
* @param hopCounter
* The number of hops
* @param isUDP
* Flag if message can be sent with UDP
*/
private void firstPeer(final Number160 messageKey,
final NavigableMap<Number640, Data> dataMap, final int hopCounter,
final boolean isUDP) {
final List<PeerAddress> list = peer.peerBean().peerMap()
.fromEachBag(FROM_EACH_BAG, Number160.BITS);
for (final PeerAddress peerAddress : list) {
final int bucketNr = PeerMap.classMember(peerAddress.peerId(),
peer.peerID());
doSend(messageKey, dataMap, hopCounter, isUDP, peerAddress,
bucketNr);
}
}
/**
* This method is called on relaying peers. We select a random set and we
* send the message to those random peers.
*
* @param messageKey
* The key of the message
* @param dataMap
* The data map to send around
* @param hopCounter
* The number of hops
* @param isUDP
* Flag if message can be sent with UDP
*/
private void otherPeer(Number160 sender, Number160 messageKey,
NavigableMap<Number640, Data> dataMap, int hopCounter,
boolean isUDP, final int bucketNr) {
final List<PeerAddress> list = peer.peerBean().peerMap()
.fromEachBag(FROM_EACH_BAG, bucketNr);
for (final PeerAddress peerAddress : list) {
final int bucketNr2 = PeerMap.classMember(peerAddress.peerId(),
peer.peerID());
doSend(messageKey, dataMap, hopCounter, isUDP, peerAddress,
bucketNr2);
}
}
private void doSend(final Number160 messageKey,
final NavigableMap<Number640, Data> dataMap, final int hopCounter,
final boolean isUDP, final PeerAddress peerAddress,
final int bucketNr) {
FutureChannelCreator frr = peer.connectionBean().reservation()
.create(isUDP ? 1 : 0, isUDP ? 0 : 1);
frr.addListener(new BaseFutureAdapter<FutureChannelCreator>() {
@Override
public void operationComplete(final FutureChannelCreator future)
throws Exception {
if (future.isSuccess()) {
BroadcastBuilder broadcastBuilder = new BroadcastBuilder(
peer, messageKey);
broadcastBuilder.dataMap(dataMap);
broadcastBuilder.hopCounter(hopCounter + 1);
broadcastBuilder.udp(isUDP);
FutureResponse futureResponse = peer.broadcastRPC()
.send(peerAddress, broadcastBuilder,
future.channelCreator(), broadcastBuilder,
bucketNr);
LOG.debug("send to {}", peerAddress);
messageCounter.incrementAndGet();
Utils.addReleaseListener(future.channelCreator(),
futureResponse);
} else {
Utils.addReleaseListener(future.channelCreator());
}
}
});
}
}