package com.limegroup.gnutella; import java.util.Collection; import java.util.Iterator; import java.util.Set; import java.util.TreeSet; import com.util.LOG; import com.limegroup.gnutella.messages.PingRequest; import com.limegroup.gnutella.messages.Message; import com.limegroup.gnutella.util.Cancellable; import com.limegroup.gnutella.util.IpPort; import com.limegroup.gnutella.util.ProcessingQueue; /** * Sends Gnutella messages via UDP to a set of hosts and calls back to a * listener whenever responses are returned. */ public class UDPHostRanker { private static final MessageRouter ROUTER = RouterService.getMessageRouter(); private static final ProcessingQueue QUEUE = new ProcessingQueue("UDPHostRanker"); /** * The time to wait before expiring a message listener. * * Non-final for testing. */ public static int LISTEN_EXPIRE_TIME = 20 * 1000; /** Send pings every this often */ private static final long SEND_INTERVAL = 500; /** Send this many pings each time */ private static final int MAX_SENDS = 15; /** * The current number of datagrams we've sent in the past 500 milliseconds. */ private static int _sentAmount; /** * The last time we sent a datagram. */ private static long _lastSentTime; /** * set of endpoints we pinged since last expiration */ private static final Set _recent = new TreeSet(IpPort.COMPARATOR); /** * Ranks the specified Collection of hosts. */ public static void rank(Collection hosts) { rank(hosts, null, null, null); } /** * Ranks the specified Collection of hosts with the given message. */ public static void rank(Collection hosts, Message message) { rank(hosts, null, null, message); } /** * Ranks the specified Collection of hosts with the given * Canceller. */ public static void rank(Collection hosts, Cancellable canceller) { rank(hosts, null, canceller, null); } /** * Ranks the specified collection of hosts with the given * MessageListener. */ public static void rank(Collection hosts, MessageListener listener) { rank(hosts, listener, null, null); } /** * Ranks the specified collection of hosts with the given * MessageListener & Cancellable. */ public static void rank(Collection hosts, MessageListener listener, Cancellable canceller) { rank(hosts, listener, canceller, null); } /** * Ranks the specified <tt>Collection</tt> of hosts. * * @param hosts the <tt>Collection</tt> of hosts to rank * @param listener a MessageListener if you want to spy on the message. can * be null. * @param canceller a Cancellable that can short-circuit the sending * @param message the message to send, can be null. * @return a new <tt>UDPHostRanker</tt> instance * @throws <tt>NullPointerException</tt> if the hosts argument is * <tt>null</tt> */ public static void rank(final Collection hosts, final MessageListener listener, Cancellable canceller, final Message message) { if(hosts == null) throw new NullPointerException("null hosts not allowed"); if(canceller == null) { canceller = new Cancellable() { public boolean isCancelled() { return false; } }; } QUEUE.add(new SenderBundle(hosts, listener, canceller, message)); } /** * Waits for UDP listening to be activated. */ private static boolean waitForListening(Cancellable canceller) { int waits = 0; while(!UDPService.instance().isListening() && waits < 10 && !canceller.isCancelled()) { try { Thread.sleep(600); } catch (InterruptedException e) { // Should never happen. ErrorService.error(e); } waits++; } return waits < 10; } /** * Sends the given send bundle. */ private static void send(SenderBundle info) { final Collection hosts = info.hosts; final MessageListener listener = info.listener; final Cancellable canceller = info.canceller; Message message = info.message; // something went wrong with UDPService - don't try to send if (!waitForListening(canceller)) return; if(message == null) message = PingRequest.createUDPPing(); final byte[] messageGUID = message.getGUID(); if (listener != null) ROUTER.registerMessageListener(messageGUID, listener); Iterator iter = hosts.iterator(); while(iter.hasNext() && !canceller.isCancelled()) { IpPort host = (IpPort)iter.next(); if (_recent.contains(host)) continue; _recent.add(host); long now = System.currentTimeMillis(); if(now > _lastSentTime + SEND_INTERVAL) { _sentAmount = 0; } else if(_sentAmount == MAX_SENDS) { try { Thread.sleep(SEND_INTERVAL); now = System.currentTimeMillis(); } catch(InterruptedException ignored) {} _sentAmount = 0; } if(LOG.isTraceEnabled()) LOG.trace("Sending to " + host + ": " + message); UDPService.instance().send(message, host); _sentAmount++; _lastSentTime = now; } // also take care of any MessageListeners if (listener != null) { // Now schedule a runnable that will remove the mapping for the GUID // of the above message after 20 seconds so that we don't store it // indefinitely in memory for no reason. Runnable udpMessagePurger = new Runnable() { public void run() { ROUTER.unregisterMessageListener(messageGUID, listener); } }; // Purge after 20 seconds. RouterService.schedule(udpMessagePurger, LISTEN_EXPIRE_TIME, 0); } } /** * clears the list of Endpoints we pinged since the last reset, * after sending all currently queued messages. */ static void resetData() { QUEUE.add(new Runnable(){ public void run() { _recent.clear(); } }); } /** * Simple bundle that can send itself. */ private static class SenderBundle implements Runnable { private final Collection hosts; private final MessageListener listener; private final Cancellable canceller; private final Message message; public SenderBundle(Collection hosts, MessageListener listener, Cancellable canceller, Message message) { this.hosts = hosts; this.listener = listener; this.canceller = canceller; this.message = message; } public void run() { send(this); } } }