/*
* Copyright (c) 2003-2007 Sun Microsystems, Inc. All rights reserved.
*
* The Sun Project JXTA(TM) Software License
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The end-user documentation included with the redistribution, if any, must
* include the following acknowledgment: "This product includes software
* developed by Sun Microsystems, Inc. for JXTA(TM) technology."
* Alternately, this acknowledgment may appear in the software itself, if
* and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
* not be used to endorse or promote products derived from this software
* without prior written permission. For written permission, please contact
* Project JXTA at http://www.jxta.org.
*
* 5. Products derived from this software may not be called "JXTA", nor may
* "JXTA" appear in their name, without prior written permission of Sun.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
* MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* JXTA is a registered trademark of Sun Microsystems, Inc. in the United
* States and other countries.
*
* Please see the license information page at :
* <http://www.jxta.org/project/www/license.html> for instructions on use of
* the license in source files.
*
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals
* on behalf of Project JXTA. For more information on Project JXTA, please see
* http://www.jxta.org.
*
* This license is based on the BSD license adopted by the Apache Foundation.
*/
package net.jxta.impl.util.pipe.reliable;
import net.jxta.id.IDFactory;
import net.jxta.id.ID;
import net.jxta.pipe.PipeID;
import net.jxta.pipe.PipeService;
import net.jxta.pipe.InputPipe;
import net.jxta.pipe.OutputPipe;
import net.jxta.pipe.OutputPipeEvent;
import net.jxta.pipe.OutputPipeListener;
import net.jxta.pipe.PipeMsgListener;
import net.jxta.pipe.PipeMsgEvent;
import net.jxta.protocol.PipeAdvertisement;
import net.jxta.peergroup.PeerGroup;
import net.jxta.peergroup.PeerGroupID;
import net.jxta.peergroup.PeerGroupFactory;
import net.jxta.rendezvous.RendezVousService;
import net.jxta.rendezvous.RendezvousEvent;
import net.jxta.rendezvous.RendezvousListener;
import net.jxta.discovery.DiscoveryService;
import net.jxta.discovery.DiscoveryListener;
import net.jxta.discovery.DiscoveryEvent;
import net.jxta.document.MimeMediaType;
import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.StructuredDocumentFactory;
import net.jxta.document.XMLDocument;
import net.jxta.endpoint.Message;
import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.ByteArrayMessageElement;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.endpoint.Message.ElementIterator;
import java.util.Vector;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Enumeration;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Collections;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.io.IOException;
import java.io.StringReader;
import junit.framework.TestSuite;
import junit.framework.TestCase;
import junit.framework.Test;
import junit.textui.TestRunner;
import net.jxta.impl.util.threads.TaskManager;
public class ReliableTest extends TestCase implements
RendezvousListener, DiscoveryListener, PipeMsgListener, OutputPipeListener {
private static int MIN_LOAD = 1024;
private static int MAX_LOAD = 65536;
private static final String MESSAGE_TAG = "reliable.message";
private static final String SENT_AT_TAG = "reliable.sent.at";
private static final String PAYLOAD_TAG = "reliable.payload";
private static final MimeMediaType MIME_BINARY = MimeMediaType.AOS;
private static String MSG_PIPE_NAME = "ReliableTestMsgPipe";
private static String ACK_PIPE_NAME = "ReliableTestAckPipe";
private static boolean DEBUG = false;
private static boolean ADAPTIVE = false;
private static boolean IS_QUIET = false;
private static boolean IS_SENDER = false;
private static boolean IS_SERVER = false;
private static boolean waitRdv = false;
private static String PRINCIPAL = "password";
private static String PASSWORD = "password";
private static int DROP_MSG = Integer.MAX_VALUE;
private static int BW_LIMIT = Integer.MAX_VALUE;
private static int PIPE_LEN = 327680; // 20 packets of 16K
private static int LATENCY = 0;
private static int DELAY = 200;
private static int ITERATIONS = 1000;
private Object rdvConnectLock = new Object();
private Random random = new Random(System.currentTimeMillis());
private int nextMessageId = 0;
private ArrayList loadElements = null;
private int dropMsgCount = 0;
private ScheduledExecutorService scheduledExecutor;
private ScheduledFuture<?> reliableTestTaskHandle = null;
private PeerGroup netPeerGroup = null;
private RendezVousService rendezvousService = null;
private DiscoveryService discoverySvc = null;
private PipeService pipeSvc = null;
PipeAdvertisement msgPipeAdv = null;
PipeAdvertisement ackPipeAdv = null;
OutputPipe outputPipe = null;
InputPipe inputPipe = null;
OutgoingPipeAdaptorSync outgoing = null;
IncomingPipeAdaptor incoming = null;
ReliableOutputStream ros = null;
ReliableInputStream ris = null;
BlockingQueue<Message> bwQueue = new LinkedBlockingQueue<Message>(Integer.MAX_VALUE);
long bwQueueSz = 0;
long nextInjectTime = 0;
long delayAdj = 0;
long roundingLoss = 0;
long lostToCongestion = 0;
class TimedMsg implements Runnable {
long delivDate;
public TimedMsg(long date) {
delivDate = date;
}
public void run() {
Message msg;
synchronized (bwQueue) {
msg = (Message) bwQueue.poll();
long msgLen = msg.getByteLength();
bwQueueSz -= msgLen;
delayAdj = delivDate - System.currentTimeMillis();
if (ros != null) {
ros.recv(msg);
} else if (ris != null) {
ris.recv(msg);
}
}
}
}
private void bwQueueMsg(Message msg) {
synchronized (bwQueue) {
long len = msg.getByteLength();
if (bwQueueSz + len > PIPE_LEN) {
lostToCongestion++;
if (!IS_QUIET) {
System.out.println("\nSimulating congestion");
}
return;
}
bwQueue.offer(msg);
bwQueueSz += len;
}
// Schedule delivery of the message based on bw, layency, and
// current messages in the pipe.
long now = System.currentTimeMillis();
// The injection or extraction time depends on length and bandwidth
long bitsToClock = msg.getByteLength() * 8000 + roundingLoss;
long delay = bitsToClock / (BW_LIMIT * 1024);
long roundingLoss = bitsToClock % (BW_LIMIT * 1024);
// We can inject a message if/after the last byte of the previous one
// is done injecting.
nextInjectTime = Math.max(nextInjectTime, now) + delay;
// At the new nextInjectTime, we have injected the last byte of the
// new message. The message is delivered when this last byte arrives.
long delivDate = nextInjectTime + LATENCY;
long delivDelay = delivDate - now;
if (delayAdj >= 10) {
delivDelay += 10;
delayAdj -= 10;
}
if (delayAdj <= -10) {
delivDelay -= 10;
delayAdj += 10;
}
// A carefully chosen combination of unrealistic parameters can
// lead to an attempt at delivering messages in the past.
if (delivDelay <= 0) {
delivDelay = 1;
}
// Because we strictly serialize messages.
// Their delivery order is the same than their queuing order. So the
// timer task only needs to pickup the next message and deliver it.
reliableTestTaskHandle = scheduledExecutor.scheduleAtFixedRate(new TimedMsg(delivDate), delivDelay, Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
public ReliableTest(String testName) {
super(testName);
}
public static Test suite() {
TestSuite suite = new TestSuite(ReliableTest.class);
return suite;
}
@Override
protected void setUp() {
scheduledExecutor = TaskManager.getTaskManager().getLocalScheduledExecutorService("ReliableTest");
loadElements = new ArrayList();
for (int size = MIN_LOAD; size <= MAX_LOAD; size = size << 1) {
byte[] le = new byte[size];
random.nextBytes(le);
loadElements.add(le);
}
System.setProperty("net.jxta.tls.password", PASSWORD);
System.setProperty("net.jxta.tls.principal", PRINCIPAL);
try {
netPeerGroup = PeerGroupFactory.newNetPeerGroup();
discoverySvc = netPeerGroup.getDiscoveryService();
pipeSvc = netPeerGroup.getPipeService();
rendezvousService = netPeerGroup.getRendezVousService();
rendezvousService.addListener(this);
} catch (Throwable t) {
t.printStackTrace();
fail("failed to start jxta");
}
if (waitRdv) {
System.out.print("connecting to rendezvous...");
System.out.flush();
synchronized (rdvConnectLock) {
while (!rendezvousService.isConnectedToRendezVous()) {
System.out.print(".");
System.out.flush();
try {
rdvConnectLock.wait(10 * DELAY);
} catch (InterruptedException ignore) {}
}
}
System.out.println(" connected");
}
}
@Override
public void tearDown() {
if (reliableTestTaskHandle != null) {
reliableTestTaskHandle.cancel(false);
reliableTestTaskHandle = null;
}
scheduledExecutor.shutdownNow();
System.exit(0);
}
public static void main(String[] args) throws Exception {
parse(args);
TestRunner.run(suite());
System.err.flush();
System.out.flush();
}
public static void parse(String[] args) {
for (int i = 0; i < args.length; i++) {
if (args[i].equals("-quiet")) {
IS_QUIET = true;
} else if (args[i].equals("-sender")) {
IS_SENDER = true;
} else if (args[i].equals("-receiver")) {
IS_SENDER = false;
} else if (args[i].equals("-server")) {
IS_SENDER = false;
IS_SERVER = true;
} else if (args[i].equals("-waitrdv")) {
waitRdv = true;
} else if (args[i].equals("-delay") && i + 1 < args.length) {
String delayStr = args[++i];
try {
DELAY = Integer.parseInt(delayStr);
} catch (NumberFormatException ex) {
System.err.println("Invalid delay: " + delayStr + USAGE);
return;
}
} else if (args[i].equals("-iterations") && i + 1 < args.length) {
String iterStr = args[++i];
try {
ITERATIONS = Integer.parseInt(iterStr);
} catch (NumberFormatException ex) {
System.err.println("Invalid iterations: " + iterStr + USAGE);
return;
}
} else if (args[i].equals("-drop") && i + 1 < args.length) {
String dropStr = args[++i];
try {
DROP_MSG = Integer.parseInt(dropStr);
} catch (NumberFormatException ex) {
System.err.println("Invalid drop message: " + dropStr + USAGE);
return;
}
} else if (args[i].equals("-bw") && i + 1 < args.length) {
String bwStr = args[++i];
try {
BW_LIMIT = Integer.parseInt(bwStr);
} catch (NumberFormatException ex) {
System.err.println("Invalid bw: " + bwStr + USAGE);
return;
}
} else if (args[i].equals("-pl") && i + 1 < args.length) {
String plStr = args[++i];
try {
PIPE_LEN = Integer.parseInt(plStr);
} catch (NumberFormatException ex) {
System.err.println("Invalid pl: " + plStr + USAGE);
return;
}
} else if (args[i].equals("-lat") && i + 1 < args.length) {
String latStr = args[++i];
try {
LATENCY = Integer.parseInt(latStr);
} catch (NumberFormatException ex) {
System.err.println("Invalid lat: " + latStr + USAGE);
return;
}
} else if (args[i].equals("-minload") && i + 1 < args.length) {
String minlStr = args[++i];
try {
MIN_LOAD = Integer.parseInt(minlStr);
} catch (NumberFormatException ex) {
System.err.println("Invalid minload: " + minlStr + USAGE);
return;
}
} else if (args[i].equals("-maxload") && i + 1 < args.length) {
String maxlStr = args[++i];
try {
MAX_LOAD = Integer.parseInt(maxlStr);
} catch (NumberFormatException ex) {
System.err.println("Invalid maxload: " + maxlStr + USAGE);
return;
}
} else if (args[i].equals("-password") && i + 1 < args.length) {
PASSWORD = args[++i];
} else if (args[i].equals("-principal") && i + 1 < args.length) {
PRINCIPAL = args[++i];
} else if (args[i].equals("-name") && i + 1 < args.length) {
MSG_PIPE_NAME = args[++i] + "MsgPipe";
ACK_PIPE_NAME = args[i] + "AckPipe";
} else if (args[i].equals("-debug")) {
DEBUG = true;
} else if (args[i].equals("-adapt")) {
ADAPTIVE = true;
} else if (args[i].equals("-help")) {
System.err.println(USAGE);
System.err.println(HELP);
return;
}
}
System.out.println(
(IS_SENDER ? "Sender" : "Receiver") + "\n--------" + "\n quiet: " + IS_QUIET + "\n delay: " + DELAY
+ "\n iterations: " + ITERATIONS + "\n drop: " + DROP_MSG + "\n bw: " + BW_LIMIT + "\n pl: "
+ PIPE_LEN + "\n latency: " + LATENCY + "\n min load: " + MIN_LOAD + "\n max load: " + MAX_LOAD
+ "\n adaptive: " + ADAPTIVE + "\n debug: " + DEBUG);
}
public void rendezvousEvent(RendezvousEvent event) {
synchronized (rdvConnectLock) {
rdvConnectLock.notifyAll();
}
}
public void test() {
if (IS_SENDER) {
doSender();
longPause();
longPause();
doSender();
} else {
do {
doReceiver();
} while (IS_SERVER);
}
}
private PipeAdvertisement createPipeAdv(String pipeName) {
PipeAdvertisement padv = (PipeAdvertisement)
AdvertisementFactory.newAdvertisement(PipeAdvertisement.getAdvertisementType());
String pipeType = PipeService.UnicastType;
String composite = pipeName + pipeType;
byte[] seed = Integer.toHexString(composite.hashCode()).getBytes();
PeerGroupID pgID = netPeerGroup.getPeerGroupID();
PipeID pipeId = IDFactory.newPipeID(pgID, seed);
padv.setName(pipeName);
padv.setPipeID(pipeId);
padv.setType(pipeType);
return padv;
}
private void pause() {
synchronized (this) {
try {
wait(DELAY);
} catch (InterruptedException e) {}
}
}
private void longPause() {
try {
synchronized (this) {
wait(10 * DELAY);
}
} catch (InterruptedException e) {}
}
private void doSender() {
try {
int sequence = 0;
// Create the ros and the outgoing adaptor to go with it before
// the outputpipe exists. The pipe will be set when ready. In the
// meantime, send() would just fail or block. However, in the sender
// we do not start before the pipes are created; we do not need to.
// The same trick is more usefull to the receiver.
outgoing = new OutgoingPipeAdaptorSync(null);
ros = ADAPTIVE
? new ReliableOutputStream(outgoing, new AdaptiveFlowControl())
: new ReliableOutputStream(outgoing, new FixedFlowControl(40));
for (int i = 0; i < ITERATIONS; i++) {
// if we do not already have it resolved, retry
// to open the output pipe every so often
if (outputPipe == null) {
pause();
if ((i % 11) == 0) {
PipeAdvertisement padv = IS_SENDER ? msgPipeAdv : ackPipeAdv;
if (padv == null) {
discoverySvc.getRemoteAdvertisements(null, DiscoveryService.ADV, "Name", MSG_PIPE_NAME, 10, this);
discoverySvc.getRemoteAdvertisements(null, DiscoveryService.ADV, "Name", ACK_PIPE_NAME, 10, this);
if (DEBUG) {
System.out.println("launched remote discovery for " + MSG_PIPE_NAME + " and " + ACK_PIPE_NAME);
}
// wait for discovery response to come in
continue;
}
if (DEBUG) {
System.out.println("re-resolving output pipe " + padv.getName());
}
try {
pipeSvc.createOutputPipe(padv, this);
} catch (IOException ex) {
System.err.println(ex.getMessage());
return;
}
}
}
// No need to start before we could supply the pipes; messages
// would go nowhere and/or the other side would not be able to
// resolve the ack pipe.
if (outputPipe == null || inputPipe == null) {
continue;
}
}
System.out.print("Sending...");
System.out.flush();
for (int i = 0; i <= ITERATIONS; i++) {
Message msg = new Message();
if (i == ITERATIONS) {
msg.addMessageElement(new StringMessageElement(MESSAGE_TAG, "mclose", null));
} else {
String messageId = "m" + Integer.toString(nextMessageId++);
msg.addMessageElement(new StringMessageElement(MESSAGE_TAG, messageId, null));
// add a random load element
int index = random.nextInt(loadElements.size());
byte[] le = (byte[]) loadElements.get(index);
MessageElement elm = new ByteArrayMessageElement(PAYLOAD_TAG, MIME_BINARY, le, null);
msg.addMessageElement(elm);
}
msg.addMessageElement(new StringMessageElement(SENT_AT_TAG, Long.toString(System.currentTimeMillis()), null));
try {
sequence = ros.send(msg);
// System.out.println(messageId + " " +
// msg.getByteLength() + "b " +
// sequence + "seq " +
// ros.getMaxAck() + "ack");
} catch (Throwable e) {
e.printStackTrace();
return;
}
}
System.out.print("closing...");
System.out.flush();
while (ros.getMaxAck() != sequence) {
pause();
}
ros.close();
inputPipe.close();
outputPipe.close();
msgPipeAdv = null;
ackPipeAdv = null;
outputPipe = null;
inputPipe = null;
outgoing = null;
incoming = null;
ros = null;
System.out.println("Done");
} catch (Throwable t) {
t.printStackTrace();
}
}
public void discoveryEvent(DiscoveryEvent event) {
Enumeration ae = event.getResponse().getResponses();
while (ae.hasMoreElements()) {
String str = (String) ae.nextElement();
// create Advertisement from response
Advertisement adv = null;
XMLDocument advDocument = null;
try {
advDocument = (XMLDocument) StructuredDocumentFactory.newStructuredDocument( MimeMediaType.XMLUTF8, new StringReader(str) );
adv = AdvertisementFactory.newAdvertisement(advDocument);
} catch (IOException ex) {
System.err.println("error parsing discovery response");
System.err.println(ex.getMessage());
continue;
}
if (adv instanceof PipeAdvertisement) {
PipeAdvertisement pipeAdv = (PipeAdvertisement) adv;
String pipeName = pipeAdv.getName();
if (MSG_PIPE_NAME.equals(pipeName)) {
msgPipeAdv = pipeAdv;
if (DEBUG) {
System.out.println("discovered msg pipe: " + pipeName);
}
try {
pipeSvc.createOutputPipe(msgPipeAdv, this);
if (DEBUG) {
System.out.println("opened msg pipe for output");
}
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
} else if (ACK_PIPE_NAME.equals(pipeName)) {
ackPipeAdv = pipeAdv;
if (DEBUG) {
System.out.println("discovered ack pipe: " + pipeName);
}
try {
inputPipe = pipeSvc.createInputPipe(ackPipeAdv, this);
if (DEBUG) {
System.out.println("opened ack pipe for input");
}
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
}
}
}
}
private void doReceiver() {
try {
if (msgPipeAdv == null) {
msgPipeAdv = createPipeAdv(MSG_PIPE_NAME);
discoverySvc.publish(msgPipeAdv);
discoverySvc.remotePublish(msgPipeAdv);
inputPipe = pipeSvc.createInputPipe(msgPipeAdv, this);
}
} catch (IOException ex) {
fail(ex.getMessage());
}
if (DEBUG) {
System.out.println("published msg pipe: " + msgPipeAdv.getName());
System.out.println("opened msg pipe for input");
}
// We need to give to our input a reference to our output, but,
// obviously, we need to create our input before resolving the output
// pipe, otherwise we do not known when to start trying to resolve.
// In addition, our output must not loose messages while the pipe is
// being resolved: these are acks. All the first packets would remain
// un-acked, which would make for a very slow start.
// Therefore we need a form of output that can be created before
// the pipe. This used to be solved by interposing queues and threads.
// Now, we create the outgoing adaptor without a pipe.
// The pipe will be set whenever it is ready. In the meantime, the
// reliable input stream will block or return false if it is used.
outgoing = new OutgoingPipeAdaptorSync(null);
ris = new ReliableInputStream(outgoing, 0);
try {
if (ackPipeAdv == null) {
ackPipeAdv = createPipeAdv(ACK_PIPE_NAME);
discoverySvc.publish(ackPipeAdv);
discoverySvc.remotePublish(ackPipeAdv);
}
pipeSvc.createOutputPipe(ackPipeAdv, this);
} catch (IOException ex) {
fail(ex.getMessage());
}
if (DEBUG) {
System.out.println("published ack pipe: " + ackPipeAdv.getName());
System.out.println("opened ack pipe for output");
}
System.out.print("Waiting for sender...");
System.out.flush();
while (!ris.hasNextMessage()) {
pause();
}
System.out.println("Receiving");
for (int i = 0; outputPipe == null;) {
pause();
if ((i++ % 11) == 0) {
if (DEBUG) {
System.out.println("re-resolving output pipe " + ackPipeAdv.getName());
}
try {
pipeSvc.createOutputPipe(ackPipeAdv, this);
} catch (IOException ex) {
System.err.println(ex.getMessage());
}
}
}
long startedAt = 0;
long bytesTransferred = 0;
long nbMsgs = 0;
while (true) {
Message msg = null;
try {
msg = ris.nextMessage(true);
} catch (IOException ioe) {
System.err.println("Failed to obtain next message");
ioe.printStackTrace();
}
++nbMsgs;
printMessageForDebug("doReceiver", msg);
String msgId = (msg.getMessageElement(MESSAGE_TAG)).toString();
String sentAt = (msg.getMessageElement(SENT_AT_TAG)).toString();
long size = msg.getByteLength();
long now = System.currentTimeMillis();
long sent = now;
try {
sent = Long.parseLong(sentAt);
} catch (NumberFormatException nex) {
System.err.println("could not parse msg send time: " + sentAt);
continue;
}
// We start computing averages only after the first message has
// been received.
long throughput = 0;
if (nbMsgs > (2 * ITERATIONS / 3)) {
if (startedAt == 0) {
startedAt = System.currentTimeMillis();
} else {
long totalTime = now - startedAt;
if (totalTime <= 0) {
// that can't be. Some time has passed.
totalTime = 1;
}
bytesTransferred += size;
throughput = (((1000 * 8 * bytesTransferred) / totalTime) / 1024);
}
}
long delay = now - sent;
if (msgId.equals("mclose")) {
System.out.println(
"\nResults" + "\n-------" + "\ntransferred: " + bytesTransferred + " bytes"
+ "\nthroughput: " + throughput + " kbps" + "\ncongestion events: " + lostToCongestion);
try {
ris.close();
} catch (IOException ioe1) {}
ris = null;
outgoing.close();
outgoing = null;
outputPipe = null;
longPause();
break;
}
if (!IS_QUIET) {
System.out.print(msgId + " " + size + "b " + delay + "ms " + throughput + "kbps avg\r");
System.out.flush();
}
}
}
private void printMessageForDebug(String header, Message msg) {
if (DEBUG) {
ElementIterator iter = msg.getMessageElements();
System.out.print(header + " " + IS_SENDER + " (");
while (iter.hasNext()) {
MessageElement el = iter.next();
System.out.print(el.getElementName());
if (iter.hasNext()) {
System.out.print(", ");
}
}
System.out.println(")");
}
}
public void pipeMsgEvent(PipeMsgEvent inputPipeEvent) {
Message msg = inputPipeEvent.getMessage();
if (msg == null) {
return;
}
printMessageForDebug("pipeMsgEvent", msg);
if (dropMessage()) {
if (DEBUG) {
System.out.println("dropped incoming:" + (IS_SENDER ? "ack" : "msg"));
}
return;
}
if (BW_LIMIT < Integer.MAX_VALUE) {
bwQueueMsg(msg);
return;
}
// We're redirecting either to the ros or to the ris; depending
// on whether we're on one side or the other of the reliable stream.
// The other stream obj is null.
if (ros != null) {
ros.recv(msg);
} else if (ris != null) {
ris.recv(msg);
}
}
public void outputPipeEvent(OutputPipeEvent outputPipeEvent) {
String pid = outputPipeEvent.getPipeID();
// this will happen in the sender
if (outputPipe == null && pid.equals(msgPipeAdv.getPipeID().toString())) {
outputPipe = outputPipeEvent.getOutputPipe();
outgoing.setPipe(outputPipe);
// the next line is not needed in this case,
// since here we register 'this' as an
// PipeMsgListener (Input Pipe Listener) and
// 'manually' redirect the ack messages to
// ros.recv() from pipeMsgEvent
// incoming = new IncomingPipeAdaptor(inputPipe, ros);
if (DEBUG) {
System.out.println("resolved msg output pipe " + outputPipe.getName());
}
}
// this will happen in the receiver
if (outputPipe == null && pid.equals(ackPipeAdv.getPipeID().toString())) {
outputPipe = outputPipeEvent.getOutputPipe();
outgoing.setPipe(outputPipe);
// the next line is not needed in this case,
// since here we register 'this' as an
// PipeMsgListener (Input Pipe Listener) and
// 'manually' redirect the ack messages to
// ros.recv() from pipeMsgEvent
// incoming = new IncomingPipeAdaptor(inputPipe, ris);
if (DEBUG) {
System.out.println("resolved ack output pipe " + outputPipe.getName());
}
}
}
synchronized boolean dropMessage() {
return ((++dropMsgCount) % DROP_MSG) == 0;
}
private static final String USAGE = "\nUsage: ReliableTest <options>" + "\n" + "options:\n"
+ " -help outputs some usefull advice" + " -quiet only output a summary (" + IS_QUIET + ")\n"
+ " -sender whether to run as sender of messages (" + IS_SENDER + ")\n"
+ " -receiver whether to run as a receiver of messages (" + !IS_SENDER + ")\n"
+ " -server whether to run as a permanent receiver of messages (" + IS_SERVER + ")\n"
+ " -waitrdv wait for a rendezvous connection before starting (not)\n"
+ " -drop drop every Nth messages (on arrival) (" + DROP_MSG + ")\n"
+ " -bw simulated bw cap in Kbit/s (on arrival) (" + BW_LIMIT + ")\n"
+ " -pl simulated pipe length in bytes (only with bw) (" + PIPE_LEN + ")\n"
+ " -lat simulated latency in ms (only with bw) (" + LATENCY + ")\n"
+ " -minload smallest of the random payload sizes in bytes (" + MIN_LOAD + ")\n"
+ " -maxload largest of the random payload sizes in bytes (" + MAX_LOAD + ")\n"
+ " -name base name for the pipes (ReliableTest)\n"
+ " -debug whether to turn on debugging in the peer (" + DEBUG + ")\n"
+ " -adapt Use adaptive flow control (do not)\n" + " -iterations number of times to send a message ("
+ ITERATIONS + ")\n" + " -delay Basic delay unit (" + DELAY + ")\n"
+ " -principal net.jxta.tls.principal property (" + PRINCIPAL + ")\n"
+ " -password net.jxta.tls.password property (" + PASSWORD + ")\n";
private static final String HELP = "Some options serve to simulate particular network conditions.\n"
+ "These conditions are simulated on the destination side of a\n"
+ "link. As a result, the options given to the receiver will\n"
+ "control the behaviour of the data channel, while the options\n"
+ "to the sender will control the behaviour of the ack channel.\n" + "\n" + "These options are the following:\n"
+ "-bw, as in \"bandwidth\":\n" + "\tcontrols the time it takes for the slowest segment of the path\n"
+ "\tto go from begining the transmition of one bit to being ready\n"
+ "\tto transmit the next one. It is expressed in Kbit per second.\n" + "-lat, as in \"latency\":\n"
+ "\tcontrols the time it takes for a bit to traverse the network in\n"
+ "\taddition to the time caused by the bandwidth. In real life, the\n"
+ "\tprincipal contributors to this time would be signal travel time\n"
+ "\tper the laws of physics, and packet processing time at each\n"
+ "\tnode along the path. The essential characteristics of latency,\n"
+ "\tis that over one given packet's latency time, a number of other\n" + "\tpackets can begin traveling as well.\n"
+ "-pl, as in \"path length\":\n" + "\tcontrols the actual amount of data that can be traveling along\n"
+ "\tthe network path (in bytes). In real life, this is the\n"
+ "\tproduct bandwidth * latency + some buffering, but the three\n"
+ "\tvalues can be chosen differently in order to simulate extreme\n"
+ "\tconditions. For example, most often pl would be larger than\n"
+ "\tthe bandwidth*latency product to reflect buffering capacity at\n"
+ "\tvarious nodes along the path. Under non-congested conditions\n"
+ "\tthese buffers are used to store at most one pending packet\n"
+ "\ton each node, which ensures back-to-back link utilization\n"
+ "\tdespite statistical variations. In that case, buffering has\n"
+ "\tno noticeable effect on latency. However, buffering space\n"
+ "\tcan retard congestion when the capacity of a node is exceeded\n"
+ "\t(and the apparent latency will increase).\n" + "\tIn practice, under simulated conditions on a single host, it\n"
+ "\tis impossible to keep the network path fully utilized if\n"
+ "\tpath length is less than twice bandwidth*latency. Any\n"
+ "\trealistic network has at least one extra buffer at each node.\n"
+ "\tSetting a path length smaller than the bandwidth*latency\n"
+ "\tproduct is somewhat artifical. It roughly corresponds to some\n"
+ "\tnode along the path delaying packets with no buffer to store\n"
+ "\tthem while they wait. In effect it results in the bandwidth\n"
+ "\tlowering to match, but makes congestions harder to predict. So\n"
+ "\tit is usefull for testing congestion recovery mechanisms.\n";
}