net.jenet.Host.java Source code

Java tutorial

Introduction

Here is the source code for net.jenet.Host.java

Source

/*
 * Copyright (c) 2005 Dizan Vasquez
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package net.jenet;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This is the base class used for UDP packet communications.
 * 
 * @author Dizan Vasquez
 */
public class Host {

    private static Log LOG = LogFactory.getLog(Host.class);

    protected static final short WAIT_RECEIVE = 1;

    protected static final short WAIT_SEND = 2;

    protected static final short WAIT_NONE = 4;

    protected static final short WAIT_ERROR = 8;

    protected InetSocketAddress address;

    protected int bandwidthThrottleEpoch;

    protected int bufferCount;

    protected ByteBuffer buffers;

    protected ConcurrentLinkedQueue<Command> commands = new ConcurrentLinkedQueue<Command>();

    protected DatagramChannel communicationChannel;

    protected Selector communicationSelector;

    protected PropertiesConfiguration configuration;

    protected int incomingBandwidth;

    protected Peer lastServicedPeer;

    protected int maxConnections;

    protected short mtu;

    protected int outgoingBandwidth;

    protected HashMap<Short, Peer> peers = new HashMap<Short, Peer>();

    protected boolean recalculateBandwithLimits;

    protected InetSocketAddress receivedAddress;

    protected ByteBuffer receivedData;

    protected long timeBase;

    protected int timeCurrent;

    /**
     * Creates a new <code>Host</code> object. If the incoming/outgoing
     * bandwidths are not bounded, the host will drop unreliable packets in
     * order to keep them below the fixed limits. This parameter also influences
     * the number and size of the reliable packets that may be handled at once.
     * 
     * @param address
     *            The to bind this host to (<emph>ie</emph> the address at
     *            which other peers may connect to this one) or
     *            <code>0</code> to get a system-assigned address.
     * @param maxConnections
     *            The maximum number of peers/connections that this host will be
     *            able to connect to.
     * @param incomingBandwith
     *            The maximum incoming bandwidth in bytes/second (0 =
     *            unbounded).
     * @param outgoingBandwith
     *            The maximum outgoing bandwidth in bytes/second (0 =
     *            unbounded).
     * @throws IOException
     *             if it can not bind the port.
     * @throws ConfigurationException
     *             if the file enet.properties is not in the path
     */
    public Host(InetSocketAddress address, int maxConnections, int incomingBandwith, int outgoingBandwith)
            throws IOException, ConfigurationException {
        super();
        communicationChannel = DatagramChannel.open();
        communicationChannel.configureBlocking(false);
        communicationChannel.socket().bind(address);
        communicationSelector = Selector.open();
        communicationChannel.register(communicationSelector, SelectionKey.OP_READ);
        LOG.debug("Host bound to address: " + address);
        this.address = (InetSocketAddress) communicationChannel.socket().getLocalSocketAddress();
        initHost(maxConnections, incomingBandwith, outgoingBandwith);
    }

    short assignPeerID(Peer peer) {
        for (short peerID = 0; peerID < maxConnections; peerID++)
            if (!peers.containsKey(peerID)) {
                peer.setIncomingPeerID(peerID);
                peers.put(peerID, peer);
                return peerID;
            }
        return -1;
    }

    /**
     * Adjusts the incoming/outgoing bandwidths for this host.
     * 
     * @see #Host
     * @param incomingBandwidth
     *            The maximum incoming bandwidth in bytes/secod (0 = unbounded).
     * @param outgoingBandwidth
     *            The maximum outgoing bandwidth in bytes/secod (0 = unbounded).
     */
    public void bandwidthLimit(int incomingBandwidth, int outgoingBandwidth) {
        this.incomingBandwidth = incomingBandwidth;
        this.outgoingBandwidth = outgoingBandwidth;
        recalculateBandwithLimits = true;
    }

    void bandwidthThrottle() {
        int timeCurrent = getTime();
        int elapsedTime = timeCurrent - bandwidthThrottleEpoch;
        int peersTotal = 0;
        int dataTotal = 0;
        int peersRemaining = 0;
        int bandwidth = 0;
        int throttle = 0;
        int bandwidthLimit = 0;
        boolean needsAdjustment = false;
        BandwidthLimit command = new BandwidthLimit();

        if (elapsedTime < configuration.getInt("ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL"))
            return;

        for (Peer peer : peers.values())
            if (peer.isConnected()) {
                ++peersTotal;
                dataTotal += peer.getOutgoingDataTotal();
            }

        if (peersTotal == 0)
            return;

        peersRemaining = peersTotal;
        needsAdjustment = true;

        if (outgoingBandwidth == 0)
            bandwidth = 0;
        else
            bandwidth = outgoingBandwidth * elapsedTime / 1000;

        while (peersRemaining > 0 && needsAdjustment) {
            needsAdjustment = false;
            if (dataTotal < bandwidth)
                throttle = bandwidth * configuration.getInt("ENET_PEER_PACKET_THROTTLE_SCALE") / dataTotal;

            for (Peer peer : peers.values()) {
                int peerBandwidth;

                if (!peer.isConnected() || peer.getIncomingBandwidth() == 0
                        || peer.getOutgoingBandwidthThrottleEpoch() == timeCurrent)
                    continue;

                peerBandwidth = peer.getIncomingBandwidth() * elapsedTime / 1000;

                if (throttle * peer.getOutgoingDataTotal()
                        / configuration.getInt("ENET_PEER_PACKET_THROTTLE_SCALE") >= peerBandwidth)
                    continue;

                peer.setPacketThrottleLimit(peerBandwidth * configuration.getInt("ENET_PEER_PACKET_THROTTLE_SCALE")
                        / peer.getOutgoingDataTotal());

                if (peer.getPacketThrottleLimit() == 0)
                    peer.setPacketThrottleLimit(1);

                if (peer.getPacketThrottle() > peer.getPacketThrottleLimit())
                    peer.setPacketThrottle(peer.getPacketThrottleLimit());

                peer.setOutgoingBandwidthThrottleEpoch(timeCurrent);

                needsAdjustment = true;

                --peersRemaining;

                bandwidth -= peerBandwidth;
                dataTotal -= peerBandwidth;
            }
        }

        if (peersRemaining > 0) {
            for (Peer peer : peers.values()) {
                if (!peer.isConnected() || peer.getOutgoingBandwidthThrottleEpoch() == timeCurrent)
                    continue;

                peer.setPacketThrottleLimit(throttle);

                if (peer.getPacketThrottle() > peer.getPacketThrottleLimit())
                    peer.setPacketThrottle(peer.getPacketThrottleLimit());
            }
        }

        if (recalculateBandwithLimits) {
            recalculateBandwithLimits = false;

            peersRemaining = peersTotal;

            bandwidth = incomingBandwidth;
            needsAdjustment = true;

            if (bandwidth == 0)
                bandwidthLimit = 0;
            else
                while (peersRemaining > 0 && needsAdjustment) {
                    for (Peer peer : peers.values()) {
                        if (!peer.isConnected() || peer.getIncomingBandwidthThrottleEpoch() == timeCurrent)
                            continue;

                        if (peer.getOutgoingBandwidth() > 0
                                && bandwidthLimit > peer.getIncomingBandwidthThrottleEpoch())
                            continue;

                        peer.setIncomingBandwidthThrottleEpoch(timeCurrent);

                        needsAdjustment = true;
                        --peersRemaining;
                        bandwidth -= peer.getOutgoingBandwidth();
                    }
                }

            for (Peer peer : peers.values()) {
                if (!peer.isConnected())
                    continue;

                command.getHeader().setChannelID((byte) 0xFF);
                command.getHeader().setFlags(Header.FLAG_ACKNOWLEDGE);
                command.setOutgoingBandwidth(outgoingBandwidth);

                if (peer.getIncomingBandwidthThrottleEpoch() == timeCurrent)
                    command.setIncomingBandwidth(peer.getOutgoingBandwidth());
                else
                    command.setIncomingBandwidth(bandwidthLimit);

                peer.queueOutgoingCommand(command, null, 0, (short) 0);
            }
        }

        bandwidthThrottleEpoch = timeCurrent;

        for (Peer peer : peers.values()) {
            peer.setIncomingDataTotal(0);
            peer.setOutgoingDataTotal(0);
        }
    }

    /**
     * Broadcasts a packet to all the connected peers.
     * 
     * @param channelID
     *            The channel on which to broadcast.
     * @param packet
     *            The packet to broadcast.
     * @throws IOException
     *             on failure
     */
    public void broadcast(byte channelID, Packet packet) throws IOException {
        for (Peer currentPeer : peers.values())
            if (currentPeer.isConnected())
                currentPeer.send(channelID, packet);
    }

    void buffer(IBufferable object) {
        LOG.debug(address + ": Buffering object: \n" + object);
        object.toBuffer(buffers);
        bufferCount++;
    }

    void buffer(OutgoingCommand command) {
        command.getCommand().getHeader().setCommandLength(command.byteSize());
        buffer(command.getCommand());
        if (command.getPacket() != null) {
            LOG.debug(address + ": Buffering object: \n" + command.getPacket());
            command.getPacket().toBuffer(buffers, command.getFragmentOffset(), command.getFragmentLength());
            bufferCount++;
        }
    }

    /**
     * Initiates a connection to a foreign host. Subsequent calls to
     * {@link #service} will take care of any further handshaking.
     * 
     * @param address
     *            Address of the host to connect to.
     * @param channelCount
     *            Maximum number of communication channels.
     * @return A {@link net.jenet.Peer} object representing the foreign host.
     */
    public Peer connect(InetSocketAddress address, int channelCount) {
        if (channelCount < configuration.getInt("ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT"))
            channelCount = configuration.getInt("ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT");
        else if (channelCount > configuration.getInt("ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT"))
            channelCount = configuration.getInt("ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT");

        if (peers.size() >= maxConnections)
            return null;

        Peer currentPeer = new Peer(this, address, channelCount);
        assignPeerID(currentPeer);
        currentPeer.setChannelCount(channelCount);
        Connect connect = new Connect();
        connect.getHeader().setChannelID((byte) 0xFF);
        connect.getHeader().setFlags(Header.FLAG_ACKNOWLEDGE);
        connect.setOutgoingPeerID(currentPeer.getIncomingPeerID());
        connect.setMtu(currentPeer.getMtu());
        connect.setWindowSize(currentPeer.getWindowSize());
        connect.setChannelCount(channelCount);
        connect.setIncomingBandwidth(incomingBandwidth);
        connect.setOutgoingBandwidth(outgoingBandwidth);
        connect.setPacketThrottleInterval(currentPeer.getPacketThrottleInterval());
        connect.setPacketThrottleAcceleration(currentPeer.getPacketThrottleAcceleration());
        connect.setPacketThrottleDeceleration(currentPeer.getPacketThrottleDeceleration());
        currentPeer.queueOutgoingCommand(connect, null, 0, (short) 0);
        return currentPeer;
    }

    /**
     * Destroys this host object.
     * 
     * @throws IOException
     */
    public void destroy() throws IOException {
        communicationChannel.close();
    }

    Event dispatchIncomingCommands() {
        Event result = new Event();
        Peer currentPeer;

        LinkedList<Peer> peersList = new LinkedList<Peer>(peers.values());

        if (peers.size() == 0)
            return result;

        /* 
         * Simply calling containsKey( lastServicedPeer.getIncomingPeerId() ) will 
         * not be sufficient because the peerID of lastServicedPeer may have been 
         * reassigned.  The get operation is quicker than containsValue because 
         * it does not have to search through all the peers. 
         * 
         * lastServicedPeer.isDisconnected() may be sufficient, but this feels more robust.
         */
        if (lastServicedPeer == null || peers.get(lastServicedPeer.getIncomingPeerID()) != lastServicedPeer)
            lastServicedPeer = peersList.getFirst();
        else
            while (peersList.getLast() != lastServicedPeer)
                peersList.addLast(peersList.removeFirst());

        do {
            currentPeer = peersList.removeFirst();
            peersList.addLast(currentPeer);

            if (currentPeer.isZombie()) {
                recalculateBandwithLimits = true;
                currentPeer.reset();
                result.setType(Event.TYPE.DISCONNECTED);
                result.setPeer(currentPeer);
                lastServicedPeer = currentPeer;
                return result;
            }

            if (!currentPeer.isConnected())
                continue;

            for (byte channelID : currentPeer.getChannels().keySet()) {
                Channel channel = currentPeer.getChannels().get(channelID);
                if (channel.getIncomingReliableCommands().isEmpty()
                        && channel.getIncomingUnreliableCommands().isEmpty())
                    continue;
                Packet packet = currentPeer.receive(channelID);
                result.setPacket(packet);
                if (packet == null)
                    continue;
                result.setType(Event.TYPE.RECEIVED);
                result.setPeer(currentPeer);
                result.setChannelID(channelID);
                result.setPacket(packet);
                lastServicedPeer = currentPeer;

                return result;
            }
        } while (currentPeer != lastServicedPeer);

        return result;
    }

    /**
     * Sends all pending messages to all the peers.
     */
    public void flush() {
        timeCurrent = getTime();
        sendOutgoingCommands(false);
    }

    /**
     * Get the address to which this Host is connected.
     * 
     * @return Returns the address.
     */
    public InetSocketAddress getAddress() {
        return address;
    }

    /**
     * @return Returns the buffers.
     */
    int getBufferCount() {
        return bufferCount;
    }

    /**
     * @return Returns the buffers.
     */
    int getCommandCount() {
        return commands.size();
    }

    ConcurrentLinkedQueue<Command> getCommands() {
        return commands;
    }

    /**
     * @return Returns the configuration.
     */
    PropertiesConfiguration getConfiguration() {
        return configuration;
    }

    /**
     * The total incomingBandwidth for this Host.
     * 
     * @return Returns the incomingBandwidth.
     */
    public int getIncomingBandwidth() {
        return incomingBandwidth;
    }

    /**
     * The number of simultaneous connections that this host can handle.
     * 
     * @return Returns the maxConnections.
     */
    public int getMaxConnections() {
        return maxConnections;
    }

    /**
     * @return Returns the mtu.
     */
    short getMtu() {
        return mtu;
    }

    /**
     * The total outgoingBandwidth for this host
     * 
     * @return Returns the outgoingBandwidth.
     */
    public int getOutgoingBandwidth() {
        return outgoingBandwidth;
    }

    /**
     * @return Returns the packetSize.
     */
    int getPacketSize() {
        int size = buffers.position();
        return size;
    }

    /**
     * The peers to which this host is connected.
     * 
     * @return Returns the peers.
     */
    public HashMap<Short, Peer> getPeers() {
        return peers;
    }

    /**
     * @return Returns the receivedAddress.
     */
    InetSocketAddress getReceivedAddress() {
        return receivedAddress;
    }

    /**
     * @return Returns the receivedData.
     */
    ByteBuffer getReceivedData() {
        return receivedData;
    }

    /**
     * @return The time elapsed in milliseconds.
     */

    /**
     * @return The system's time
     */
    int getTime() {
        return (int) (System.currentTimeMillis() - timeBase);
    }

    /**
     * @return Returns the timeBase.
     */
    long getTimeBase() {
        return timeBase;
    }

    /**
     * Returns the timestamp of last network's operation.
     * 
     * @return Returns the timeCurrent.
     */
    public int getTimeCurrent() {
        return timeCurrent;
    }

    /**
     * Executes received command
     * @return
     */
    private Event handleIncomingCommands() {
        Event result = new Event();
        Command command;
        Header header = new Header();
        Peer peer;

        if (receivedData.limit() < header.byteSize())
            return result;

        header.fromBuffer(receivedData);

        LOG.debug(address + ": Parsed header: \n" + header);

        if (header.getPeerID() == (short) 0xFFFF)
            peer = null;
        else if (header.getPeerID() >= maxConnections)
            return result;
        else {
            peer = peers.get(header.getPeerID());
            if (peer == null || peer.isDisconnected() || peer.isZombie()
                    || !receivedAddress.equals(peer.getAddress()) || peer.getChallenge() != header.getChallenge())
                return result;
            else
                peer.setAddress(receivedAddress);
        }

        if (peer != null)
            peer.setIncomingDataTotal(peer.getIncomingDataTotal() + receivedData.limit());

        int commandCount = header.getCommandCount();

        while (commandCount > 0 && receivedData.position() < receivedData.limit()) {
            command = Command.readCommand(receivedData);
            LOG.debug(address + ": Parsed Command: \n" + command);

            if (command == null)
                return result;

            commandCount--;

            if (peer == null)
                if (!(command instanceof Connect))
                    return result;

            LOG.debug(address + " executing command " + command.getClass());
            Event event = command.execute(this, peer, header);
            LOG.debug(address + " command execution ended " + command.getClass());

            if (event.type != Event.TYPE.NONE)
                result = event;

            if (command instanceof Connect)
                peer = event.getPeer();

            if (peer != null && (command.getHeader().getFlags() & Header.FLAG_ACKNOWLEDGE) != 0) {
                switch (peer.getState()) {
                case DISCONNECTING:
                    break;
                case ACKNOWLEDGING_DISCONNECT:
                    if (!(command instanceof Disconnect))
                        break;

                default:
                    peer.queueAcknowledgement(command, header.getSentTime());
                    break;
                }
            }
        }

        return result;
    }

    void initHost(int maxConnections, int incomingBandwith, int outgoingBandwith) throws ConfigurationException {
        this.maxConnections = maxConnections;
        this.incomingBandwidth = incomingBandwith;
        this.outgoingBandwidth = outgoingBandwith;

        lastServicedPeer = null;

        configuration = new PropertiesConfiguration("enet.properties");

        peers = new HashMap<Short, Peer>();

        bandwidthThrottleEpoch = 0;
        recalculateBandwithLimits = false;
        mtu = configuration.getShort("ENET_HOST_DEFAULT_MTU");
        receivedAddress = new InetSocketAddress((InetAddress) null, 0);
        buffers = ByteBuffer.allocateDirect(mtu);
        buffers.clear();
        bufferCount = 0;
    }

    /**
     * @param buffer
     * @return
     */
    int receive(ByteBuffer buffer) {
        try {
            buffer.clear();
            receivedAddress = (InetSocketAddress) communicationChannel.receive(buffer);
            buffer.flip();
            if (receivedAddress != null)
                LOG.debug("Host.receive:" + address + ". Received " + buffer.limit() + " bytes  from "
                        + receivedAddress);
            return buffer.limit();
        } catch (Exception e) {
            LOG.error("Host.receive: Error reading buffers.", e);
            return -1;
        }
    }

    /**
     * Reads data from socket and calls handleIncomingCommands
     */
    Event receiveIncomingCommands() {
        Event result = new Event();
        for (;;) {
            receivedData = ByteBuffer.allocateDirect(mtu);

            int receivedLength = receive(receivedData);

            if (receivedLength < 0) {
                result.setType(Event.TYPE.ERROR);
                return result;
            }

            if (receivedLength == 0) {
                result.setType(Event.TYPE.NONE);
                return result;
            }

            result = handleIncomingCommands();

            if (result.getType() != Event.TYPE.NONE)
                return result;
        }
    }

    // enet_socket_send
    int send(Peer peer) {
        buffers.flip();
        LOG.debug(address + ": Sending " + buffers.limit() + " bytes to " + peer.getAddress());
        try {
            return (int) communicationChannel.send(buffers, peer.getAddress());
        } catch (Exception e) {
            LOG.error("Host.send: Error writing buffers.", e);
            return -1;
        }
    }

    Event sendOutgoingCommands(boolean checkForTimeouts) {
        Event result = new Event();
        result.setType(Event.TYPE.NONE);
        Header header = new Header();
        int packetsSent = 1;
        int sentLength;

        while (packetsSent > 0) {
            packetsSent = 0;
            for (Peer currentPeer : peers.values()) {
                if (currentPeer.isDisconnected() || currentPeer.isZombie())
                    continue;

                commands.clear();
                buffers.clear();
                bufferCount = 0;
                header.toBuffer(buffers);

                if (!currentPeer.getAcknowledgements().isEmpty())
                    currentPeer.sendAcknowledgements();

                if (commands.size() < configuration.getInt("ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS")) {

                    if (checkForTimeouts && !currentPeer.getSentReliableCommands().isEmpty()
                            && Time.greaterEqual(timeCurrent, currentPeer.getNextTimeout())) {
                        result = currentPeer.checkTimeouts();
                        if (result.getType() == Event.TYPE.DISCONNECTED)
                            return result;
                    }

                    Ping ping = new Ping();

                    //Send any reliable commands
                    if (!currentPeer.getOutgoingReliableCommands().isEmpty())
                        currentPeer.sendReliableOutgoingCommands();

                    else if (currentPeer.getSentReliableCommands().isEmpty()
                            && Time.difference(timeCurrent, currentPeer.getLastReceiveTime()) >= configuration
                                    .getInt("ENET_PEER_PING_INTERVAL")
                            && currentPeer.getMtu() - getPacketSize() >= ping.byteSize()) {
                        currentPeer.ping(); //Add the ping the peer's reliable command list
                        currentPeer.sendReliableOutgoingCommands(); //Send the ping
                    }

                    //Send unreliable commands if there is space
                    if (commands.size() < configuration.getInt("ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS")
                            && !currentPeer.getOutgoingUnreliableCommands().isEmpty()) {
                        currentPeer.sendUnreliableOutgoingCommands();
                    }

                    if (commands.size() == 0)
                        continue;

                    if (currentPeer.getPacketLossEpoch() == 0)
                        currentPeer.setPacketLossEpoch(timeCurrent);
                    else if (Time.difference(timeCurrent, currentPeer.getPacketLossEpoch()) >= configuration
                            .getInt("ENET_PEER_PACKET_LOSS_INTERVAL") && currentPeer.getPacketsSent() > 0) {
                        currentPeer.updatePacketLossVariance(timeCurrent);
                    }

                    header.setPeerID(currentPeer.getOutgoingPeerID());
                    header.setFlags((byte) 0);
                    header.setCommandCount((byte) commands.size());
                    header.setSentTime(timeCurrent);
                    header.setChallenge(currentPeer.getChallenge());

                    int position = buffers.position();
                    int limit = buffers.limit();
                    buffers.clear();
                    header.toBuffer(buffers);
                    buffers.position(position);
                    buffers.limit(limit);

                    LOG.debug("Host.sendOutgoingCommands:" + address + ". Buffering header: \n" + header);

                    currentPeer.setLastSendTime(timeCurrent);

                    ++packetsSent;

                    sentLength = send(currentPeer);
                    currentPeer.getSentUnreliableCommands().clear();

                    if (sentLength < 0) {
                        result.setType(Event.TYPE.ERROR);
                        return result;
                    }
                }

            }
        }
        return result;
    }

    /**
     * Poll for new events and handle packet transmission and reception. This
     * method polls the network until a new {@link net.jenet.Event} is received
     * or the number of milliseconds in <code>timeout</code> has elapsed.
     * 
     * @see net.jenet.Event
     * @param timeout
     *            The time to poll the network in milliseconds
     * @return An {@link net.jenet.Event} object.
     */
    synchronized public Event service(int timeout) {
        LOG.debug("Servicing host:\n" + this);
        Event event = dispatchIncomingCommands();
        short waitCondition;

        if (event.getType() == Event.TYPE.DISCONNECTED || event.getType() == Event.TYPE.RECEIVED)
            return event;

        timeCurrent = getTime();
        timeout += timeCurrent;

        do {

            if (Time.difference(timeCurrent, bandwidthThrottleEpoch) >= configuration
                    .getInt("ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL"))
                bandwidthThrottle();

            event = sendOutgoingCommands(true);

            if (event.getType() == Event.TYPE.ERROR)
                LOG.error("Error");

            if (event.getType() == Event.TYPE.DISCONNECTED || event.getType() == Event.TYPE.ERROR)
                return event;

            event = receiveIncomingCommands();

            if (event.getType() != Event.TYPE.NONE)
                return event;

            event = sendOutgoingCommands(true);

            if (event.getType() == Event.TYPE.DISCONNECTED || event.getType() == Event.TYPE.ERROR)
                return event;

            event = dispatchIncomingCommands();

            if (event.getType() == Event.TYPE.DISCONNECTED || event.getType() == Event.TYPE.RECEIVED)
                return event;

            timeCurrent = getTime();

            if (Time.greaterEqual(timeCurrent, timeout)) {
                LOG.debug("Host.service: " + address + " service timed out");
                return event;
            }

            waitCondition = socketWait(WAIT_RECEIVE, Time.difference(timeout, timeCurrent));

            if (waitCondition == WAIT_ERROR) {
                event.setType(Event.TYPE.ERROR);
                return event;
            }

            timeCurrent = getTime();

        } while (waitCondition == WAIT_RECEIVE);

        return event;
    }

    /**
     * @param recalculateBandwithLimits
     *            The recalculateBandwithLimits to set.
     */
    void setRecalculateBandwithLimits(boolean recalculateBandwithLimits) {
        this.recalculateBandwithLimits = recalculateBandwithLimits;
    }

    short socketWait(short keyType, int timeOut) {
        try {
            communicationSelector.select(timeOut);
        } catch (Exception e) {
            LOG.error("Error waiting network events", e);
            return WAIT_ERROR;
        }

        for (SelectionKey key : communicationSelector.selectedKeys()) {
            if (key.isReadable() && keyType == WAIT_RECEIVE)
                return WAIT_RECEIVE;
            else if (key.isWritable() && keyType == WAIT_SEND)
                return WAIT_SEND;

        }
        return WAIT_NONE;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("address", address)
                .append("peers", peers).toString();
    }
}