/* The Java RTP/I library, Version 0.1 alpha.
*
* This library provides the functionality of RTP/I as it is specified in
* Internet Draft draft-mauve-rtpi-00.txt.
*
* Copyright (C) 2000 Martin Mauve
* University of Mannheim / Germany
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Martin Mauve
*
* e-mail:
* mauve@informatik.uni-mannheim.de
*
* paper mail:
* Martin Mauve
* University of Mannheim
* Lehrstuhl Praktische Informatik IV
* L15, 16
* 68131 Mannheim
* Germany
*/
package rtpi.transport.ipmc;
import rtpi.transport.Transport;
import rtpi.transport.TransportRecipient;
import rtpi.transport.TransportPacket;
import rtpi.transport.NotificationRecipient;
import java.net.*;
import java.io.IOException;
import java.util.Vector;
import rtpi.util.SyncQueue;
import rtpi.util.Sleeper;
import rtpi.util.WakeUpThread;
import rtpi.util.Sleeper2;
import rtpi.util.WakeUpThread2;
/**
* IPMCTransport is a Transport implementation for (unreliable) IP multicast
* with minimal rate control. If IPMCTransort receives more transport packets
* from the local application, than can be sent according to the given rate,
* then packets are bufferd and delayed. The actual rate should never exceed
* the given rate. Currently this implementation cannot handle more than
* 4 MBit/s. The maximum data rate depends on time resolution (50ms for
* Win95/98 machines) and the (ethernet) MTU.
*/
public class IPMCTransport extends Thread implements Transport, Sleeper, Sleeper2 {
SyncQueue messages = new SyncQueue();
MulticastSocket socket;
InetAddress address;
int port;
int ttl;
TransportRecipient recipient;
IPMCTransportListener listener;
int transmissionRate;
long sleepTime;
long waterHigh;
long watermark;
long waterAccumulatedSince = 0;
boolean waiting = false;
Vector waitingPackets = new Vector(100,50);
WakeUpThread alarm=null;
int bytesSentLastInterval=0;
int bytesReceivedLastInterval=0;
NotificationRecipient notifyWho=null;
int acceptableSendRate=0;
int acceptableReceiveRate=0;
WakeUpThread2 bookKeepThread = null;
static final long BOOK_KEEP_INTERVAL = 500; // The interval with which the bookkeeping is done!
static final int MTU=1500; // Ethernet MTU
static final int TRANSPORT_HEADER_SIZE=100; // generous approximation
static final int MAX_PAYLOAD_SIZE=1400; // Subtract size of header info
static final int MIN_BUF_SIZE=64000; // this is the minimum buffer we expect to get for
; // a socket!
static final int HEADER_SIZE=28; //IP=20 UDP=8;
/**
* This constructs an IPMCTransport instance.
*
* @param taddr The multicast (network) address of this transport instance.
* @param tport The port for this transport address.
* @param tttl The time to live for the transport packets sent.
* @param rate The maximum rate for this transport instance.
*/
public IPMCTransport(InetAddress taddr, int tport, int tttl, int rate) {
address = taddr;
port = tport;
ttl = tttl;
execSetRate(rate);
bookKeepThread = new WakeUpThread2(BOOK_KEEP_INTERVAL, this);
start();
}
public void run() {
Message message=null;
while (true) {
try {
message=(Message) messages.get();
} catch (Exception exe) {
System.err.println("RTQueue : could not get next message!");
continue;
}
switch(message.opCode) {
case Message.QUIT:
execQuit(); // clean up!
return; // return to leave the run method!
case Message.JOIN_GROUP:
execJoinGroup();
break;
case Message.LEAVE_GROUP:
execLeaveGroup();
break;
case Message.REGISTER:
execRegisterTransportRecipient((TransportRecipient) message.args);
break;
case Message.SEND:
execSendTransportPacket((TransportPacket) message.args);
break;
case Message.SET_RATE:
execSetRate(((Integer) message.args).intValue());
break;
case Message.WAKE_UP:
execWakeUp();
break;
case Message.WAKE_UP2:
execWakeUp2();
break;
case Message.QUERY_NETWORK_RESOURCES:
QueryNetworkResourcesArgs args0 = (QueryNetworkResourcesArgs) message.args;
execQueryNetworkResources(args0.senderFree, args0.receiverFree, args0.recipient);
break;
case Message.UPDATE_RECEIVED_RATE:
execUpdateReceivedRate(((Integer) message.args).intValue());
break;
default:
System.err.println("IPMCTransport: illegal message opcode: "+message.opCode+message.args);
break;
}
}
}
/**
* This terminates the reliability service.
*/
public void quit(){
messages.put(new Message(Message.QUIT, null));
}
private void execQuit() {
try {
if (alarm!=null) {
alarm.interrupt();
}
if (bookKeepThread!=null) {
bookKeepThread.interrupt();
}
listener.leaveGroup();
socket.close();
} catch (Exception ex) {
System.err.println("IPMCTransport: Could not execute quit properly, there may me resources remianing. Error was "+ex);
}
return;
}
/**
* This makes the IPMCTransport instrance join the MC group.
*/
public void joinGroup() throws IOException {
if (!address.isMulticastAddress())
throw new IOException();
messages.put(new Message(Message.JOIN_GROUP, null));
}
void execJoinGroup() {
try {
socket=new MulticastSocket(port);
socket.setTimeToLive(ttl);
socket.joinGroup(address);
socket.setSendBufferSize(MIN_BUF_SIZE);
socket.setReceiveBufferSize(MIN_BUF_SIZE);
if (socket.getSendBufferSize()<MIN_BUF_SIZE ||
socket.getReceiveBufferSize()<MIN_BUF_SIZE) {
System.err.println("IPMCTransport: Socketbuffers too small,");
System.err.println("might drop packets locally!");
}
} catch (Exception ex) {
System.err.println("IPMCTransport: cant execJoin!");
return;
}
listener = new IPMCTransportListener(recipient, socket, this);
listener.start();
}
/**
* This makes the IPMCTransport instance leave the MC group.
*/
public void leaveGroup() {
messages.put(new Message(Message.LEAVE_GROUP, null));
}
void execLeaveGroup() {
listener.leaveGroup();
try {
socket.leaveGroup(address);
socket.close();
} catch (IOException ex) {
System.err.println("IPMCTransport: cant execLeave!");
return;
}
socket=null;
}
/**
* Register a recipient for the transport packets received via
* this IPMC transport instance. Note that there can be only
* one recipient. If a second recipient is registerd, this
* second recipient will be the only one packets are delivered
* to.
*
* @param transRec The recipient which should receive transport packets.
*/
public void registerTransportRecipient(TransportRecipient transRec) {
messages.put(new Message(Message.REGISTER, transRec));
}
void execRegisterTransportRecipient(TransportRecipient transRec) {
recipient=transRec;
}
/**
* Send a transport packet.
*
* @param packet The TransportPacket to be sent.
*/
public void sendTransportPacket(TransportPacket packet) {
messages.put(new Message(Message.SEND, packet));
}
void execSendTransportPacket(TransportPacket packet) {
if (waiting) {
waitingPackets.add(packet);
return;
}
watermark+=packet.getLength()+TRANSPORT_HEADER_SIZE;
if (watermark>=waterHigh) {
if ((System.currentTimeMillis()-waterAccumulatedSince) >= sleepTime) {
watermark=packet.getLength()+TRANSPORT_HEADER_SIZE;
waterAccumulatedSince=System.currentTimeMillis();
} else {
waitingPackets.add(0,packet);
alarm = new WakeUpThread(sleepTime,this);
watermark=0;
waterAccumulatedSince=0;
waiting=true;
}
}
if(!waiting) try {
DatagramPacket dPacket=new DatagramPacket(packet.getData(),0,
packet.getLength(), address, port);
socket.send(dPacket);
bytesSentLastInterval+=packet.getLength();
} catch (Exception ex) {
System.err.println("IPMCTransport: cant sendPacket: "+ex+" has the session been joined?");
return;
}
}
/**
* The maximum rate to be sent over this IPMCTransport instance.
*
* @param rate The maximum rate.
*/
public void setRate(int rate) {
messages.put(new Message(Message.SET_RATE, new Integer(rate)));
}
void execSetRate(int rate) {
// max rate we can handle is 4 MBit/s
// with more we'll overrun our socket buffer!
// We could sleep less than 50 ms, however this
// is the time resolution on windows machines.
if (rate>4000000) {
transmissionRate=4000000;
} else {
transmissionRate=rate;
}
// we should be able to transmit at least one
// message before having to sleep!
long minSleep = 1000*MTU*8;
minSleep = minSleep/transmissionRate+1; // +1 because fraction is cut off
if (minSleep>50) {
sleepTime=minSleep;
} else {
sleepTime=50;
}
waterHigh=(sleepTime*transmissionRate)/8000+1; // +1 because fraction is cut off,
// 8000 to transfer bit*ms to byte*sec
}
public void wakeUp() {
messages.put(new Message(Message.WAKE_UP, null));
}
void execWakeUp() {
TransportPacket packet;
int i= 0;
waiting=false;
while (!waiting && !waitingPackets.isEmpty()) {
try {
packet = (TransportPacket) waitingPackets.remove(0);
} catch (ArrayIndexOutOfBoundsException ex) {
System.err.println("IPMCTransport: could not read waiting packet!");
return;
}
watermark+=packet.getLength()+TRANSPORT_HEADER_SIZE;
if (watermark>=waterHigh) {
waitingPackets.add(0,packet);
alarm = new WakeUpThread(sleepTime,this);
watermark=0;
waiting=true;
}
if (!waiting) try {
i++;
DatagramPacket dPacket=new DatagramPacket(packet.getData(), 0,
packet.getLength(), address, port);
socket.send(dPacket);
bytesSentLastInterval+=packet.getLength();
} catch (IOException ex) {
System.err.println("IPMCTransport: cant sendPacket!");
return;
}
}
if (!waiting) {
waterAccumulatedSince=System.currentTimeMillis();
}
}
void updateReceivedRate(int length) {
messages.put(new Message(Message.UPDATE_RECEIVED_RATE, new Integer(length)));
}
private void execUpdateReceivedRate(int length) {
bytesReceivedLastInterval+=length;
}
public void wakeUp2() {
messages.put(new Message(Message.WAKE_UP2, null));
}
void execWakeUp2() {
if ((notifyWho!=null) &&
(acceptableSendRate >= (bytesSentLastInterval*8*1000/BOOK_KEEP_INTERVAL)) &&
(acceptableReceiveRate >= (bytesReceivedLastInterval*8*1000/BOOK_KEEP_INTERVAL))) {
notifyWho.networkResourcesAvailable();
notifyWho=null;
}
bytesSentLastInterval=0;
bytesReceivedLastInterval=0;
bookKeepThread = new WakeUpThread2(BOOK_KEEP_INTERVAL, this);
}
/**
* This requests that a NotificationRecipient is informed when the
* network becomes idle.
*
* @param sendFree The boundary of the send data rate (bit/s) below which the network
* is considered idle.
* @param receivFree The boundary of of the received data rate (bit/s) below which the
* network is considered idle. This includes the data send. Therefore:
* receivedFree=sendFree+x
* @param recipient The notificationRecipient that is interested in receiving a
* notification when the network becomes idle.
*/
public void queryNetworkResources(int senderFree, int receiverFree, NotificationRecipient recipient) {
messages.put(new Message(Message.QUERY_NETWORK_RESOURCES, new QueryNetworkResourcesArgs( senderFree,
receiverFree,
recipient)));
}
void execQueryNetworkResources(int senderFree, int receiverFree, NotificationRecipient recipient) {
acceptableSendRate = senderFree;
acceptableReceiveRate = receiverFree;
notifyWho=recipient;
}
/**
* This queries the maximum transport payload size, which can be transported
* without fragmentation.
*
* @return The maximum IPMCTransport payload size.
*/
public int getTransportPayloadSize() {
return MAX_PAYLOAD_SIZE;
}
/**
* This queries the header size (transport+network).
*
* @return The header size.
*/
public int getHeaderSize() {
return HEADER_SIZE;
}
}