com.gemstone.org.jgroups.protocols.obsolete.UDP.txt Maven / Gradle / Ivy
Show all versions of gemfire-jgroups Show documentation
// $Id: UDP.java.txt,v 1.1 2005/06/24 11:19:28 belaban Exp $
package org.jgroups.protocols;
import org.jgroups.*;
import org.jgroups.stack.IpAddress;
import org.jgroups.stack.Protocol;
import org.jgroups.util.List;
import org.jgroups.util.*;
import org.jgroups.util.Queue;
import java.io.*;
import java.net.*;
import java.util.*;
/**
* IP multicast transport based on UDP. Messages to the group (msg.dest == null) will
* be multicast (to all group members), whereas point-to-point messages
* (msg.dest != null) will be unicast to a single member. Uses a multicast and
* a unicast socket.
* The following properties are being read by the UDP protocol
* param mcast_addr - the multicast address to use default is 228.8.8.8
* param mcast_port - (int) the port that the multicast is sent on default is 7600
* param ip_mcast - (boolean) flag whether to use IP multicast - default is true
* param ip_ttl - Set the default time-to-live for multicast packets sent out on this
* socket. default is 32
* param use_packet_handler - If set, the mcast and ucast receiver threads just put
* the datagram's payload (a byte buffer) into a queue, from where a separate thread
* will dequeue and handle them (unmarshal and pass up). This frees the receiver
* threads from having to do message unmarshalling; this time can now be spent
* receiving packets. If you have lots of retransmissions because of network
* input buffer overflow, consider setting this property to true (default is false).
* @author Bela Ban
*/
public class UDP extends Protocol implements Runnable {
/** Socket used for
*
* - sending unicast packets and
*
- receiving unicast packets
*
* The address of this socket will be our local address (local_addr) */
DatagramSocket sock=null;
/**
* BoundedList of the last 100 ports used. This is to avoid reusing a port for DatagramSocket
*/
private static BoundedList last_ports_used=null;
/** Maintain a list of local ports opened by DatagramSocket. If this is 0, this option is turned off.
* If bind_port is null, then this options will be ignored */
int num_last_ports=100;
/** IP multicast socket for receiving multicast packets */
MulticastSocket mcast_recv_sock=null;
/** IP multicast socket for sending multicast packets */
MulticastSocket mcast_send_sock=null;
/**
* Traffic class for sending unicast and multicast datagrams.
* Valid values are (check {@link #DatagramSocket.setTrafficClass(int)} for details):
*
* IPTOS_LOWCOST (0x02)
, decimal 2
* IPTOS_RELIABILITY (0x04)
<, decimal 4/LI>
* IPTOS_THROUGHPUT (0x08)
, decimal 8
* IPTOS_LOWDELAY (0x10)
, decimal 16
*
*/
int tos=0; // valid values: 2, 4, 8, 16
/** The address (host and port) of this member */
IpAddress local_addr=null;
/** The name of the group to which this member is connected */
String channel_name=null;
UdpHeader udp_hdr=null;
/** The multicast address (mcast address and port) this member uses */
IpAddress mcast_addr=null;
/** The interface (NIC) to which the unicast and multicast sockets bind */
InetAddress bind_addr=null;
/** Bind the receiver multicast socket to all available interfaces (requires JDK 1.4) */
boolean bind_to_all_interfaces=false;
/** The port to which the unicast receiver socket binds.
* 0 means to bind to any (ephemeral) port */
int bind_port=0;
int port_range=1; // 27-6-2003 bgooren, Only try one port by default
/** The multicast address used for sending and receiving packets */
String mcast_addr_name="228.8.8.8";
/** The multicast port used for sending and receiving packets */
int mcast_port=7600;
/** The multicast receiver thread */
Thread mcast_receiver=null;
/** The unicast receiver thread */
UcastReceiver ucast_receiver=null;
/** Whether to enable IP multicasting. If false, multiple unicast datagram
* packets are sent rather than one multicast packet */
boolean ip_mcast=true;
/** The time-to-live (TTL) for multicast datagram packets */
int ip_ttl=64;
/** The members of this group (updated when a member joins or leaves) */
final Vector members=new Vector(11);
/** Pre-allocated byte stream. Used for serializing datagram packets. Will grow as needed */
final ExposedByteArrayOutputStream out_stream=new ExposedByteArrayOutputStream(1024);
/** Send buffer size of the multicast datagram socket */
int mcast_send_buf_size=32000;
/** Receive buffer size of the multicast datagram socket */
int mcast_recv_buf_size=64000;
/** Send buffer size of the unicast datagram socket */
int ucast_send_buf_size=32000;
/** Receive buffer size of the unicast datagram socket */
int ucast_recv_buf_size=64000;
/** If true, messages sent to self are treated specially: unicast messages are
* looped back immediately, multicast messages get a local copy first and -
* when the real copy arrives - it will be discarded. Useful for Window
* media (non)sense */
boolean loopback=true;
/** Discard packets with a different version. Usually minor version differences are okay. Setting this property
* to true means that we expect the exact same version on all incoming packets */
boolean discard_incompatible_packets=false;
/** Sometimes receivers are overloaded (they have to handle de-serialization etc).
* Packet handler is a separate thread taking care of de-serialization, receiver
* thread(s) simply put packet in queue and return immediately. Setting this to
* true adds one more thread */
boolean use_incoming_packet_handler=false;
/** Used by packet handler to store incoming DatagramPackets */
Queue incoming_queue=null;
/** Dequeues DatagramPackets from packet_queue, unmarshalls them and
* calls handleIncomingUdpPacket() */
IncomingPacketHandler incoming_packet_handler=null;
/** Packets to be sent are stored in outgoing_queue and sent by a separate thread. Enabling this
* value uses an additional thread */
boolean use_outgoing_packet_handler=false;
/** Used by packet handler to store outgoing DatagramPackets */
Queue outgoing_queue=null;
OutgoingPacketHandler outgoing_packet_handler=null;
/** If set it will be added to local_addr. Used to implement
* for example transport independent addresses */
byte[] additional_data=null;
/** Maximum number of bytes for messages to be queued until they are sent. This value needs to be smaller
than the largest UDP datagram packet size */
int max_bundle_size=AUTOCONF.senseMaxFragSizeStatic();
/** Max number of milliseconds until queued messages are sent. Messages are sent when max_bundle_size or
* max_bundle_timeout has been exceeded (whichever occurs faster)
*/
long max_bundle_timeout=20;
/** Enabled bundling of smaller messages into bigger ones */
boolean enable_bundling=false;
/** Used by BundlingOutgoingPacketHandler */
TimeScheduler timer=null;
/** HashMap. Keys=senders, values=destinations. For each incoming message M with sender S, adds
* an entry with key=S and value= sender's IP address and port.
*/
HashMap addr_translation_table=new HashMap();
boolean use_addr_translation=false;
/** The name of this protocol */
static final String name="UDP";
static final String IGNORE_BIND_ADDRESS_PROPERTY="ignore.bind.address";
/** Usually, src addresses are nulled, and the receiver simply sets them to the address of the sender. However,
* for multiple addresses on a Windows loopback device, this doesn't work
* (see http://jira.jboss.com/jira/browse/JGRP-79 and the JGroups wiki for details). This must be the same
* value for all members of the same group. Default is true, for performance reasons */
boolean null_src_addresses=true;
long num_msgs_sent=0, num_msgs_received=0, num_bytes_sent=0, num_bytes_received=0;
/**
* public constructor. creates the UDP protocol, and initializes the
* state variables, does however not start any sockets or threads
*/
public UDP() {
;
}
/**
* debug only
*/
public String toString() {
return "UDP(local address: " + local_addr + ')';
}
public void resetStats() {
num_msgs_sent=num_msgs_received=num_bytes_sent=num_bytes_received=0;
}
private BoundedList getLastPortsUsed() {
if(last_ports_used == null)
last_ports_used=new BoundedList(num_last_ports);
return last_ports_used;
}
public long getNumMessagesSent() {return num_msgs_sent;}
public long getNumMessagesReceived() {return num_msgs_received;}
public long getNumBytesSent() {return num_bytes_sent;}
public long getNumBytesReceived() {return num_bytes_received;}
public String getBindAddress() {return bind_addr != null? bind_addr.toString() : "null";}
public void setBindAddress(String bind_addr) throws UnknownHostException {
this.bind_addr=InetAddress.getByName(bind_addr);
}
public boolean getBindToAllInterfaces() {return bind_to_all_interfaces;}
public void setBindToAllInterfaces(boolean flag) {this.bind_to_all_interfaces=flag;}
public boolean isDiscardIncompatiblePackets() {return discard_incompatible_packets;}
public void setDiscardIncompatiblePackets(boolean flag) {discard_incompatible_packets=flag;}
public boolean isEnableBundling() {return enable_bundling;}
public void setEnableBundling(boolean flag) {enable_bundling=flag;}
public int getMaxBundleSize() {return max_bundle_size;}
public void setMaxBundleSize(int size) {max_bundle_size=size;}
public long getMaxBundleTimeout() {return max_bundle_timeout;}
public void setMaxBundleTimeout(long timeout) {max_bundle_timeout=timeout;}
/* ----------------------- Receiving of MCAST UDP packets ------------------------ */
public void run() {
DatagramPacket packet;
byte receive_buf[]=new byte[65535];
int len, sender_port;
byte[] tmp, data;
InetAddress sender_addr;
// moved out of loop to avoid excessive object creations (bela March 8 2001)
packet=new DatagramPacket(receive_buf, receive_buf.length);
while(mcast_receiver != null && mcast_recv_sock != null) {
try {
packet.setData(receive_buf, 0, receive_buf.length);
mcast_recv_sock.receive(packet);
sender_addr=packet.getAddress();
sender_port=packet.getPort();
len=packet.getLength();
data=packet.getData();
if(len == 4) { // received a diagnostics probe
if(data[0] == 'd' && data[1] == 'i' && data[2] == 'a' && data[3] == 'g') {
handleDiagnosticProbe(sender_addr, sender_port);
continue;
}
}
if(log.isTraceEnabled()){
StringBuffer sb=new StringBuffer("received (mcast) ");
sb.append(len).append(" bytes from ").append(sender_addr).append(':');
sb.append(sender_port).append(" (size=").append(len).append(" bytes)");
log.trace(sb.toString());
}
if(len > receive_buf.length) {
if(log.isErrorEnabled()) log.error("size of the received packet (" + len + ") is bigger than " +
"allocated buffer (" + receive_buf.length + "): will not be able to handle packet. " +
"Use the FRAG protocol and make its frag_size lower than " + receive_buf.length);
}
if(use_incoming_packet_handler) {
tmp=new byte[len];
System.arraycopy(data, 0, tmp, 0, len);
incoming_queue.add(new IncomingQueueEntry(mcast_addr, sender_addr, sender_port, tmp));
}
else
handleIncomingUdpPacket(mcast_addr, sender_addr, sender_port, data);
}
catch(SocketException sock_ex) {
if(log.isTraceEnabled()) log.trace("multicast socket is closed, exception=" + sock_ex);
break;
}
catch(InterruptedIOException io_ex) { // thread was interrupted
; // go back to top of loop, where we will terminate loop
}
catch(Throwable ex) {
if(log.isErrorEnabled())
log.error("failure in multicast receive()", ex);
Util.sleep(100); // so we don't get into 100% cpu spinning (should NEVER happen !)
}
}
if(log.isDebugEnabled()) log.debug("multicast thread terminated");
}
private void handleDiagnosticProbe(InetAddress sender, int port) {
try {
byte[] diag_rsp=getDiagResponse().getBytes();
DatagramPacket rsp=new DatagramPacket(diag_rsp, 0, diag_rsp.length, sender, port);
if(log.isDebugEnabled()) log.debug("sending diag response to " + sender + ':' + port);
sock.send(rsp);
}
catch(Throwable t) {
if(log.isErrorEnabled()) log.error("failed sending diag rsp to " + sender + ':' + port +
", exception=" + t);
}
}
private String getDiagResponse() {
StringBuffer sb=new StringBuffer();
sb.append(local_addr).append(" (").append(channel_name).append(')');
sb.append(" [").append(mcast_addr_name).append(':').append(mcast_port).append("]\n");
sb.append("Version=").append(Version.description).append(", cvs=\"").append(Version.cvs).append("\"\n");
sb.append("bound to ").append(bind_addr).append(':').append(bind_port).append('\n');
sb.append("members: ").append(members).append('\n');
return sb.toString();
}
/* ------------------------------------------------------------------------------- */
/*------------------------------ Protocol interface ------------------------------ */
public String getName() {
return name;
}
public void init() throws Exception {
if(use_incoming_packet_handler) {
incoming_queue=new Queue();
incoming_packet_handler=new IncomingPacketHandler();
}
if(use_outgoing_packet_handler) {
outgoing_queue=new Queue();
if(enable_bundling) {
timer=stack != null? stack.timer : null;
if(timer == null)
throw new Exception("timer could not be retrieved");
outgoing_packet_handler=new BundlingOutgoingPacketHandler();
}
else
outgoing_packet_handler=new OutgoingPacketHandler();
}
}
/**
* Creates the unicast and multicast sockets and starts the unicast and multicast receiver threads
*/
public void start() throws Exception {
if(log.isDebugEnabled()) log.debug("creating sockets and starting threads");
createSockets();
passUp(new Event(Event.SET_LOCAL_ADDRESS, local_addr));
startThreads();
}
public void stop() {
if(log.isDebugEnabled()) log.debug("closing sockets and stopping threads");
stopThreads(); // will close sockets, closeSockets() is not really needed anymore, but...
closeSockets(); // ... we'll leave it in there for now (doesn't do anything if already closed)
}
/**
* Setup the Protocol instance acording to the configuration string
* The following properties are being read by the UDP protocol
* param mcast_addr - the multicast address to use default is 228.8.8.8
* param mcast_port - (int) the port that the multicast is sent on default is 7600
* param ip_mcast - (boolean) flag whether to use IP multicast - default is true
* param ip_ttl - Set the default time-to-live for multicast packets sent out on this socket. default is 32
* @return true if no other properties are left.
* false if the properties still have data in them, ie ,
* properties are left over and not handled by the protocol stack
*/
public boolean setProperties(Properties props) {
String str;
String tmp = null;
super.setProperties(props);
// PropertyPermission not granted if running in an untrusted environment with JNLP.
try {
tmp=System.getProperty("bind.address");
if(Boolean.getBoolean(IGNORE_BIND_ADDRESS_PROPERTY)) {
tmp=null;
}
}
catch (SecurityException ex){
}
if(tmp != null)
str=tmp;
else
str=props.getProperty("bind_addr");
if(str != null) {
try {
bind_addr=InetAddress.getByName(str);
}
catch(UnknownHostException unknown) {
if(log.isFatalEnabled()) log.fatal("(bind_addr): host " + str + " not known");
return false;
}
props.remove("bind_addr");
}
str=props.getProperty("bind_to_all_interfaces");
if(str != null) {
bind_to_all_interfaces=new Boolean(str).booleanValue();
props.remove("bind_to_all_interfaces");
}
str=props.getProperty("bind_port");
if(str != null) {
bind_port=Integer.parseInt(str);
props.remove("bind_port");
}
str=props.getProperty("num_last_ports");
if(str != null) {
num_last_ports=Integer.parseInt(str);
props.remove("num_last_ports");
}
str=props.getProperty("start_port");
if(str != null) {
bind_port=Integer.parseInt(str);
props.remove("start_port");
}
str=props.getProperty("port_range");
if(str != null) {
port_range=Integer.parseInt(str);
props.remove("port_range");
}
str=props.getProperty("mcast_addr");
if(str != null) {
mcast_addr_name=str;
props.remove("mcast_addr");
}
str=props.getProperty("mcast_port");
if(str != null) {
mcast_port=Integer.parseInt(str);
props.remove("mcast_port");
}
str=props.getProperty("ip_mcast");
if(str != null) {
ip_mcast=Boolean.valueOf(str).booleanValue();
props.remove("ip_mcast");
}
str=props.getProperty("ip_ttl");
if(str != null) {
ip_ttl=Integer.parseInt(str);
props.remove("ip_ttl");
}
str=props.getProperty("tos");
if(str != null) {
tos=Integer.parseInt(str);
props.remove("tos");
}
str=props.getProperty("mcast_send_buf_size");
if(str != null) {
mcast_send_buf_size=Integer.parseInt(str);
props.remove("mcast_send_buf_size");
}
str=props.getProperty("mcast_recv_buf_size");
if(str != null) {
mcast_recv_buf_size=Integer.parseInt(str);
props.remove("mcast_recv_buf_size");
}
str=props.getProperty("ucast_send_buf_size");
if(str != null) {
ucast_send_buf_size=Integer.parseInt(str);
props.remove("ucast_send_buf_size");
}
str=props.getProperty("ucast_recv_buf_size");
if(str != null) {
ucast_recv_buf_size=Integer.parseInt(str);
props.remove("ucast_recv_buf_size");
}
str=props.getProperty("loopback");
if(str != null) {
loopback=Boolean.valueOf(str).booleanValue();
props.remove("loopback");
}
str=props.getProperty("discard_incompatible_packets");
if(str != null) {
discard_incompatible_packets=Boolean.valueOf(str).booleanValue();
props.remove("discard_incompatible_packets");
}
// this is deprecated, just left for compatibility (use use_incoming_packet_handler)
str=props.getProperty("use_packet_handler");
if(str != null) {
use_incoming_packet_handler=Boolean.valueOf(str).booleanValue();
props.remove("use_packet_handler");
if(log.isWarnEnabled()) log.warn("'use_packet_handler' is deprecated; use 'use_incoming_packet_handler' instead");
}
str=props.getProperty("use_incoming_packet_handler");
if(str != null) {
use_incoming_packet_handler=Boolean.valueOf(str).booleanValue();
props.remove("use_incoming_packet_handler");
}
str=props.getProperty("use_outgoing_packet_handler");
if(str != null) {
use_outgoing_packet_handler=Boolean.valueOf(str).booleanValue();
props.remove("use_outgoing_packet_handler");
}
str=props.getProperty("max_bundle_size");
if(str != null) {
int bundle_size=Integer.parseInt(str);
if(bundle_size > max_bundle_size) {
if(log.isErrorEnabled()) log.error("max_bundle_size (" + bundle_size +
") is greater than largest UDP fragmentation size (" + max_bundle_size + ')');
return false;
}
if(bundle_size <= 0) {
if(log.isErrorEnabled()) log.error("max_bundle_size (" + bundle_size + ") is <= 0");
return false;
}
max_bundle_size=bundle_size;
props.remove("max_bundle_size");
}
str=props.getProperty("max_bundle_timeout");
if(str != null) {
max_bundle_timeout=Long.parseLong(str);
if(max_bundle_timeout <= 0) {
if(log.isErrorEnabled()) log.error("max_bundle_timeout of " + max_bundle_timeout + " is invalid");
return false;
}
props.remove("max_bundle_timeout");
}
str=props.getProperty("enable_bundling");
if(str != null) {
enable_bundling=Boolean.valueOf(str).booleanValue();
props.remove("enable_bundling");
}
str=props.getProperty("use_addr_translation");
if(str != null) {
use_addr_translation=Boolean.valueOf(str).booleanValue();
props.remove("use_addr_translation");
}
str=props.getProperty("null_src_addresses");
if(str != null) {
null_src_addresses=Boolean.valueOf(str).booleanValue();
props.remove("null_src_addresses");
}
if(props.size() > 0) {
log.error("UDP.setProperties(): the following properties are not recognized: " + props);
return false;
}
if(enable_bundling) {
if(use_outgoing_packet_handler == false)
if(log.isWarnEnabled()) log.warn("enable_bundling is true; setting use_outgoing_packet_handler=true");
use_outgoing_packet_handler=true;
}
return true;
}
/**
* DON'T REMOVE ! This prevents the up-handler thread to be created, which essentially is superfluous:
* messages are received from the network rather than from a layer below.
*/
public void startUpHandler() {
;
}
/**
* handle the UP event.
* @param evt - the event being send from the stack
*/
public void up(Event evt) {
switch(evt.getType()) {
case Event.CONFIG:
passUp(evt);
if(log.isDebugEnabled()) log.debug("received CONFIG event: " + evt.getArg());
handleConfigEvent((HashMap)evt.getArg());
return;
}
passUp(evt);
}
/**
* Caller by the layer above this layer. Usually we just put this Message
* into the send queue and let one or more worker threads handle it. A worker thread
* then removes the Message from the send queue, performs a conversion and adds the
* modified Message to the send queue of the layer below it, by calling Down).
*/
public void down(Event evt) {
Message msg;
Object dest_addr;
if(evt.getType() != Event.MSG) { // unless it is a message handle it and respond
handleDownEvent(evt);
return;
}
msg=(Message)evt.getArg();
if(channel_name != null) {
// added patch by Roland Kurmann (March 20 2003)
// msg.putHeader(name, new UdpHeader(channel_name));
msg.putHeader(name, udp_hdr);
}
dest_addr=msg.getDest();
// Because we don't call Protocol.passDown(), we notify the observer directly (e.g. PerfObserver).
// This way, we still have performance numbers for UDP
if(observer != null)
observer.passDown(evt);
if(dest_addr == null) { // 'null' means send to all group members
if(ip_mcast) {
if(mcast_addr == null) {
if(log.isErrorEnabled()) log.error("dest address of message is null, and " +
"sending to default address fails as mcast_addr is null, too !" +
" Discarding message " + Util.printEvent(evt));
return;
}
// if we want to use IP multicast, then set the destination of the message
msg.setDest(mcast_addr);
}
else {
//sends a separate UDP message to each address
sendMultipleUdpMessages(msg, members);
return;
}
}
try {
sendUdpMessage(msg);
}
catch(Exception e) {
if(log.isErrorEnabled()) log.error("exception=" + e + ", msg=" + msg + ", mcast_addr=" + mcast_addr);
}
}
/*--------------------------- End of Protocol interface -------------------------- */
/* ------------------------------ Private Methods -------------------------------- */
/**
* If the sender is null, set our own address. We cannot just go ahead and set the address
* anyway, as we might be sending a message on behalf of someone else ! E.gin case of
* retransmission, when the original sender has crashed, or in a FLUSH protocol when we
* have to return all unstable messages with the FLUSH_OK response.
*/
private void setSourceAddress(Message msg) {
if(msg.getSrc() == null)
msg.setSrc(local_addr);
}
/**
* Processes a packet read from either the multicast or unicast socket. Needs to be synchronized because
* mcast or unicast socket reads can be concurrent.
* Correction (bela April 19 2005): we acces no instance variables, all vars are allocated on the stack, so
* this method should be reentrant: removed 'synchronized' keyword
*/
void handleIncomingUdpPacket(IpAddress dest, InetAddress sender, int port, byte[] data) {
ByteArrayInputStream inp_stream=null;
DataInputStream inp=null;
Message msg=null;
List l; // used if bundling is enabled
short version;
boolean is_message_list;
try {
// skip the first n bytes (default: 4), this is the version info
inp_stream=new ByteArrayInputStream(data);
inp=new DataInputStream(inp_stream);
version=inp.readShort();
if(Version.compareTo(version) == false) {
if(log.isWarnEnabled()) {
StringBuffer sb=new StringBuffer();
sb.append("packet from ").append(sender).append(':').append(port);
sb.append(" has different version (").append(version);
sb.append(") from ours (").append(Version.printVersion()).append("). ");
if(discard_incompatible_packets)
sb.append("Packet is discarded");
else
sb.append("This may cause problems");
log.warn(sb.toString());
}
if(discard_incompatible_packets)
return;
}
is_message_list=inp.readBoolean();
if(is_message_list) {
l=bufferToList(inp, dest);
for(Enumeration en=l.elements(); en.hasMoreElements();) {
msg=(Message)en.nextElement();
try {
handleMessage(msg);
}
catch(Throwable t) {
if(log.isErrorEnabled())
log.error("failed unmarshalling message list", t);
}
}
}
else {
msg=bufferToMessage(inp, dest, sender, port);
handleMessage(msg);
}
}
catch(Throwable e) {
if(log.isErrorEnabled()) log.error("exception in processing incoming packet", e);
}
finally {
Util.closeInputStream(inp);
Util.closeInputStream(inp_stream);
}
}
void handleMessage(Message msg) {
Event evt;
UdpHeader hdr;
Address dst=msg.getDest();
if(dst == null)
dst=mcast_addr;
if(stats) {
num_msgs_received++;
num_bytes_received+=msg.getLength();
}
// discard my own multicast loopback copy
if(loopback) {
Address src=msg.getSrc();
if((dst == null || (dst != null && dst.isMulticastAddress())) && src != null && local_addr.equals(src)) {
if(log.isTraceEnabled())
log.trace("discarded own loopback multicast packet");
return;
}
}
evt=new Event(Event.MSG, msg);
if(log.isTraceEnabled()) {
StringBuffer sb=new StringBuffer("message is ");
sb.append(msg).append(", headers are ").append(msg.getHeaders());
log.trace(sb.toString());
}
/* Because Protocol.up() is never called by this bottommost layer, we call up() directly in the observer.
* This allows e.g. PerfObserver to get the time of reception of a message */
if(observer != null)
observer.up(evt, up_queue.size());
hdr=(UdpHeader)msg.getHeader(name); // replaced removeHeader() with getHeader()
if(hdr != null) {
/* Discard all messages destined for a channel with a different name */
String ch_name=hdr.channel_name;
// Discard if message's group name is not the same as our group name unless the
// message is a diagnosis message (special group name DIAG_GROUP)
if(ch_name != null && channel_name != null && !channel_name.equals(ch_name) &&
!ch_name.equals(Util.DIAG_GROUP)) {
if(log.isWarnEnabled()) log.warn("discarded message from different group (" +
ch_name + "). Sender was " + msg.getSrc());
return;
}
}
else {
if(log.isErrorEnabled()) log.error("message does not have a UDP header");
}
passUp(evt);
}
void sendUdpMessage(Message msg) throws Exception {
sendUdpMessage(msg, false);
}
/** Send a message to the address specified in dest */
void sendUdpMessage(Message msg, boolean copyForOutgoingQueue) throws Exception {
IpAddress dest;
Message copy;
Event evt;
dest=(IpAddress)msg.getDest(); // guaranteed to be non-null
setSourceAddress(msg);
if(log.isTraceEnabled()) {
StringBuffer sb=new StringBuffer("sending msg to ");
sb.append(msg.getDest()).append(" (src=").append(msg.getSrc()).append("), headers are ").append(msg.getHeaders());
log.trace(sb.toString());
}
// Don't send if destination is local address. Instead, switch dst and src and put in up_queue.
// If multicast message, loopback a copy directly to us (but still multicast). Once we receive this,
// we will discard our own multicast message
if(loopback && (dest.equals(local_addr) || dest.isMulticastAddress())) {
copy=msg.copy();
// copy.removeHeader(name); // we don't remove the header
copy.setSrc(local_addr);
// copy.setDest(dest);
evt=new Event(Event.MSG, copy);
/* Because Protocol.up() is never called by this bottommost layer, we call up() directly in the observer.
This allows e.g. PerfObserver to get the time of reception of a message */
if(observer != null)
observer.up(evt, up_queue.size());
if(log.isTraceEnabled()) log.trace("looped back local message " + copy);
passUp(evt);
if(dest != null && !dest.isMulticastAddress())
return;
}
if(use_outgoing_packet_handler) {
if(copyForOutgoingQueue)
outgoing_queue.add(msg.copy());
else
outgoing_queue.add(msg);
return;
}
send(msg);
}
/** Internal method to serialize and send a message. This method is not reentrant */
void send(Message msg) throws Exception {
Buffer buf;
IpAddress dest=(IpAddress)msg.getDest(); // guaranteed to be non-null
IpAddress src=(IpAddress)msg.getSrc();
synchronized(out_stream) {
buf=messageToBuffer(msg, dest, src);
doSend(buf, dest.getIpAddress(), dest.getPort());
}
}
void doSend(Buffer buf, InetAddress dest, int port) throws IOException {
DatagramPacket packet;
// packet=new DatagramPacket(data, data.length, dest, port);
packet=new DatagramPacket(buf.getBuf(), buf.getOffset(), buf.getLength(), dest, port);
if(stats) {
num_msgs_sent++;
num_bytes_sent+=buf.getLength();
}
if(dest.isMulticastAddress() && mcast_send_sock != null) { // mcast_recv_sock might be null if ip_mcast is false
mcast_send_sock.send(packet);
}
else {
if(sock != null)
sock.send(packet);
}
}
void sendMultipleUdpMessages(Message msg, Vector dests) {
Address dest;
for(int i=0; i < dests.size(); i++) {
dest=(Address)dests.elementAt(i);
msg.setDest(dest);
try {
sendUdpMessage(msg,
true); // copy for outgoing queue if outgoing queue handler is enabled
}
catch(Exception e) {
if(log.isErrorEnabled()) log.error("failed sending multiple messages", e);
}
}
}
/**
* This method needs to be synchronized on out_stream when it is called
* @param msg
* @param dest
* @param src
* @return
* @throws IOException
*/
private Buffer messageToBuffer(Message msg, IpAddress dest, IpAddress src) throws Exception {
Buffer retval;
DataOutputStream out=null;
try {
out_stream.reset();
out=new DataOutputStream(out_stream);
out.writeShort(Version.version); // write the version
out.writeBoolean(false); // single message, *not* a list of messages
nullAddresses(msg, dest, src);
msg.writeTo(out);
revertAddresses(msg, dest, src);
out.flush();
retval=new Buffer(out_stream.getRawBuffer(), 0, out_stream.size());
return retval;
}
finally {
Util.closeOutputStream(out);
}
}
private void nullAddresses(Message msg, IpAddress dest, IpAddress src) {
msg.setDest(null);
if(!dest.isMulticastAddress()) { // unicast
if(src != null) {
if(null_src_addresses)
msg.setSrc(new IpAddress(src.getPort(), false)); // null the host part, leave the port
if(src.getAdditionalData() != null)
((IpAddress)msg.getSrc()).setAdditionalData(src.getAdditionalData());
}
else {
msg.setSrc(null);
}
}
else { // multicast
if(src != null) {
if(null_src_addresses)
msg.setSrc(new IpAddress(src.getPort(), false)); // null the host part, leave the port
if(src.getAdditionalData() != null)
((IpAddress)msg.getSrc()).setAdditionalData(src.getAdditionalData());
}
}
}
private void revertAddresses(Message msg, IpAddress dest, IpAddress src) {
msg.setDest(dest);
msg.setSrc(src);
}
private Message bufferToMessage(DataInputStream instream, IpAddress dest, InetAddress sender, int port)
throws Exception {
Message msg=new Message();
msg.readFrom(instream);
setAddresses(msg, dest, sender, port);
return msg;
}
private void setAddresses(Message msg, IpAddress dest, InetAddress sender, int port) {
// set the destination address
if(msg.getDest() == null && dest != null)
msg.setDest(dest);
// set the source address if not set
IpAddress src_addr=(IpAddress)msg.getSrc();
if(src_addr == null) {
try {msg.setSrc(new IpAddress(sender, port));} catch(Throwable t) {}
}
else {
byte[] tmp_additional_data=src_addr.getAdditionalData();
if(src_addr.getIpAddress() == null) {
try {msg.setSrc(new IpAddress(sender, src_addr.getPort()));} catch(Throwable t) {}
}
if(tmp_additional_data != null)
((IpAddress)msg.getSrc()).setAdditionalData(tmp_additional_data);
}
}
private Buffer listToBuffer(List l, IpAddress dest) throws Exception {
Buffer retval=null;
IpAddress src;
Message msg;
int len=l != null? l.size() : 0;
boolean src_written=false;
DataOutputStream out=null;
out_stream.reset();
try {
out=new DataOutputStream(out_stream);
out.writeShort(Version.version);
out.writeBoolean(true);
out.writeInt(len);
for(Enumeration en=l.elements(); en.hasMoreElements();) {
msg=(Message)en.nextElement();
src=(IpAddress)msg.getSrc();
if(!src_written) {
Util.writeAddress(src, out);
src_written=true;
}
msg.setDest(null);
msg.setSrc(null);
msg.writeTo(out);
revertAddresses(msg, dest, src);
}
out.flush();
retval=new Buffer(out_stream.getRawBuffer(), 0, out_stream.size());
return retval;
}
finally {
Util.closeOutputStream(out);
}
}
private List bufferToList(DataInputStream instream, IpAddress dest) throws Exception {
List l=new List();
DataInputStream in=null;
int len;
Message msg;
Address src;
try {
len=instream.readInt();
src=Util.readAddress(instream);
for(int i=0; i < len; i++) {
msg=new Message();
msg.readFrom(instream);
msg.setDest(dest);
msg.setSrc(src);
l.add(msg);
}
return l;
}
finally {
Util.closeInputStream(in);
}
}
/**
* Create UDP sender and receiver sockets. Currently there are 2 sockets
* (sending and receiving). This is due to Linux's non-BSD compatibility
* in the JDK port (see DESIGN).
*/
void createSockets() throws Exception {
InetAddress tmp_addr=null;
// bind_addr not set, try to assign one by default. This is needed on Windows
// changed by bela Feb 12 2003: by default multicast sockets will be bound to all network interfaces
// CHANGED *BACK* by bela March 13 2003: binding to all interfaces did not result in a correct
// local_addr. As a matter of fact, comparison between e.g. 0.0.0.0:1234 (on hostA) and
// 0.0.0.0:1.2.3.4 (on hostB) would fail !
if(bind_addr == null) {
InetAddress[] interfaces=InetAddress.getAllByName(InetAddress.getLocalHost().getHostAddress());
if(interfaces != null && interfaces.length > 0)
bind_addr=interfaces[0];
}
if(bind_addr == null)
bind_addr=InetAddress.getLocalHost();
if(bind_addr != null)
if(log.isInfoEnabled()) log.info("sockets will use interface " + bind_addr.getHostAddress());
// 2. Create socket for receiving unicast UDP packets. The address and port
// of this socket will be our local address (local_addr)
if(bind_port > 0) {
sock=createDatagramSocketWithBindPort();
}
else {
sock=createEphemeralDatagramSocket();
}
if(tos > 0) {
try {
sock.setTrafficClass(tos);
}
catch(SocketException e) {
log.warn("traffic class of " + tos + " could not be set, will be ignored", e);
}
}
if(sock == null)
throw new Exception("UDP.createSocket(): sock is null");
local_addr=new IpAddress(sock.getLocalAddress(), sock.getLocalPort());
if(additional_data != null)
local_addr.setAdditionalData(additional_data);
// 3. Create socket for receiving IP multicast packets
if(ip_mcast) {
// 3a. Create mcast receiver socket
mcast_recv_sock=new MulticastSocket(mcast_port);
mcast_recv_sock.setTimeToLive(ip_ttl);
tmp_addr=InetAddress.getByName(mcast_addr_name);
mcast_addr=new IpAddress(tmp_addr, mcast_port);
if(bind_to_all_interfaces) {
bindToAllInterfaces(mcast_recv_sock, mcast_addr.getIpAddress());
}
else {
if(bind_addr != null)
mcast_recv_sock.setInterface(bind_addr);
mcast_recv_sock.joinGroup(tmp_addr);
}
// 3b. Create mcast sender socket
mcast_send_sock=new MulticastSocket();
mcast_send_sock.setTimeToLive(ip_ttl);
if(bind_addr != null)
mcast_send_sock.setInterface(bind_addr);
if(tos > 0) {
try {
mcast_send_sock.setTrafficClass(tos); // high throughput
}
catch(SocketException e) {
log.warn("traffic class of " + tos + " could not be set, will be ignored", e);
}
}
}
setBufferSizes();
if(log.isInfoEnabled()) log.info("socket information:\n" + dumpSocketInfo());
}
private void bindToAllInterfaces(MulticastSocket s, InetAddress mcastAddr) throws IOException {
SocketAddress tmp_mcast_addr=new InetSocketAddress(mcastAddr, mcast_port);
Enumeration en=NetworkInterface.getNetworkInterfaces();
while(en.hasMoreElements()) {
NetworkInterface i=(NetworkInterface)en.nextElement();
for(Enumeration en2=i.getInetAddresses(); en2.hasMoreElements();) {
InetAddress addr=(InetAddress)en2.nextElement();
// if(addr.isLoopbackAddress())
// continue;
s.joinGroup(tmp_mcast_addr, i);
if(log.isTraceEnabled())
log.trace("joined " + tmp_mcast_addr + " on interface " + i.getName() + " (" + addr + ")");
break;
}
}
}
/** Creates a DatagramSocket with a random port. Because in certain operating systems, ports are reused,
* we keep a list of the n last used ports, and avoid port reuse */
private DatagramSocket createEphemeralDatagramSocket() throws SocketException {
DatagramSocket tmp=null;
int localPort=0;
while(true) {
tmp=new DatagramSocket(localPort, bind_addr); // first time localPort is 0
if(num_last_ports <= 0)
break;
localPort=tmp.getLocalPort();
if(getLastPortsUsed().contains(new Integer(localPort))) {
if(log.isDebugEnabled())
log.debug("local port " + localPort + " already seen in this session; will try to get other port");
try {tmp.close();} catch(Throwable e) {}
localPort++;
continue;
}
else {
getLastPortsUsed().add(new Integer(localPort));
break;
}
}
return tmp;
}
/**
* Creates a DatagramSocket when bind_port > 0. Attempts to allocate the socket with port == bind_port, and
* increments until it finds a valid port, or until port_range has been exceeded
* @return DatagramSocket The newly created socket
* @throws Exception
*/
private DatagramSocket createDatagramSocketWithBindPort() throws Exception {
DatagramSocket tmp=null;
// 27-6-2003 bgooren, find available port in range (start_port, start_port+port_range)
int rcv_port=bind_port, max_port=bind_port + port_range;
while(rcv_port <= max_port) {
try {
tmp=new DatagramSocket(rcv_port, bind_addr);
break;
}
catch(SocketException bind_ex) { // Cannot listen on this port
rcv_port++;
}
catch(SecurityException sec_ex) { // Not allowed to listen on this port
rcv_port++;
}
// Cannot listen at all, throw an Exception
if(rcv_port >= max_port + 1) { // +1 due to the increment above
throw new Exception("UDP.createSockets(): cannot list on any port in range " +
bind_port + '-' + (bind_port + port_range));
}
}
return tmp;
}
private String dumpSocketInfo() throws Exception {
StringBuffer sb=new StringBuffer(128);
sb.append("local_addr=").append(local_addr);
sb.append(", mcast_addr=").append(mcast_addr);
sb.append(", bind_addr=").append(bind_addr);
sb.append(", ttl=").append(ip_ttl);
if(sock != null) {
sb.append("\nsock: bound to ");
sb.append(sock.getLocalAddress().getHostAddress()).append(':').append(sock.getLocalPort());
sb.append(", receive buffer size=").append(sock.getReceiveBufferSize());
sb.append(", send buffer size=").append(sock.getSendBufferSize());
}
if(mcast_recv_sock != null) {
sb.append("\nmcast_recv_sock: bound to ");
sb.append(mcast_recv_sock.getInterface().getHostAddress()).append(':').append(mcast_recv_sock.getLocalPort());
sb.append(", send buffer size=").append(mcast_recv_sock.getSendBufferSize());
sb.append(", receive buffer size=").append(mcast_recv_sock.getReceiveBufferSize());
}
if(mcast_send_sock != null) {
sb.append("\nmcast_send_sock: bound to ");
sb.append(mcast_send_sock.getInterface().getHostAddress()).append(':').append(mcast_send_sock.getLocalPort());
sb.append(", send buffer size=").append(mcast_send_sock.getSendBufferSize());
sb.append(", receive buffer size=").append(mcast_send_sock.getReceiveBufferSize());
}
return sb.toString();
}
void setBufferSizes() {
if(sock != null) {
try {
sock.setSendBufferSize(ucast_send_buf_size);
}
catch(Throwable ex) {
if(log.isWarnEnabled()) log.warn("failed setting ucast_send_buf_size in sock: " + ex);
}
try {
sock.setReceiveBufferSize(ucast_recv_buf_size);
}
catch(Throwable ex) {
if(log.isWarnEnabled()) log.warn("failed setting ucast_recv_buf_size in sock: " + ex);
}
}
if(mcast_recv_sock != null) {
try {
mcast_recv_sock.setSendBufferSize(mcast_send_buf_size);
}
catch(Throwable ex) {
if(log.isWarnEnabled()) log.warn("failed setting mcast_send_buf_size in mcast_recv_sock: " + ex);
}
try {
mcast_recv_sock.setReceiveBufferSize(mcast_recv_buf_size);
}
catch(Throwable ex) {
if(log.isWarnEnabled()) log.warn("failed setting mcast_recv_buf_size in mcast_recv_sock: " + ex);
}
}
if(mcast_send_sock != null) {
try {
mcast_send_sock.setSendBufferSize(mcast_send_buf_size);
}
catch(Throwable ex) {
if(log.isWarnEnabled()) log.warn("failed setting mcast_send_buf_size in mcast_send_sock: " + ex);
}
try {
mcast_send_sock.setReceiveBufferSize(mcast_recv_buf_size);
}
catch(Throwable ex) {
if(log.isWarnEnabled()) log.warn("failed setting mcast_recv_buf_size in mcast_send_sock: " + ex);
}
}
}
/**
* Closed UDP unicast and multicast sockets
*/
void closeSockets() {
// 1. Close multicast socket
closeMulticastSocket();
// 2. Close socket
closeSocket();
}
void closeMulticastSocket() {
if(mcast_recv_sock != null) {
try {
if(mcast_addr != null) {
mcast_recv_sock.leaveGroup(mcast_addr.getIpAddress());
}
mcast_recv_sock.close(); // this will cause the mcast receiver thread to break out of its loop
mcast_recv_sock=null;
if(log.isDebugEnabled()) log.debug("multicast receive socket closed");
}
catch(IOException ex) {
}
mcast_addr=null;
}
if(mcast_send_sock != null) {
mcast_send_sock.close(); // this will cause the mcast receiver thread to break out of its loop
mcast_send_sock=null;
if(log.isDebugEnabled()) log.debug("multicast send socket closed");
}
}
void closeSocket() {
if(sock != null) {
sock.close();
sock=null;
if(log.isDebugEnabled()) log.debug("socket closed");
}
}
/**
* Starts the unicast and multicast receiver threads
*/
void startThreads() throws Exception {
if(ucast_receiver == null) {
//start the listener thread of the ucast_recv_sock
ucast_receiver=new UcastReceiver();
ucast_receiver.start();
if(log.isDebugEnabled()) log.debug("created unicast receiver thread");
}
if(ip_mcast) {
if(mcast_receiver != null) {
if(mcast_receiver.isAlive()) {
if(log.isDebugEnabled()) log.debug("did not create new multicastreceiver thread as existing " +
"multicast receiver thread is still running");
}
else
mcast_receiver=null; // will be created just below...
}
if(mcast_receiver == null) {
mcast_receiver=new Thread(this, "UDP mcast receiver");
mcast_receiver.setPriority(Thread.MAX_PRIORITY); // needed ????
mcast_receiver.setDaemon(true);
mcast_receiver.start();
}
}
if(use_outgoing_packet_handler)
outgoing_packet_handler.start();
if(use_incoming_packet_handler)
incoming_packet_handler.start();
}
/**
* Stops unicast and multicast receiver threads
*/
void stopThreads() {
Thread tmp;
// 1. Stop the multicast receiver thread
if(mcast_receiver != null) {
if(mcast_receiver.isAlive()) {
tmp=mcast_receiver;
mcast_receiver=null;
closeMulticastSocket(); // will cause the multicast thread to terminate
tmp.interrupt();
try {
tmp.join(100);
}
catch(Exception e) {
}
tmp=null;
}
mcast_receiver=null;
}
// 2. Stop the unicast receiver thread
if(ucast_receiver != null) {
ucast_receiver.stop();
ucast_receiver=null;
}
// 3. Stop the in_packet_handler thread
if(incoming_packet_handler != null)
incoming_packet_handler.stop();
// 4. Stop the outgoing packet handler thread
if(outgoing_packet_handler != null)
outgoing_packet_handler.stop();
}
void handleDownEvent(Event evt) {
switch(evt.getType()) {
case Event.TMP_VIEW:
case Event.VIEW_CHANGE:
synchronized(members) {
members.removeAllElements();
Vector tmpvec=((View)evt.getArg()).getMembers();
for(int i=0; i < tmpvec.size(); i++)
members.addElement(tmpvec.elementAt(i));
}
break;
case Event.GET_LOCAL_ADDRESS: // return local address -> Event(SET_LOCAL_ADDRESS, local)
passUp(new Event(Event.SET_LOCAL_ADDRESS, local_addr));
break;
case Event.CONNECT:
channel_name=(String)evt.getArg();
udp_hdr=new UdpHeader(channel_name);
// removed March 18 2003 (bela), not needed (handled by GMS)
// changed July 2 2003 (bela): we discard CONNECT_OK at the GMS level anyway, this might
// be needed if we run without GMS though
passUp(new Event(Event.CONNECT_OK));
break;
case Event.DISCONNECT:
passUp(new Event(Event.DISCONNECT_OK));
break;
case Event.CONFIG:
if(log.isDebugEnabled()) log.debug("received CONFIG event: " + evt.getArg());
handleConfigEvent((HashMap)evt.getArg());
break;
}
}
void handleConfigEvent(HashMap map) {
if(map == null) return;
if(map.containsKey("additional_data"))
additional_data=(byte[])map.get("additional_data");
if(map.containsKey("send_buf_size")) {
mcast_send_buf_size=((Integer)map.get("send_buf_size")).intValue();
ucast_send_buf_size=mcast_send_buf_size;
}
if(map.containsKey("recv_buf_size")) {
mcast_recv_buf_size=((Integer)map.get("recv_buf_size")).intValue();
ucast_recv_buf_size=mcast_recv_buf_size;
}
setBufferSizes();
}
/* ----------------------------- End of Private Methods ---------------------------------------- */
/* ----------------------------- Inner Classes ---------------------------------------- */
class IncomingQueueEntry {
IpAddress dest=null;
InetAddress sender=null;
int port=-1;
byte[] buf;
public IncomingQueueEntry(IpAddress dest, InetAddress sender, int port, byte[] buf) {
this.dest=dest;
this.sender=sender;
this.port=port;
this.buf=buf;
}
public IncomingQueueEntry(byte[] buf) {
this.buf=buf;
}
}
public class UcastReceiver implements Runnable {
boolean running=true;
Thread thread=null;
public void start() {
if(thread == null) {
thread=new Thread(this, "UDP.UcastReceiverThread");
thread.setDaemon(true);
running=true;
thread.start();
}
}
public void stop() {
Thread tmp;
if(thread != null && thread.isAlive()) {
running=false;
tmp=thread;
thread=null;
closeSocket(); // this will cause the thread to break out of its loop
tmp.interrupt();
tmp=null;
}
thread=null;
}
public void run() {
DatagramPacket packet;
byte receive_buf[]=new byte[65535];
int len;
byte[] data, tmp;
InetAddress sender_addr;
int sender_port;
// moved out of loop to avoid excessive object creations (bela March 8 2001)
packet=new DatagramPacket(receive_buf, receive_buf.length);
while(running && thread != null && sock != null) {
try {
packet.setData(receive_buf, 0, receive_buf.length);
sock.receive(packet);
sender_addr=packet.getAddress();
sender_port=packet.getPort();
len=packet.getLength();
data=packet.getData();
if(log.isTraceEnabled())
log.trace(new StringBuffer("received (ucast) ").append(len).append(" bytes from ").
append(sender_addr).append(':').append(sender_port));
if(len > receive_buf.length) {
if(log.isErrorEnabled())
log.error("size of the received packet (" + len + ") is bigger than allocated buffer (" +
receive_buf.length + "): will not be able to handle packet. " +
"Use the FRAG protocol and make its frag_size lower than " + receive_buf.length);
}
if(use_incoming_packet_handler) {
tmp=new byte[len];
System.arraycopy(data, 0, tmp, 0, len);
incoming_queue.add(new IncomingQueueEntry(local_addr, sender_addr, sender_port, tmp));
}
else
handleIncomingUdpPacket(local_addr, sender_addr, sender_port, data);
}
catch(SocketException sock_ex) {
if(log.isDebugEnabled()) log.debug("unicast receiver socket is closed, exception=" + sock_ex);
break;
}
catch(InterruptedIOException io_ex) { // thread was interrupted
; // go back to top of loop, where we will terminate loop
}
catch(Throwable ex) {
if(log.isErrorEnabled())
log.error("[" + local_addr + "] failed receiving unicast packet", ex);
Util.sleep(100); // so we don't get into 100% cpu spinning (should NEVER happen !)
}
}
if(log.isDebugEnabled()) log.debug("unicast receiver thread terminated");
}
}
/**
* This thread fetches byte buffers from the packet_queue, converts them into messages and passes them up
* to the higher layer (done in handleIncomingUdpPacket()).
*/
class IncomingPacketHandler implements Runnable {
Thread t=null;
public void run() {
byte[] data;
IncomingQueueEntry entry;
while(incoming_queue != null && incoming_packet_handler != null) {
try {
entry=(IncomingQueueEntry)incoming_queue.remove();
data=entry.buf;
}
catch(QueueClosedException closed_ex) {
if(log.isDebugEnabled()) log.debug("packet_handler thread terminating");
break;
}
handleIncomingUdpPacket(entry.dest, entry.sender, entry.port, data);
}
}
void start() {
if(t == null || !t.isAlive()) {
t=new Thread(this, "UDP.IncomingPacketHandler thread");
t.setDaemon(true);
t.start();
}
}
void stop() {
if(incoming_queue != null)
incoming_queue.close(false); // should terminate the packet_handler thread too
t=null;
incoming_queue=null;
}
}
/**
* This thread fetches byte buffers from the outgoing_packet_queue, converts them into messages and sends them
* using the unicast or multicast socket
*/
class OutgoingPacketHandler implements Runnable {
Thread t=null;
byte[] buf;
DatagramPacket packet;
IpAddress dest;
public void run() {
Message msg;
while(outgoing_queue != null && outgoing_packet_handler != null) {
try {
msg=(Message)outgoing_queue.remove();
handleMessage(msg);
}
catch(QueueClosedException closed_ex) {
break;
}
catch(Throwable th) {
if(log.isErrorEnabled()) log.error("exception sending packet", th);
}
msg=null; // let's give the poor garbage collector a hand...
}
if(log.isTraceEnabled()) log.trace("packet_handler thread terminating");
}
protected void handleMessage(Message msg) throws Exception {
send(msg);
}
void start() {
if(t == null || !t.isAlive()) {
t=new Thread(this, "UDP.OutgoingPacketHandler thread");
t.setDaemon(true);
t.start();
}
}
void stop() {
if(outgoing_queue != null)
outgoing_queue.close(false); // should terminate the packet_handler thread too
t=null;
// outgoing_queue=null;
}
}
/**
* Bundles smaller messages into bigger ones. Collects messages in a list until
* messages of a total of max_bundle_size bytes have accumulated, or until
* max_bundle_timeout milliseconds have elapsed, whichever is first. Messages
* are unbundled at the receiver.
*/
class BundlingOutgoingPacketHandler extends OutgoingPacketHandler {
long total_bytes=0;
/** HashMap>. Keys are destinations, values are lists of Messages */
final HashMap msgs=new HashMap(11);
void start() {
super.start();
t.setName("UDP.BundlingOutgoingPacketHandler thread");
}
public void run() {
Message msg=null, leftover=null;
long start=0;
while(outgoing_queue != null) {
try {
total_bytes=0;
msg=leftover != null? leftover : (Message)outgoing_queue.remove(); // blocks until message is available
start=System.currentTimeMillis();
leftover=waitForMessagesToAccumulate(msg, outgoing_queue, max_bundle_size, start, max_bundle_timeout);
bundleAndSend(start);
}
catch(QueueClosedException closed_ex) {
break;
}
catch(Throwable th) {
if(log.isErrorEnabled()) log.error("exception sending packet", th);
}
}
bundleAndSend(start);
if(log.isTraceEnabled()) log.trace("packet_handler thread terminating");
}
/**
* Waits until max_size bytes have accumulated in the queue, or max_time milliseconds have elapsed.
* When a message cannot be added to the ready-to-send bundle, it is returned, so the caller can
* re-submit it again next time.
* @param m
* @param q
* @param max_size
* @param max_time
* @return
*/
Message waitForMessagesToAccumulate(Message m, Queue q, long max_size, long start_time, long max_time) {
Message msg, leftover=null;
boolean running=true, size_exceeded=false, time_reached=false;
long len, time_to_wait=max_time, waited_time=0;
while(running) {
try {
msg=m != null? m : (Message)q.remove(time_to_wait);
m=null; // necessary, otherwise we get 'm' again in subsequent iterations of the same loop !
len=msg.size();
checkLength(len);
waited_time=System.currentTimeMillis() - start_time;
time_to_wait=max_time - waited_time;
size_exceeded=total_bytes + len > max_size;
time_reached=time_to_wait <= 0;
if(size_exceeded) {
running=false;
leftover=msg;
}
else {
addMessage(msg);
total_bytes+=len;
if(time_reached)
running=false;
}
}
catch(TimeoutException timeout) {
waited_time=System.currentTimeMillis() - start_time;
time_reached=true;
break;
}
catch(QueueClosedException closed) {
break;
}
catch(Exception ex) {
log.error("failure in bundling", ex);
}
}
return leftover;
}
void checkLength(long len) throws Exception {
if(len > max_bundle_size)
throw new Exception("UDP.BundlingOutgoingPacketHandler.handleMessage(): message size (" + len +
") is greater than max bundling size (" + max_bundle_size + "). " +
"Set the fragmentation/bundle size in FRAG and UDP correctly");
}
void addMessage(Message msg) {
List tmp;
Address dst=msg.getDest();
synchronized(msgs) {
tmp=(List)msgs.get(dst);
if(tmp == null) {
tmp=new List();
msgs.put(dst, tmp);
}
tmp.add(msg);
}
}
private void bundleAndSend(long start_time) {
Map.Entry entry;
IpAddress dst;
Buffer buffer;
InetAddress addr;
int port;
List l;
long stop_time=System.currentTimeMillis();
synchronized(msgs) {
if(msgs.size() == 0)
return;
if(start_time == 0)
start_time=System.currentTimeMillis();
if(log.isTraceEnabled()) {
StringBuffer sb=new StringBuffer("sending ").append(numMsgs(msgs)).append(" msgs (");
sb.append(total_bytes).append(" bytes, ").append(stop_time-start_time).append("ms)");
sb.append(" to ").append(msgs.size()).append(" destination(s)");
if(msgs.size() > 1) sb.append(" (dests=").append(msgs.keySet()).append(")");
log.trace(sb.toString());
}
for(Iterator it=msgs.entrySet().iterator(); it.hasNext();) {
entry=(Map.Entry)it.next();
dst=(IpAddress)entry.getKey();
addr=dst.getIpAddress();
port=dst.getPort();
l=(List)entry.getValue();
try {
if(l.size() > 0) {
synchronized(out_stream) {
buffer=listToBuffer(l, dst);
doSend(buffer, addr, port);
}
}
}
catch(Exception e) {
if(log.isErrorEnabled()) log.error("exception sending msg (to dest=" + dst + ")", e);
}
}
msgs.clear();
}
}
private int numMsgs(HashMap map) {
Collection values=map.values();
List l;
int size=0;
for(Iterator it=values.iterator(); it.hasNext();) {
l=(List)it.next();
size+=l.size();
}
return size;
}
}
String dumpMessages(HashMap map) {
StringBuffer sb=new StringBuffer();
Map.Entry entry;
List l;
Object key;
if(map != null) {
synchronized(map) {
for(Iterator it=map.entrySet().iterator(); it.hasNext();) {
entry=(Map.Entry)it.next();
key=entry.getKey();
if(key == null)
key="null";
l=(List)entry.getValue();
sb.append(key).append(": ");
sb.append(l.size()).append(" msgs\n");
}
}
}
return sb.toString();
}
}