package com.limegroup.gnutella;
import java.util.Collection;
import java.util.Iterator;
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;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;
/**
* Sends Gnutella messages via UDP to a set of hosts and calls back to a
* listener whenever responses are returned.
*/
public class UDPPinger {
private static final Log LOG = LogFactory.getLog(UDPPinger.class);
protected 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;
/**
* Ranks the specified Collection of hosts.
*/
public void rank(Collection hosts) {
rank(hosts, null, null, null);
}
/**
* Ranks the specified Collection of hosts with the given message.
*/
public void rank(Collection hosts, Message message) {
rank(hosts, null, null, message);
}
/**
* Ranks the specified Collection of hosts with the given
* Canceller.
*/
public void rank(Collection hosts, Cancellable canceller) {
rank(hosts, null, canceller, null);
}
/**
* Ranks the specified collection of hosts with the given
* MessageListener.
*/
public void rank(Collection hosts, MessageListener listener) {
rank(hosts, listener, null, null);
}
/**
* Ranks the specified collection of hosts with the given
* MessageListener & Cancellable.
*/
public 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 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 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.
*/
protected void send(Collection hosts,
final MessageListener listener,
Cancellable canceller,
Message 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)
RouterService.getMessageRouter().registerMessageListener(messageGUID, listener);
Iterator iter = hosts.iterator();
while(iter.hasNext() && !canceller.isCancelled())
sendSingleMessage((IpPort)iter.next(),message);
// 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() {
RouterService.getMessageRouter().unregisterMessageListener(messageGUID, listener);
}
};
// Purge after 20 seconds.
RouterService.schedule(udpMessagePurger, LISTEN_EXPIRE_TIME, 0);
}
}
protected void sendSingleMessage(IpPort host, Message message) {
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.getClass()+" "+message);
UDPService.instance().send(message, host);
_sentAmount++;
_lastSentTime = now;
}
/**
* Simple bundle that can send itself.
*/
private 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(hosts,listener,canceller,message);
}
}
}