/** * */ 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.connect.PeerNode; import net.varkhan.serv.p2p.message.PeerResolver; import net.varkhan.serv.p2p.connect.transport.UDPAddress; import net.varkhan.serv.p2p.message.dispatch.MesgReceiver; import net.varkhan.serv.p2p.message.dispatch.MesgSender; import net.varkhan.serv.p2p.connect.PeerAddress; import net.varkhan.serv.p2p.message.*; import net.varkhan.serv.p2p.message.protocol.BinaryEnvelope; import net.varkhan.serv.p2p.message.protocol.BinaryPayload; 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.*; /** * <b>.</b> * <p/> * @author varkhan * @date Nov 12, 2009 * @time 6:52:29 AM */ @SuppressWarnings( { "UnusedDeclaration" }) public class UDPTransport implements MesgTransport, MesgSender { public static final Logger log=LogManager.getLogger(UDPTransport.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 public static final int RECV_TIMEOUT =500; protected int timeout =RECV_TIMEOUT; protected int packetSize=PACKET_SIZE; protected int queueSize =QUEUE_SIZE; protected InetAddress localAddr; protected DatagramSocket server=null; protected final PeerResolver resolver; protected final MesgDispatcher dispatcher; protected final PeerAddress local; private final JMXAverageMonitorReport stats; private final String name; private volatile boolean run =false; private volatile Thread thread=null; /** * Create a UDP 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 localPort the local port number to bind to (or {@code 0} if any) * @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 UDPTransport( PeerResolver resolver, MesgDispatcher dispatcher, PeerAddress localPoint, InetAddress localAddr, int localPort, int queueSize, int packetSize, JMXAverageMonitorReport stats) throws SocketException { this.resolver=resolver; this.dispatcher=dispatcher; this.local=localPoint; this.stats=stats; this.localAddr=localAddr; if (this.localAddr == null) { if(log.isDebugEnabled()) log.debug("BIND UDP 0.0.0.0:"+localPort); server= new DatagramSocket(localPort); } else { if(log.isDebugEnabled()) log.debug("BIND UDP "+this.localAddr.getHostAddress()+":"+localPort); server= new DatagramSocket(localPort, this.localAddr); } server.setBroadcast(true); server.setReuseAddress(true); server.setSoTimeout(timeout); setPacketSize(packetSize); setQueueSize(queueSize); this.name = ((server.getLocalAddress()==null)?"0.0.0.0": server.getLocalAddress().getHostAddress())+ ":"+server.getLocalPort()+ "@"+localPoint.name(); } /** * Create a UDP WeaveChannel. * * @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 localPort the local port number to bind to (or {@code 0} if any) * @param stats activity statistics collector * @throws java.net.SocketException if the UDP listening socket could not be created and bound */ public UDPTransport( PeerResolver resolver, MesgDispatcher dispatcher, PeerAddress localPoint, InetAddress localAddr, int localPort, JMXAverageMonitorReport stats) throws SocketException { this(resolver, dispatcher, localPoint,localAddr,localPort,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 channel 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 channel 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 channel * * @return the local address this channel listens on */ public InetAddress getAddress() { return server.getLocalAddress(); } /** * Gets the local listening port for this channel * * @return the local port this channel listens on */ public int getPort() { return server.getLocalPort(); } /********************************************************************************** ** Life-cycle management **/ @Override public UDPAddress endpoint() { return new UDPAddress() { @Override public InetAddress addrUDP() { return server.getLocalAddress(); } @Override public int portUDP() { return server.getLocalPort(); } @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 channel is started and able to handle messages * * @return {@code true} if the channel 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 channel. * * @throws java.io.IOException if the channel could not bind to an address */ public synchronized void start() throws IOException { if(run || (thread!=null && thread.isAlive())) return; if (log.isDebugEnabled()) { log.debug(UDPTransport.class.getSimpleName()+": start "+name + "(" + server.getLocalAddress().getHostAddress() + ":" + server.getLocalPort() + ")"); } thread = new Thread() { public void run() { serverLoop(); } }; thread.setName(UDPTransport.class.getSimpleName()+"("+server.getLocalAddress().getHostAddress()+":"+server.getLocalPort()+")"); thread.setDaemon(true); run = true; thread.start(); } /** * Stop the channel. * * @throws java.io.IOException if the channel could not free resources */ public synchronized void stop() throws IOException { run = false; Thread t=thread; if(t==null) return; thread = null; t.interrupt(); try { t.join(2*timeout); } catch(InterruptedException e) { // ignore } log.debug(UDPTransport.class.getSimpleName()+": stop "+name + "(" + server.getLocalAddress().getHostAddress() + ":" + server.getLocalPort() + ")"); } private void serverLoop() { 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) { stats.inc(UDPTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]"); continue; } catch(InterruptedIOException e) { if(!run) return; stats.inc(UDPTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]"); continue; } catch(SocketException e) { stats.inc(UDPTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]"); continue; } catch(IOException e) { stats.inc(UDPTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]"); if (log.isDebugEnabled()) { log.debug("Receive on socket failed", e); } continue; } catch (Throwable e) { stats.inc(UDPTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]"); log.error("Unexpected exception", e); continue; } if (log.isDebugEnabled()) { log.debug("Receiving a message from "+pakt.getAddress().getHostAddress()); } // Handle the message try { receive(pakt); stats.inc(UDPTransport.class.getSimpleName()+".Recv.Success"); } catch (IOException e) { stats.inc(UDPTransport.class.getSimpleName()+".Recv.error["+e.getClass().getSimpleName()+"]"); if (log.isDebugEnabled()) { log.debug("Data read on socket failed", e); } } catch (Throwable e) { stats.inc(UDPTransport.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(UDPTransport.class.getSimpleName()+".Call.NotRunning"); return false; } // We only know how to send messages from the local addr if(!local.equals(src)) { stats.inc(UDPTransport.class.getSimpleName()+".Call.NotLocal"); return false; } // We do not handle messages bigger than the packet size if(message.getLength()>MAX_DATA_SIZE) { stats.inc(UDPTransport.class.getSimpleName()+".Call.Oversize"); return false; } UDPAddress node = dst.as(UDPAddress.class); if(node==null) { stats.inc(UDPTransport.class.getSimpleName()+".Call.Protocol.Unsupported"); return false; } InetAddress addr=node.addrUDP(); int port=node.portUDP(); long sequence = -1; if(handler!=null) sequence = dispatcher.register(handler); if(log.isDebugEnabled()) log.debug("CALL udp://"+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 udp://"+addr.getHostAddress()+":"+port+"/"+method+"#"+sequence+" failed", e); stats.inc(UDPTransport.class.getSimpleName()+".Call.error["+e.getClass().getSimpleName()+"]"); throw e; } stats.inc(UDPTransport.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(UDPTransport.class.getSimpleName()+".Repl.NotRunning"); return false; } // We only know how to send messages from the local addr if(!local.equals(src)) { stats.inc(UDPTransport.class.getSimpleName()+".Repl.NotLocal"); return false; } // We do not handle messages bigger than the packet size if(message.getLength()>MAX_DATA_SIZE) { stats.inc(UDPTransport.class.getSimpleName()+".Repl.Oversize"); return false; } UDPAddress node = dst.as(UDPAddress.class); if(node==null) { stats.inc(UDPTransport.class.getSimpleName()+".Repl.Protocol.Unsupported"); return false; } InetAddress addr=node.addrUDP(); int port=node.portUDP(); if(log.isDebugEnabled()) log.debug("REPL udp://"+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 udp://"+addr.getHostAddress()+":"+port+"/"+method+"#"+sequence+" failed", e); stats.inc(UDPTransport.class.getSimpleName()+".Repl.error["+e.getClass().getSimpleName()+"]"); throw e; } stats.inc(UDPTransport.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(UDPTransport.class.getSimpleName()+".Ping.NotRunning"); return false; } // We only know how to send messages from the local addr if(!local.equals(src)) { stats.inc(UDPTransport.class.getSimpleName()+".Ping.NotLocal"); return false; } // We do not handle messages bigger than the packet size if(message.getLength()>MAX_DATA_SIZE) { stats.inc(UDPTransport.class.getSimpleName()+".Ping.Oversize"); return false; } UDPAddress node = dst.as(UDPAddress.class); if(node==null) { stats.inc(UDPTransport.class.getSimpleName()+".Ping.Protocol.Unsupported"); return false; } InetAddress addr=node.addrUDP(); int port=node.portUDP(); if(log.isDebugEnabled()) log.debug("PING udp://"+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 udp://"+addr.getHostAddress()+":"+port+"/"+method+"#"+sequence+" failed", e); stats.inc(UDPTransport.class.getSimpleName()+".Ping.error["+e.getClass().getSimpleName()+"]"); throw e; } stats.inc(UDPTransport.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); } }