/**
*
*/
package net.varkhan.serv.p2p.message.transport;
import net.varkhan.base.management.report.JMXAverageMonitorReport;
import net.varkhan.serv.p2p.message.dispatch.MesgDispatcher;
import net.varkhan.serv.p2p.message.dispatch.MesgReceiver;
import net.varkhan.serv.p2p.message.dispatch.MesgSender;
import net.varkhan.serv.p2p.connect.PeerNode;
import net.varkhan.serv.p2p.message.PeerResolver;
import net.varkhan.serv.p2p.connect.PeerAddress;
import net.varkhan.serv.p2p.connect.transport.MTXAddress;
import net.varkhan.serv.p2p.message.*;
import net.varkhan.serv.p2p.message.protocol.BinaryEnvelope;
import net.varkhan.serv.p2p.message.protocol.BinaryPayload;
import net.varkhan.serv.p2p.connect.transport.UDPAddress;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.*;
import java.util.Enumeration;
/**
* <b>.</b>
* <p/>
* @author varkhan
* @date Nov 12, 2009
* @time 9:40:07 AM
*/
@SuppressWarnings( { "UnusedDeclaration" })
public class MTXTransport implements MesgTransport, MesgSender {
public static final Logger log=LogManager.getLogger(MTXTransport.class);
// Incoming packet queue size
public static final int QUEUE_SIZE =100;
// Default packet recv / send size
public static final int PACKET_SIZE =1024*16;
// Maximum data payload inside packets
public static final int MAX_DATA_SIZE=PACKET_SIZE-50;
// Maximum time spent waiting for message reception (5sec)
private static final int RECV_TIMEOUT =5000;
/** The MTX group address */
private InetAddress groupAddr;
/** The MTX group port */
private int groupPort;
private int timeout =RECV_TIMEOUT;
private int packetSize=PACKET_SIZE;
private int queueSize =QUEUE_SIZE;
private InetAddress localAddr;
protected MulticastSocket server=null;
protected PeerResolver resolver;
protected MesgDispatcher dispatcher;
protected PeerAddress local;
private JMXAverageMonitorReport stats;
private String name;
private volatile boolean run =false;
private volatile Thread thread=null;
/**
* Creates a Multicast PeerChannel, specifying incoming packet and queue size
*
* @param resolver the point name resolver
* @param dispatcher the message dispatcher
* @param localPoint the local point node
* @param localAddr the local address to bind to (or {@code null} if any)
* @param groupAddr the multicast group address
* @param groupPort the multicast group port number
* @param queueSize the size of the packet queue
* @param packetSize the size of outgoing/incoming packets
* @param stats activity statistics collector
* @throws java.net.SocketException if the UDP listening socket could not be created and bound
*/
public MTXTransport(
PeerResolver resolver, MesgDispatcher dispatcher,
PeerAddress localPoint, InetAddress localAddr,
InetAddress groupAddr, int groupPort,
int queueSize, int packetSize, JMXAverageMonitorReport stats) throws IOException {
this.resolver=resolver;
this.dispatcher=dispatcher;
this.local=localPoint;
this.stats=stats;
this.localAddr=localAddr;
this.groupAddr = groupAddr;
this.groupPort = groupPort;
if(log.isDebugEnabled()) log.debug("BIND MTX "+(localAddr==null?"0.0.0.0":localAddr.getHostAddress())+":"+groupPort);
server=new MulticastSocket(new InetSocketAddress(localAddr,groupPort));
server.setReuseAddress(true);
server.setSoTimeout(timeout);
// socket.joinGroup(groupAddr);
if(this.localAddr==null) {
Enumeration<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
if(ifaces.hasMoreElements()) server.setNetworkInterface(ifaces.nextElement());
}
else {
// This fails because Java sucks at networking
// server.setInterface(this.localAddr);
NetworkInterface localIface = NetworkInterface.getByInetAddress(this.localAddr);
if(localIface!=null) {
server.setNetworkInterface(localIface);
}
}
setQueueSize(queueSize);
setPacketSize(packetSize);
this.name = ((server.getLocalAddress()==null)?"0.0.0.0": server.getLocalAddress().getHostAddress())+
":"+server.getLocalPort()+
"@"+localPoint.name();
}
/**
* Creates a Multicast MesgChannel
*
* @param resolver the point name resolver
* @param dispatcher the message dispatcher
* @param localPoint the local point node
* @param localAddr the local address to bind to (or {@code null} if any)
* @param groupAddr the multicast group address
* @param groupPort the multicast group port number
* @param stats activity statistics collector
* @throws java.net.SocketException if the UDP listening socket could not be created and bound
*/
public MTXTransport(
PeerResolver resolver, MesgDispatcher dispatcher,
PeerAddress localPoint, InetAddress localAddr,
InetAddress groupAddr, int groupPort,
JMXAverageMonitorReport stats) throws IOException {
this(resolver,dispatcher,localPoint,localAddr,groupAddr,groupPort,QUEUE_SIZE,PACKET_SIZE,stats);
}
/**
* Creates a Multicast MesgChannel
*
* @param resolver the point name resolver
* @param dispatcher the message dispatcher
* @param localPoint the local point node
* @param groupAddr the multicast group address
* @param groupPort the multicast group port number
* @param stats activity statistics collector
* @throws java.net.SocketException if the UDP listening socket could not be created and bound
*/
public MTXTransport(
PeerResolver resolver, MesgDispatcher dispatcher,
PeerAddress localPoint, InetAddress groupAddr, int groupPort,
JMXAverageMonitorReport stats) throws IOException {
this(resolver,dispatcher,localPoint,null,groupAddr,groupPort,QUEUE_SIZE, PACKET_SIZE,stats);
}
/**
* Get the size of datagram packets emitted from or received by this server
*
* @return the size of datagram packets emitted from or received by this server
*/
public int getPacketSize() {
return packetSize;
}
/**
* Set the size of datagram packets emitted from or received by this server
*
* @param size the size of datagram packets emitted from or received by this server
* @throws java.net.SocketException if the underlying transport did not accept reconfiguration
*/
public void setPacketSize(int size) throws SocketException {
packetSize = size;
if (server!= null) {
server.setReceiveBufferSize(packetSize * queueSize);
server.setSendBufferSize(packetSize * queueSize);
}
}
/**
* Get the size of the message waiting queue
*
* @return the number of messages that can be queued in the reception buffer
*/
public int getQueueSize() {
return queueSize;
}
/**
* Set the size of the message waiting queue
*
* @param size the number of messages that can be queued in the reception buffer
* @throws java.net.SocketException if the underlying transport did not accept reconfiguration
*/
public void setQueueSize(int size) throws SocketException {
queueSize = size;
if (server!= null) {
server.setReceiveBufferSize(packetSize * queueSize);
server.setSendBufferSize(packetSize * queueSize);
}
}
/**
* Get the message reception timeout
*
* @return the maximum number of milliseconds the exchange will wait for the reception of a single message
*/
public int getTimeout() {
return timeout;
}
/**
* Set the message reception timeout
*
* @param delay the maximum number of milliseconds the exchange will wait for the reception of a single message
* @throws java.net.SocketException if the underlying transport did not accept reconfiguration
*/
public void setTimeout(int delay) throws SocketException {
timeout = delay;
if(server!=null) server.setSoTimeout(timeout);
}
/**
* Gets the local listening address for this exchange
*
* @return the local address this exchange listens on
*/
public InetAddress getAddress() {
return server.getLocalAddress();
}
/**
* Gets the local listening port for this exchange
*
* @return the local port this exchange listens on
*/
public int getPort() {
return server.getLocalPort();
}
public InetAddress getGroupAddr() {
return groupAddr;
}
public int getGroupPort() {
return groupPort;
}
/**********************************************************************************
** Lifecycle management
**/
@Override
public PeerAddress endpoint() {
return new MTXAddress() {
@Override
public InetAddress addrMTXgroup() {
return groupAddr;
}
@Override
public int portMTXgroup() {
return groupPort;
}
@Override
public String name() {
return name;
}
@Override
@SuppressWarnings("unchecked")
public <T> T as(Class<T> c) {
if(c.isAssignableFrom(UDPAddress.class)) return (T) this;
return null;
}
};
}
/**
* Indicates whether the exchange is started and able to handle messages
*
* @return {@code true} if the exchange transport layer is ready to handle outgoing messages,
* and the listening thread is handling incoming messages
*/
public boolean isStarted() {
return run && thread!=null && thread.isAlive();
}
/**
* Start the exchange
*
* @throws java.io.IOException if the exchange could not bind to an address
*/
public synchronized void start() throws IOException {
if(run || (thread!=null && thread.isAlive())) return;
if (log.isDebugEnabled()) {
log.debug(MTXTransport.class.getSimpleName()+": start "+name +
"(" + getAddress().getHostAddress() + ":" + getPort() +
"@" + groupAddr.getHostAddress()+":"+ groupPort + ")");
}
server.joinGroup(groupAddr);
thread = new Thread() {
public void run() { receive(); }
};
thread.setName(MTXTransport.class.getSimpleName()+"("+groupAddr.getHostAddress()+":"+ groupPort+")");
thread.setDaemon(true);
run = true;
thread.start();
}
/**
* Stop the exchange
*
* @throws java.io.IOException if the exchange could not free resources
*/
public synchronized void stop() throws IOException {
run = false;
Thread t=thread;
if(t==null) return;
server.leaveGroup(groupAddr);
thread = null;
t.interrupt();
try {
t.join(2*timeout);
}
catch(InterruptedException e) {
// ignore
}
if (log.isDebugEnabled()) {
log.debug(MTXTransport.class.getSimpleName()+": stop "+name +
"(" + getAddress().getHostAddress() + ":" + getPort() +
"@" + groupAddr.getHostAddress()+":"+ groupPort + ")");
}
}
public void receive() {
DatagramPacket pakt = new DatagramPacket(new byte[packetSize], packetSize);
// Main message listening loop
while(run) {
// Waiting for reception of a message
try {
server.receive(pakt);
}
catch(SocketTimeoutException e) {
// Ignore this, as receive naturally times out because we set SO_TIMEOUT
continue;
}
catch(InterruptedIOException e) {
if(!run) return;
stats.inc(MTXTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]");
if(log.isDebugEnabled()) log.debug("RECV mtx://"+groupAddr+":"+groupPort+" interrupted",e);
continue;
}
catch(SocketException e) {
stats.inc(MTXTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]");
if(log.isDebugEnabled()) log.debug("RECV mtx://"+groupAddr+":"+groupPort+" protocol error",e);
continue;
}
catch(IOException e) {
stats.inc(MTXTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]");
if(log.isDebugEnabled()) log.debug("RECV mtx://"+groupAddr+":"+groupPort+" failed", e);
continue;
}
catch (Throwable e) {
stats.inc(MTXTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]");
log.error("Unexpected exception", e);
continue;
}
if (log.isDebugEnabled()) log.debug("RECV mtx://"+groupAddr+":"+groupPort+" receiving from "+pakt.getAddress().getHostAddress());
// Handle the message
try {
receive(pakt);
stats.inc(MTXTransport.class.getSimpleName()+".Recv.Success");
}
catch (IOException e) {
stats.inc(MTXTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]");
if (log.isDebugEnabled()) log.debug("RECV mtx://"+groupAddr+":"+groupPort+" failed to read data", e);
}
catch (Throwable e) {
stats.inc(MTXTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]");
log.error("Unexpected exception", e);
}
}
}
private void receive(DatagramPacket pakt) throws IOException {// Building received message
BinaryEnvelope recv = new BinaryEnvelope(pakt.getData(), pakt.getOffset(), pakt.getLength());
String method=recv.method();
MesgPayload mesg = recv.message();
long sequence=recv.sequence();
// Passing message to the dispatcher
switch(recv.type()) {
case BinaryEnvelope.PING: {
PeerAddress remote = resolver.update(recv.srcId(),mesg);
if(sequence>=0){
// Reply immediately, with decremented ttl
ping(local, remote, method, resolver.info(), sequence-1);
}
}
break;
case BinaryEnvelope.CALL: {
PeerAddress src = resolver.resolve(recv.srcId());
PeerAddress dst = resolver.resolve(recv.dstId());
if(sequence<0) {
dispatcher.call(src, dst, method, mesg, null, -1, null);
}
else {
BinaryPayload repl = new BinaryPayload();
dispatcher.call(src, dst, method, mesg, repl, sequence, this);
}
}
break;
case BinaryEnvelope.REPL: {
PeerAddress src = resolver.resolve(recv.srcId());
PeerAddress dst = resolver.resolve(recv.dstId());
dispatcher.repl(src, dst, method, mesg, sequence);
}
break;
}
}
/**********************************************************************************
** Message transmission handling
**/
public boolean call(PeerAddress src, PeerAddress dst, String method, MesgPayload message, MesgReceiver handler) throws IOException {
if(!run) {
stats.inc(MTXTransport.class.getSimpleName()+".Call.NotRunning");
return false;
}
// We only know how to send messages from the local addr
if(!local.equals(src)) {
stats.inc(MTXTransport.class.getSimpleName()+".Call.NotLocal");
return false;
}
// We do not handle messages bigger than the packet size
if(message.getLength()>MAX_DATA_SIZE) {
stats.inc(MTXTransport.class.getSimpleName()+".Call.Oversize");
return false;
}
MTXAddress node = dst.as(MTXAddress.class);
if(node==null) {
stats.inc(MTXTransport.class.getSimpleName()+".Call.Protocol.Unsupported");
return false;
}
InetAddress addr=node.addrMTXgroup();
int port=node.portMTXgroup();
if(!groupAddr.equals(addr) || groupPort!=port) {
stats.inc(MTXTransport.class.getSimpleName()+".Call.Address.Unsupported");
return false;
}
long sequence = -1;
if(handler!=null) sequence = dispatcher.register(handler);
if(log.isDebugEnabled()) log.debug("CALL mtx://"+addr.getHostAddress()+":"+port+"/"+method+"#"+sequence);
BinaryEnvelope send = new BinaryEnvelope(BinaryEnvelope.REPL, src.name(),"",dst.name(),sequence,message);
ByteArrayOutputStream data = new ByteArrayOutputStream();
try {
send.send(data);
byte[] buf = data.toByteArray();
server.send(new DatagramPacket(buf,0,buf.length, addr, port));
}
catch(IOException e) {
if(log.isInfoEnabled()) log.info("CALL mtx://"+addr.getHostAddress()+":"+port+"/"+method+"#"+sequence+" failed", e);
stats.inc(MTXTransport.class.getSimpleName()+".Call.error["+e.getClass().getSimpleName()+"]");
throw e;
}
stats.inc(MTXTransport.class.getSimpleName()+".Call.Success");
return true;
}
public boolean repl(PeerAddress src, PeerAddress dst, String method, MesgPayload message, long sequence) throws IOException {
if(!run) {
stats.inc(MTXTransport.class.getSimpleName()+".Repl.NotRunning");
return false;
}
// We only know how to send messages from the local addr
if(!local.equals(src)) {
stats.inc(MTXTransport.class.getSimpleName()+".Repl.NotLocal");
return false;
}
// We do not handle messages bigger than the packet size
if(message.getLength()>MAX_DATA_SIZE) {
stats.inc(MTXTransport.class.getSimpleName()+".Repl.Oversize");
return false;
}
MTXAddress node = dst.as(MTXAddress.class);
if(node==null) {
stats.inc(MTXTransport.class.getSimpleName()+".Repl.Protocol.Unsupported");
return false;
}
InetAddress addr=node.addrMTXgroup();
int port=node.portMTXgroup();
if(!groupAddr.equals(addr) || groupPort!=port) {
stats.inc(MTXTransport.class.getSimpleName()+".Repl.Address.Unsupported");
return false;
}
if(log.isDebugEnabled()) log.debug("REPL mtx://"+addr.getHostAddress()+":"+port+"/"+method+"#"+sequence);
BinaryEnvelope send = new BinaryEnvelope(BinaryEnvelope.REPL, src.name(),method,dst.name(),sequence,message);
ByteArrayOutputStream data = new ByteArrayOutputStream();
try {
send.send(data);
byte[] buf = data.toByteArray();
server.send(new DatagramPacket(buf,0,buf.length, addr, port));
}
catch(IOException e) {
if(log.isInfoEnabled()) log.info("REPL mtx://"+addr.getHostAddress()+":"+port+"/"+method+"#"+sequence+" failed", e);
stats.inc(MTXTransport.class.getSimpleName()+".Repl.error["+e.getClass().getSimpleName()+"]");
throw e;
}
stats.inc(MTXTransport.class.getSimpleName()+".Repl.Success");
return true;
}
public boolean ping(PeerAddress src, PeerAddress dst, String method, MesgPayload message, long sequence) throws IOException {
if(!run) {
stats.inc(MTXTransport.class.getSimpleName()+".Ping.NotRunning");
return false;
}
// We only know how to send messages from the local addr
if(!local.equals(src)) {
stats.inc(MTXTransport.class.getSimpleName()+".Ping.NotLocal");
if (log.isDebugEnabled()) log.debug("PING mtx://"+groupAddr+":"+groupPort+" not from local "+src.name());
return false;
}
// We do not handle messages bigger than the packet size
if(message.getLength()>MAX_DATA_SIZE) {
stats.inc(MTXTransport.class.getSimpleName()+".Ping.Oversize");
if (log.isDebugEnabled()) log.debug("PING mtx://"+groupAddr+":"+groupPort+" oversize message "+message.getLength());
return false;
}
MTXAddress node = dst.as(MTXAddress.class);
if(node==null) {
stats.inc(MTXTransport.class.getSimpleName()+".Ping.Protocol.Unsupported");
if (log.isDebugEnabled()) log.debug("PING mtx://"+groupAddr+":"+groupPort+" not to multicast "+dst.name());
return false;
}
InetAddress addr=node.addrMTXgroup();
int port=node.portMTXgroup();
if(!groupAddr.equals(addr) || groupPort!=port) {
stats.inc(MTXTransport.class.getSimpleName()+".Ping.Address.Unsupported");
if (log.isDebugEnabled()) log.debug("PING mtx://"+groupAddr+":"+groupPort+" not to group "+dst.name());
return false;
}
if(log.isDebugEnabled()) log.debug("PING mtx://"+addr.getHostAddress()+":"+port+"/"+method+"#"+sequence);
BinaryEnvelope send = new BinaryEnvelope(BinaryEnvelope.PING, src.name(), method,dst.name(),sequence,message);
ByteArrayOutputStream data = new ByteArrayOutputStream();
try {
send.send(data);
byte[] buf = data.toByteArray();
server.send(new DatagramPacket(buf,0,buf.length, addr, port));
}
catch(IOException e) {
if(log.isInfoEnabled()) log.info("PING mtx://"+addr.getHostAddress()+":"+port+"/"+method+"#"+sequence+" failed", e);
stats.inc(MTXTransport.class.getSimpleName()+".Ping.error["+e.getClass().getSimpleName()+"]");
throw e;
}
stats.inc(MTXTransport.class.getSimpleName()+".Ping.Success");
return true;
}
public void send(PeerAddress src, PeerAddress dst, String method, MesgPayload message, long sequence) throws IOException {
// Send reply
PeerNode node = (PeerNode)dst;
node.repl(method, message, sequence);
}
}