/* * Copyright 2012 Thomas Bocek * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package net.tomp2p.p2p; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import net.tomp2p.connection2.ConnectionConfiguration; import net.tomp2p.futures.BaseFutureAdapter; import net.tomp2p.futures.FutureChannelCreator; import net.tomp2p.futures.FutureResponse; import net.tomp2p.message.Message2; import net.tomp2p.p2p.builder.BroadcastBuilder; import net.tomp2p.peers.Number160; import net.tomp2p.peers.Number480; import net.tomp2p.peers.PeerAddress; import net.tomp2p.storage.Data; import net.tomp2p.utils.ConcurrentCacheMap; import net.tomp2p.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Default implementation for the broadcast. This is a random walk broadcast. * * @author Thomas Bocek * */ public class DefaultBroadcastHandler implements BroadcastHandler { private static final Logger LOG = LoggerFactory.getLogger(DefaultBroadcastHandler.class); private static final Set<Number160> DEBUG_COUNTER = new HashSet<Number160>(); private static final int NR = 10; private static final int MAX_HOP_COUNT = 7; private final Peer peer; private final Random rnd; private final ConcurrentCacheMap<Number160, Boolean> cache = new ConcurrentCacheMap<Number160, Boolean>(); /** * Constructor. * * @param peer * The peer that sends the broadcast messages * @param rnd * Random number, since its a random walk */ public DefaultBroadcastHandler(final Peer peer, final Random rnd) { this.peer = peer; this.rnd = rnd; } /** * Used in JUnit tests only. * * @return Return the number of peer in the debug set */ int getBroadcastCounter() { synchronized (DEBUG_COUNTER) { return DEBUG_COUNTER.size(); } } @Override public void receive(final Message2 message) { final Number160 messageKey = message.getKey(0); final Map<Number480, Data> dataMap; if(message.getDataMap(0)!=null) { dataMap = message.getDataMap(0).dataMap(); } else { dataMap = null; } final int hopCount = message.getInteger(0); if (twiceSeen(messageKey)) { return; } if (LOG.isDebugEnabled()) { LOG.debug("got broadcast map " + dataMap + " from " + peer.getPeerID()); } synchronized (DEBUG_COUNTER) { DEBUG_COUNTER.add(peer.getPeerID()); } if (hopCount < MAX_HOP_COUNT) { if (hopCount == 0) { firstPeer(messageKey, dataMap, hopCount, message.isUdp()); } else { otherPeer(messageKey, dataMap, hopCount, message.isUdp()); } } } /** * 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) { if (isInCache) { cache.put(messageKey, false); } else { 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 Map<Number480, Data> dataMap, final int hopCounter, final boolean isUDP) { final List<PeerAddress> list = peer.getPeerBean().peerMap().getAll(); for (final PeerAddress peerAddress : list) { FutureChannelCreator frr = peer.getConnectionBean().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); FutureResponse futureResponse = peer.getBroadcastRPC().send(peerAddress, broadcastBuilder, future.getChannelCreator(), broadcastBuilder); LOG.debug("1st broadcast to {}", peerAddress); Utils.addReleaseListener(future.getChannelCreator(), futureResponse); } } }); } } /** * 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(final Number160 messageKey, final Map<Number480, Data> dataMap, final int hopCounter, final boolean isUDP) { LOG.debug("other"); final List<PeerAddress> list = peer.getPeerBean().peerMap().getAll(); final int max = Math.min(NR, list.size()); FutureChannelCreator frr = peer.getConnectionBean().reservation() .create(isUDP ? max : 0, isUDP ? 0 : max); frr.addListener(new BaseFutureAdapter<FutureChannelCreator>() { @Override public void operationComplete(final FutureChannelCreator future) throws Exception { if (future.isSuccess()) { for (int i = 0; i < max; i++) { PeerAddress randomAddress = list.remove(rnd.nextInt(list.size())); BroadcastBuilder broadcastBuilder = new BroadcastBuilder(peer, messageKey); broadcastBuilder.dataMap(dataMap); broadcastBuilder.hopCounter(hopCounter + 1); FutureResponse futureResponse = peer.getBroadcastRPC().send(randomAddress, broadcastBuilder, future.getChannelCreator(), broadcastBuilder); LOG.debug("2nd broadcast to {}", randomAddress); Utils.addReleaseListener(future.getChannelCreator(), futureResponse); } } } }); } }