com.robonobo.eon.EONManager.java Source code

Java tutorial

Introduction

Here is the source code for com.robonobo.eon.EONManager.java

Source

package com.robonobo.eon;

/*
 * Eye-Of-Needle
 * Copyright (C) 2008 Will Morton (macavity@well.com) & Ray Hilton (ray@wirestorm.net)
    
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
    
 * This program 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 General Public License for more details.
    
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

import java.io.IOException;
import java.net.ConnectException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.PortUnreachableException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.robonobo.common.concurrent.StartStopable;
import com.robonobo.common.util.Modulo;

public class EONManager implements StartStopable {
    private static final int SOCKET_BUFFER_SIZE = 64 * 1024;
    public static final int MAX_PKT_SIZE = 8192;
    Log log;
    String instanceName;
    InetSocketAddress localEP;
    DatagramChannel chan;
    boolean sockOK;
    ReceiverThread recvThread;
    PktSender pktSender;
    ConnectionHolder conns;
    ScheduledThreadPoolExecutor executor;
    Modulo mod;
    Date uploadCheckLastBase = new Date();
    int bytesUploadedSinceBase = 0;
    final boolean debugLogging;

    public static InetAddress getWildcardAddress() {
        try {
            return Inet4Address.getByName("0.0.0.0");
        } catch (UnknownHostException e) {
            // Can't happen
            throw new RuntimeException(e);
        }
    }

    public EONManager(String instanceName, ScheduledThreadPoolExecutor executor, int port) throws EONException {
        this(instanceName, executor, new InetSocketAddress(port));
    }

    public EONManager(String instanceName, ScheduledThreadPoolExecutor executor) throws EONException {
        this(instanceName, executor, null);
    }

    public EONManager(String instanceName, ScheduledThreadPoolExecutor executor, InetSocketAddress localEP)
            throws EONException {
        this.instanceName = instanceName;
        log = getLogger(getClass());
        debugLogging = log.isDebugEnabled();
        this.executor = executor;
        try {
            chan = DatagramChannel.open();
            chan.socket().setReceiveBufferSize(SOCKET_BUFFER_SIZE);
            chan.socket().setSendBufferSize(SOCKET_BUFFER_SIZE);
            chan.socket().bind(localEP);
            // Re-grab the value to see what we ended up on
            this.localEP = (InetSocketAddress) chan.socket().getLocalSocketAddress();
        } catch (IOException e) {
            throw new EONException("Unable to construct udp socket on " + localEP.toString(), e);
        }
        sockOK = true;
        pktSender = new PktSender(chan);
        recvThread = null;
        conns = new ConnectionHolder(this);
        mod = new Modulo((long) Integer.MAX_VALUE + 1);
        log.debug("EONManager created on endpoint " + this.localEP.getAddress().getHostAddress() + ":"
                + this.localEP.getPort());
    }

    public boolean isRunning() {
        return sockOK;
    }

    public void start() throws EONException {
        recvThread = new ReceiverThread();
        recvThread.start();
        pktSender.start();
    }

    public void stop() {
        log.debug("Stopping EONManager");
        conns.closeAllConns(30000);
        pktSender.stop();
        recvThread.terminate();
        try {
            chan.close();
        } catch (Exception ignore) {
        }
        sockOK = false;
    }

    public Log getLogger(Class callerClass) {
        return LogFactory.getLog(callerClass.getName() + ".INSTANCE." + instanceName);
    }

    public DEONConnection createDEONConnection() {
        return new DEONConnection(this);
    }

    public SEONConnection createSEONConnection() throws EONException {
        return new SEONConnection(this);
    }

    public void sendNATSeed(InetSocketAddress remoteEP) throws EONException {
        DEONPacket seed = new DEONPacket(new EonSocketAddress(localEP, 0), new EonSocketAddress(remoteEP, 0), null);
        sendPktImmediate(seed);
    }

    public ScheduledThreadPoolExecutor getExecutor() {
        return executor;
    }

    public boolean requestPort(int localEONPort, EONConnection thisConn)
            throws EONException, IllegalArgumentException {
        return requestPort(localEONPort, new EonSocketAddress(getWildcardAddress(), 1, 1), thisConn);
    }

    public boolean requestPort(int localEONPort, EonSocketAddress addressMask, EONConnection thisConn)
            throws EONException {
        if (!sockOK)
            throw new EONException("No socket available");
        return conns.requestPort(localEONPort, addressMask, thisConn);
    }

    public int getPort(EONConnection thisConn) throws IllegalArgumentException, EONException {
        return getPort(new EonSocketAddress(getWildcardAddress(), 1, 1), thisConn);
    }

    public int getPort(EonSocketAddress addressMask, EONConnection thisConn)
            throws IllegalArgumentException, EONException {
        if (!sockOK)
            throw new EONException("No socket available");
        return conns.getPort(addressMask, thisConn);
    }

    public void returnPort(int localEONPort, EONConnection thisConn) throws EONException, IllegalArgumentException {
        returnPort(localEONPort, new EonSocketAddress(getWildcardAddress(), 1, 1), thisConn);
    }

    public void returnPort(int localEONPort, EonSocketAddress addressMask, EONConnection thisConn)
            throws EONException, IllegalArgumentException {
        conns.returnPort(localEONPort, addressMask, thisConn);
    }

    final void sendPktImmediate(EONPacket pkt) {
        pktSender.sendPktImmediate(pkt);
    }

    final void haveDataToSend(EONConnection conn) {
        pktSender.haveDataToSend(conn);
    }

    public int getLowestMaxObservedRtt(SEONConnection exceptConn) {
        return conns.getLowestMaxObservedRtt(exceptConn);
    }

    // Note: If we have allowed the system to choose our IP address and/or
    // port, this will
    // not be set until Start() is called
    public InetSocketAddress getLocalSocketAddress() throws EONException {
        if (sockOK)
            return localEP;
        else
            throw new EONException("Underlying socket not ready");
    }

    /**
     * Pass -1 to specify no limit
     */
    public void setMaxOutboundBps(int maxBps) {
        pktSender.setMaxBps(maxBps);
    }

    final int getMaxOutboundBps() {
        return pktSender.getMaxBps();
    }

    private class ReceiverThread extends Thread {
        boolean terminated = false;

        public ReceiverThread() {
            super();
            setName("EON-Recv");
        }

        public void run() {
            EONPacket thisPacket;
            log.debug("EONManagerThread running");
            // Loop, picking up packets and passing them off to connections as
            // necessary
            while (true) {
                // Awooga! Awooga! Inner loop alert!
                InetSocketAddress remoteEP = null;
                try {
                    // Allocate a new buffer every time, as it means we don't
                    // have to copy it when handling gets passed to another
                    // thread, and it's cheaper to keep allocating same-sized
                    // buffers
                    ByteBuffer recvBuf = ByteBuffer.allocate(MAX_PKT_SIZE);
                    // Pick up next packet (will block here if no packets)
                    remoteEP = (InetSocketAddress) chan.receive(recvBuf);
                    recvBuf.flip();
                    thisPacket = EONPacket.parse(recvBuf);
                    if (thisPacket == null) {
                        // Duff packet. Discard and move on.
                        log.error("Error parsing packet from " + remoteEP.getAddress().toString() + ":"
                                + remoteEP.getPort());
                        continue;
                    }
                    // Set the IP data on the packet
                    thisPacket.getSourceSocketAddress().setAddress(remoteEP.getAddress());
                    thisPacket.getSourceSocketAddress().setUdpPort(remoteEP.getPort());
                    InetSocketAddress locSockAddr = getLocalSocketAddress();
                    thisPacket.getDestSocketAddress().setAddress(locSockAddr.getAddress());
                    thisPacket.getDestSocketAddress().setUdpPort(locSockAddr.getPort());
                    if (debugLogging) {
                        String logStr = "r " + thisPacket.toString();
                        log.debug(logStr);
                    }
                    handlePacket(thisPacket);
                } catch (EONException e) {
                    if (terminated)
                        return;
                    log.error("Caught EONException in Eon Mgr thread", e);
                    continue;
                } catch (ConnectException e) {
                    // We're exiting
                    return;
                } catch (PortUnreachableException e) {
                    conns.killAllConnsAssociatedWith(remoteEP);
                    continue;
                } catch (SocketException e) {
                    if (!terminated)
                        log.error("Caught SocketException when waiting for packets", e);
                    return;
                } catch (IOException e) {
                    if (!terminated)
                        log.error("IOException when attempting to receive UDP Packet", e);
                    return;
                } catch (Exception e) {
                    log.error("EONMgr caught " + e.getClass().getName(), e);
                    continue;
                }
            }
        }

        public void terminate() {
            // set the terminated flag
            terminated = true;

            // close socket, forces receive() to stop blocking
            try {
                chan.close();
            } catch (IOException ignore) {
            }

            // interrupt the thread
            interrupt();
        }

        public void handlePacket(EONPacket thisPacket) throws EONException {
            // If this is sent to EON port 0, it is a NATseed - discard
            if (thisPacket.getDestSocketAddress().getEonPort() == 0)
                return;
            EONConnection thisConn = conns.getLocalConnForIncoming(thisPacket.getDestSocketAddress(),
                    thisPacket.getSourceSocketAddress());
            if (thisConn != null)
                thisConn.receivePacket(thisPacket);
            else
                handleUnwantedPacket(thisPacket);
        }

        private void handleUnwantedPacket(EONPacket thisPacket) {
            // No connection to take this packet
            if (thisPacket.getProtocol() == EONPacket.EON_PROTOCOL_SEON) {
                SEONPacket sPkt = (SEONPacket) thisPacket;
                sendRstPacketForBadPkt(sPkt);
            }
            // Just drop the packet and move on
            log.warn("EON Packet dropped: " + thisPacket);
        }

        private void sendRstPacketForBadPkt(SEONPacket badPkt) {
            if (badPkt.isRST())
                return;
            SEONPacket rstPacket = new SEONPacket(badPkt.getDestSocketAddress(), badPkt.getSourceSocketAddress(),
                    null);
            rstPacket.setRST(true);
            if (badPkt.isACK())
                rstPacket.setSequenceNumber(badPkt.getAckNumber());
            else {
                try {
                    rstPacket.setSequenceNumber(0);
                    rstPacket.setAckNumber(mod.add(badPkt.getSequenceNumber(),
                            (long) ((badPkt.getPayload() == null) ? 0 : badPkt.getPayload().remaining())));
                    rstPacket.setACK(true);
                } catch (IllegalArgumentException e) {
                    log.error("Unable to perform modulo operation", e);
                }
            }
            sendPktImmediate(rstPacket);
        }

    }
}