edu.umass.cs.nio.MessageNIOTransport.java Source code

Java tutorial

Introduction

Here is the source code for edu.umass.cs.nio.MessageNIOTransport.java

Source

/* Copyright (c) 2015 University of Massachusetts
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 * 
 * Initial developer(s): V. Arun */
package edu.umass.cs.nio;

import org.json.JSONException;
import org.json.JSONObject;

import edu.umass.cs.nio.interfaces.Byteable;
import edu.umass.cs.nio.interfaces.InterfaceMessageExtractor;
import edu.umass.cs.nio.interfaces.InterfaceNIOTransport;
import edu.umass.cs.nio.interfaces.NodeConfig;
import edu.umass.cs.nio.nioutils.NIOHeader;
import edu.umass.cs.nio.nioutils.NIOInstrumenter;
import edu.umass.cs.nio.nioutils.PacketDemultiplexerDefault;
import edu.umass.cs.nio.nioutils.SampleNodeConfig;
import edu.umass.cs.utils.Util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

/**
 * @author V. Arun
 * @param <NodeIDType>
 * @param <MessageType>
 * 
 *            This class exists primarily as a wrapper around NIOTransport to
 *            support messages. NIOTransport is for general-purpose NIO byte
 *            stream communication between numbered nodes as specified by the
 *            NodeConfig interface and a data processing worker as specified by
 *            the DataProcessingWorker interface that handles a byte stream.
 *            This class provides the abstraction of messages and a
 *            corresponding PacketDemultiplexer that handles messages instead of
 *            a continuous byte stream.
 * 
 *            MessageType can be any type whose toString() method results in a
 *            meaningful serialization of a MessageType instance, i.e., the
 *            packet demultiplexer on the other end must be able to reconstruct
 *            the MessageType object from the String. The one exception is
 *            byte[] that is supported here. However, MessageType can not extend
 *            Object[] or be any primitive array other than byte[]. If byte[] is
 *            MessageType, the corresponding
 *            AbstractPacketDemultiplexer.getMessage(byte[]) method should
 *            return a String decoded from the byte[] assuming ISO-8859-1
 *            encoding.
 * 
 *            This class short-circuits local sends by directly sending it to
 *            the packet demultiplexer.
 */

public class MessageNIOTransport<NodeIDType, MessageType> extends NIOTransport<NodeIDType>
        implements InterfaceNIOTransport<NodeIDType, MessageType> {

    /**
     * JSON key corresponding to sender IP address. Relevant only if
     * {@code MessageType} is JSONObject. These can be optimized to have just a
     * single short key, e.g., "SA" for sender socket address, but it doesn't
     * really matter because all four fields below are stamped only at the
     * receiver, so they don't induce more network traffic or (de-)serialization
     * overhead.
     */
    @Deprecated
    public static final String SNDR_IP_FIELD = "_SIP";
    /**
     * JSON key corresponding to sender port number. Relevant only if
     * {@code MessageType} is JSONObject.
     */
    @Deprecated
    public static final String SNDR_PORT_FIELD = "_SPORT";

    /**
     * JSON key corresponding to sender socket address. Relevant only if
     * {@code MessageType} is JSONObject.
     */
    public static final String SNDR_ADDRESS_FIELD = "_SNDR_ADDRESS";

    /**
     * JSON key corresponding to receiver IP address. Relevant only if
     * {@code MessageType} is JSONObject.
     */
    @Deprecated
    public static final String RCVR_IP_FIELD = "_RCVR_IP_ADDRESS";

    /**
     * JSON key corresponding to receiver port number. Relevant only if
     * {@code MessageType} is JSONObject.
     */
    @Deprecated
    public static final String RCVR_PORT_FIELD = "_RCVR_TCP_PORT";

    /**
     * JSON key corresponding to receiver socket address. Relevant only if
     * {@code MessageType} is JSONObject.
     */
    public static final String RCVR_ADDRESS_FIELD = "_RCVR_ADDRESS";

    /**
     * Initiates transporter with id and nodeConfig.
     * 
     * @param id
     *            My node ID.
     * @param nodeConfig
     *            A map from all nodes' IDs to their respective socket
     *            addresses.
     * @throws IOException
     */
    public MessageNIOTransport(NodeIDType id, NodeConfig<NodeIDType> nodeConfig) throws IOException {
        // Note: Default extractor will not do any useful demultiplexing
        super(id, nodeConfig, new MessageExtractor());
    }

    /**
     * @param id
     * @param nodeConfig
     * @param sslMode
     * @throws IOException
     */
    public MessageNIOTransport(NodeIDType id, NodeConfig<NodeIDType> nodeConfig,
            SSLDataProcessingWorker.SSL_MODES sslMode) throws IOException {
        // Note: Default extractor will not do any useful demultiplexing
        super(id, nodeConfig, new MessageExtractor(), true, sslMode);
    }

    /**
     * 
     * @param id
     *            My node ID.
     * @param nodeConfig
     *            A map from all nodes' IDs to their respective socket
     *            addresses.
     * @param pd
     *            The packet demultiplexer to handle received messages.
     * @param start
     *            If a server thread must be automatically started upon
     *            construction. If false, the caller must explicitly invoke (new
     *            Thread(JSONNIOTransport)).start() to start the server.
     * @throws IOException
     */
    public MessageNIOTransport(NodeIDType id, NodeConfig<NodeIDType> nodeConfig, AbstractPacketDemultiplexer<?> pd,
            boolean start) throws IOException {
        // Switched order of the latter two arguments
        super(id, nodeConfig, new MessageExtractor(pd));
        if (start && !isStarted()) {
            (new Thread(this)).start();
        }
    }

    /**
     * @param id
     * @param nodeConfig
     * @param pd
     * @param start
     * @param sslMode
     *            To enable SSL.
     * @throws IOException
     */
    public MessageNIOTransport(NodeIDType id, NodeConfig<NodeIDType> nodeConfig, AbstractPacketDemultiplexer<?> pd,
            boolean start, SSLDataProcessingWorker.SSL_MODES sslMode) throws IOException {
        // Switched order of the latter two arguments
        super(id, nodeConfig, new MessageExtractor(pd), start, sslMode);
    }

    /**
     * @param address
     * @param port
     * @param pd
     * @param sslMode
     * @throws IOException
     */
    public MessageNIOTransport(InetAddress address, int port, AbstractPacketDemultiplexer<?> pd,
            SSLDataProcessingWorker.SSL_MODES sslMode) throws IOException {
        super(address, port, new MessageExtractor(pd), sslMode);
    }

    /**
     * Used only for testing. The private nature of this method means that
     * external users can no longer explicitly specify the message extractor
     * that can now only be {@link edu.umass.cs.nio.MessageExtractor}.
     * 
     * @param id
     *            My node ID.
     * @param nodeConfig
     *            A map from all nodes' IDs to their respective socket
     *            addresses.
     * @param worker
     *            The message extractor.
     * 
     * @throws IOException
     */
    private MessageNIOTransport(NodeIDType id, NodeConfig<NodeIDType> nodeConfig, MessageExtractor worker)
            throws IOException {
        // Switched order of the latter two arguments
        super(id, nodeConfig, worker);
    }

    /**
     * @param pd
     *            The demultiplxer to add at the end of the current chain.
     */
    public void addPacketDemultiplexer(AbstractPacketDemultiplexer<?> pd) {
        // will throw exception if worker not MessageExtractor
        ((InterfaceMessageExtractor) this.worker).addPacketDemultiplexer(pd);
    }

    /**
     * @param pd
     *            The demultiplxer to add before the current chain.
     */
    public void precedePacketDemultiplexer(AbstractPacketDemultiplexer<?> pd) {
        // will throw exception if worker not MessageExtractor
        ((InterfaceMessageExtractor) this.worker).precedePacketDemultiplexer(pd);
    }

    /**
     * 
     */
    @Override
    public NodeIDType getMyID() {
        return this.myID;
    }

    public void stop() {
        super.stop();
        ((InterfaceMessageExtractor) this.worker).stop();
        JSONDelayEmulator.stop();
    }

    private MessageType putEmulatedDelays(NodeIDType id, MessageType msg) {
        if (JSONDelayEmulator.isDelayEmulated() && msg instanceof JSONObject)
            JSONDelayEmulator.putEmulatedDelay(id, (JSONObject) msg);
        return msg;
    }

    /**
     * Send a JSON packet to a node id.
     *
     * @param id
     * @param msg
     * @return Refer {@link JSONMessenger#sendToID(Object, JSONObject)
     *         JSONMessenger.sendToID(Object, JSONObject)}.
     * @throws IOException
     */
    @Override
    public int sendToID(NodeIDType id, MessageType msg) throws IOException {
        return sendToIDInternal(id, putEmulatedDelays(id, msg));
    }

    /**
     * Send a JSON packet to an inet socket address (ip and port).
     *
     * @param isa
     * @param msg
     * @return Refer {@link JSONMessenger#sendToID(Object, JSONObject)
     *         JSONMessenger.sendToID(Object, JSONObject)}.
     * @throws IOException
     */
    @Override
    public int sendToAddress(InetSocketAddress isa, MessageType msg) throws IOException {
        msg = putEmulatedDelays(null, msg);
        if (msg instanceof byte[])
            return this.sendUnderlying(isa, (byte[]) msg);
        else if (msg instanceof Byteable)
            return this.sendUnderlying(isa, ((Byteable) msg).toBytes());

        return this.sendUnderlying(isa, msg.toString().getBytes(NIO_CHARSET_ENCODING));
    }

    /**
     * @param json
     * @return Socket address of the sender recorded in this JSON message at
     *         receipt time.
     */
    public static InetSocketAddress getSenderAddress(JSONObject json) {
        if (json instanceof JSONMessenger.JSONObjectWrapper)
            try {
                return getSenderAddress((byte[]) ((JSONMessenger.JSONObjectWrapper) json).obj);
            } catch (UnknownHostException e1) {
                e1.printStackTrace();
            }
        // else
        try {
            InetSocketAddress isa = json.has(MessageNIOTransport.SNDR_ADDRESS_FIELD)
                    ? Util.getInetSocketAddressFromStringStrict(
                            json.getString(MessageNIOTransport.SNDR_ADDRESS_FIELD))
                    : null;
            return isa;
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param json
     * @return Sender address as String without intermediate conversion to InetSocketAddress.
     */
    public static String getSenderAddressAsString(JSONObject json) {
        if (json instanceof JSONMessenger.JSONObjectWrapper)
            try {
                return getSenderAddress((byte[]) ((JSONMessenger.JSONObjectWrapper) json).obj).toString();
            } catch (UnknownHostException e1) {
                e1.printStackTrace();
            }
        // else
        try {
            return json.has(MessageNIOTransport.SNDR_ADDRESS_FIELD)
                    ? (json.getString(MessageNIOTransport.SNDR_ADDRESS_FIELD))
                    : null;
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param bytes
     * @return Sender InetSocketAddress from bytes
     * @throws UnknownHostException
     */
    public static InetSocketAddress getSenderAddress(byte[] bytes) throws UnknownHostException {
        ByteBuffer bbuf = ByteBuffer.wrap(bytes, 0, NIOHeader.BYTES);
        byte[] addressBytes = new byte[4];
        bbuf.get(addressBytes);
        InetAddress address = InetAddress.getByAddress(addressBytes);
        int port = (int) bbuf.getShort();
        if (port < 0)
            port += 2 * (Short.MAX_VALUE + 1);
        return new InetSocketAddress(address, port);
    }

    /**
     * @param bytes
     * @return Sender InetSocketAddress from bytes
     * @throws UnknownHostException
     */
    public static InetSocketAddress getReceiverAddress(byte[] bytes) throws UnknownHostException {
        ByteBuffer bbuf = ByteBuffer.wrap(bytes, 6, NIOHeader.BYTES);
        byte[] addressBytes = new byte[4];
        bbuf.get(addressBytes);
        InetAddress address = InetAddress.getByAddress(addressBytes);
        int port = (int) bbuf.getShort();
        if (port < 0)
            port += 2 * (Short.MAX_VALUE + 1);
        return new InetSocketAddress(address, port);
    }

    /**
     * @param json
     * @return Parsed socket address
     */
    public static InetSocketAddress getSenderAddressJSONSmart(net.minidev.json.JSONObject json) {
        InetSocketAddress isa = json.containsKey(MessageNIOTransport.SNDR_ADDRESS_FIELD)
                ? Util.getInetSocketAddressFromString((String) json.get(MessageNIOTransport.SNDR_ADDRESS_FIELD))
                : null;
        return isa;
    }

    /**
     * @param json
     * @return Socket address of the recorded recorded in this JSON message at
     *         receipt time. Sometimes a sender needs to know on which of its
     *         possibly multiple listening sockets this message was received, so
     *         we insert it into the packet at receipt time.
     */
    public static InetSocketAddress getReceiverAddress(JSONObject json) {
        if (json instanceof JSONMessenger.JSONObjectWrapper)
            try {
                return getReceiverAddress((byte[]) ((JSONMessenger.JSONObjectWrapper) json).obj);
            } catch (UnknownHostException e1) {
                e1.printStackTrace();
            }
        InetSocketAddress isa = null;
        try {
            isa = json.has(MessageNIOTransport.RCVR_ADDRESS_FIELD)
                    ? Util.getInetSocketAddressFromStringStrict(
                            json.getString(MessageNIOTransport.RCVR_ADDRESS_FIELD))
                    : null;

        } catch (JSONException e) {
            e.printStackTrace();
        }
        return isa;
    }

    /**
     * @param json
     * @return Same as {@link #getReceiverAddress(JSONObject)}
     */
    public static InetSocketAddress getReceiverAddressJSONSmart(net.minidev.json.JSONObject json) {
        InetSocketAddress isa = json.containsKey(MessageNIOTransport.RCVR_ADDRESS_FIELD)
                ? Util.getInetSocketAddressFromString((String) json.get(MessageNIOTransport.RCVR_ADDRESS_FIELD))
                : null;

        return isa;
    }

    /**
     * @param json
     * @return Sender InetAddress read from json.
     * @throws JSONException
     */
    public static final InetAddress getSenderInetAddress(JSONObject json) throws JSONException {
        InetSocketAddress isa = getSenderAddress(json);
        return isa != null ? isa.getAddress() : null;
    }

    /* ******************End of public send methods************************** */

    /* This method adds a header only if a socket channel is used to send to a
     * remote node, otherwise it hands over the message directly to the worker. */
    protected int sendToIDInternal(NodeIDType destID, MessageType msg) throws IOException {
        if (destID.equals(this.myID))
            return sendLocal(msg);
        // else
        if (msg instanceof byte[])
            return this.sendUnderlying(destID, (byte[]) msg);
        else if (msg instanceof Byteable)
            return this.sendUnderlying(destID, ((Byteable) msg).toBytes());

        return this.sendUnderlying(destID, msg.toString().getBytes(NIO_CHARSET_ENCODING));
    }

    // bypass network send by directly passing to local worker
    private int sendLocal(Object message) throws UnsupportedEncodingException {

        byte[] msg = (message instanceof byte[] ? ((byte[]) message)
                : message.toString().getBytes(NIO_CHARSET_ENCODING));
        int length = msg.length;
        ((InterfaceMessageExtractor) worker)
                .processLocalMessage(new InetSocketAddress(this.getNodeAddress(), this.getNodePort()), msg);
        return length;
    }

    /**
     * We send even byte arrays encoded as strings because it is easier to do
     * demultiplexing (in JSONMesageExtractor) working with strings than byte
     * arrays. Using a fixed encoding only ensures that going from a string to
     * bytes and back yields the same string. But we also want the property here
     * that going from a byte array to a string and back yields the same byte
     * array. This property is not ensured by all encodings but is ensured by
     * ISO-8859-1, so we use that encoding.
     * 
     * Note: If we really just need to send a byte stream and don't care about
     * receiving all of the bytes written as a single unit, we should not be
     * using MessageNIOTransport in the first place, and just use NIOTransport
     * directly instead.
     */
    public static final String NIO_CHARSET_ENCODING = "ISO-8859-1";

    /**
     * These methods are really redundant wrappers around the corresponding
     * NIOTransport methods, but they exist so that there is one place where all
     * NIO sends actually happen.
     */
    private int sendUnderlying(NodeIDType id, byte[] data) throws IOException {
        return this.send(id, data);
    }

    private int sendUnderlying(InetSocketAddress isa, byte[] data) throws IOException {
        return this.send(isa, data);
    }

    // //////////////////////////////////////////////////////////////////////
    // for testing only
    private static JSONObject JSONify(int msgNum, String s) throws JSONException {
        return new JSONObject("{\"msg\" : \"" + s + "\" , \"msgNum\" : " + msgNum + "}");
    }

    /* The test code here is mostly identical to that of NIOTransport but tests
     * JSON messages, headers, and delay emulation features. Need to test it
     * with the rest of GNS. */
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        int msgNum = 0;
        int port = 2000;
        int nNodes = 100;
        SampleNodeConfig<Integer> snc = new SampleNodeConfig<Integer>(port);
        snc.localSetup(nNodes + 2);
        MessageExtractor[] workers = new MessageExtractor[nNodes + 1];
        for (int i = 0; i < nNodes + 1; i++) {
            workers[i] = new MessageExtractor(new PacketDemultiplexerDefault());
        }
        MessageNIOTransport<?, ?>[] niots = new MessageNIOTransport[nNodes];

        try {
            int smallNNodes = 2;
            for (int i = 0; i < smallNNodes; i++) {
                niots[i] = new MessageNIOTransport<Integer, JSONObject>(i, snc, workers[i]);
                new Thread(niots[i]).start();
            }

            /* Test a few simple hellos. The sleep is there to test that the
             * successive writes do not "accidentally" benefit from concurrency,
             * i.e., to check that OP_WRITE flags will be set correctly. */
            ((MessageNIOTransport<Integer, JSONObject>) niots[1]).sendToIDInternal(0,
                    JSONify(msgNum++, "Hello from 1 to 0"));
            ((MessageNIOTransport<Integer, JSONObject>) niots[0]).sendToIDInternal(1,
                    JSONify(msgNum++, "Hello back from 0 to 1"));
            ((MessageNIOTransport<Integer, JSONObject>) niots[0]).sendToIDInternal(1,
                    JSONify(msgNum++, "Second hello back from 0 to 1"));
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            ((MessageNIOTransport<Integer, JSONObject>) niots[0]).sendToIDInternal(1,
                    JSONify(msgNum++, "Third hello back from 0 to 1"));
            ((MessageNIOTransport<Integer, JSONObject>) niots[1]).sendToIDInternal(0,
                    JSONify(msgNum++, "Thank you for all the hellos back from 1 to 0"));
            // //////////////////////////////////////////////////////////////////////
            int seqTestNum = 1;
            Thread.sleep(2000);
            System.out.println("\n\n\nBeginning test of " + seqTestNum + " random, sequential messages");
            Thread.sleep(1000);

            // //////////////////////////////////////////////////////////////////////

            // Create the remaining nodes up to nNodes
            for (int i = smallNNodes; i < nNodes; i++) {
                niots[i] = new MessageNIOTransport<Integer, JSONObject>(i, snc, workers[i]);
                new Thread(niots[i]).start();
            }

            // Test a random, sequential communication pattern
            for (int i = 0; i < nNodes * seqTestNum; i++) {
                int k = (int) (Math.random() * nNodes);
                int j = (int) (Math.random() * nNodes);
                System.out.println("Message " + i + " with msgNum " + msgNum);
                ((MessageNIOTransport<Integer, JSONObject>) niots[k]).sendToIDInternal(j,
                        JSONify(msgNum++, "Hello from " + k + " to " + j));
            }

            int oneToOneTestNum = 1;
            // //////////////////////////////////////////////////////////////////////

            Thread.sleep(1000);
            System.out.println("\n\n\nBeginning test of " + oneToOneTestNum * nNodes
                    + " random, concurrent, 1-to-1 messages with emulated delays");
            Thread.sleep(1000);
            // //////////////////////////////////////////////////////////////////////

            // Random, concurrent communication pattern with emulated delays
            ScheduledExecutorService execpool = Executors.newScheduledThreadPool(5);
            class TX extends TimerTask {

                MessageNIOTransport<Integer, JSONObject> sndr = null;
                private int rcvr = -1;
                int msgNum = -1;

                TX(int i, int id, MessageNIOTransport<?, ?>[] n, int m) {
                    sndr = (MessageNIOTransport<Integer, JSONObject>) n[i];
                    rcvr = id;
                    msgNum = m;
                }

                TX(MessageNIOTransport<Integer, JSONObject> niot, int id, int m) {
                    sndr = niot;
                    rcvr = id;
                    msgNum = m;
                }

                public void run() {
                    try {
                        sndr.sendToIDInternal(rcvr, JSONify(msgNum, "Hello from " + sndr.myID + " to " + rcvr));
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            }
            JSONDelayEmulator.emulateDelays();

            MessageNIOTransport<Integer, JSONObject> concurrentSender = new MessageNIOTransport<Integer, JSONObject>(
                    nNodes, snc, workers[nNodes]);
            new Thread(concurrentSender).start();
            ScheduledFuture<?>[] futuresRandom = new ScheduledFuture[nNodes * oneToOneTestNum];
            for (int i = 0; i < nNodes * oneToOneTestNum; i++) {
                TX task = new TX(concurrentSender, 0, msgNum++);
                System.out.println("Scheduling random message " + i + " with msgNum " + msgNum);
                futuresRandom[i] = execpool.schedule(task, 0, TimeUnit.MILLISECONDS);
            }
            for (int i = 0; i < nNodes * oneToOneTestNum; i++) {
                try {
                    futuresRandom[i].get();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            // //////////////////////////////////////////////////////////////////////
            Thread.sleep(1000);
            System.out.println(
                    "\n\n\nBeginning test of random, concurrent, " + " any-to-any messages with emulated delays");
            Thread.sleep(1000);
            // //////////////////////////////////////////////////////////////////////

            int load = nNodes * 25;
            int msgsToFailed = 0;
            ScheduledFuture<?>[] futures = new ScheduledFuture[load];
            for (int i = 0; i < load; i++) {
                int k = (int) (Math.random() * nNodes);
                int j = (int) (Math.random() * nNodes);
                // long millis = (long)(Math.random()*1000);

                if (i % 100 == 0) {
                    // Periodically try sending to a non-existent node
                    j = nNodes + 1;
                    msgsToFailed++;
                }

                TX task = new TX(k, j, niots, msgNum++);
                System.out.println("Scheduling random message " + i + " with msgNum " + msgNum);
                futures[i] = (ScheduledFuture<?>) execpool.schedule(task, 0, TimeUnit.MILLISECONDS);
            }
            int numExceptions = 0;
            for (int i = 0; i < load; i++) {
                try {
                    futures[i].get();
                } catch (Exception e) {
                    // e.printStackTrace();
                    numExceptions++;
                }
            }

            // ////////////////////////////////////////////////////////////////
            Thread.sleep(2000);
            System.out.println("\n\n\nPrinting overall stats. Number of exceptions =  " + numExceptions);
            System.out.println((new NIOInstrumenter() + "\n"));
            boolean pending = false;
            for (int i = 0; i < nNodes; i++) {
                if (niots[i].getPendingSize() > 0) {
                    System.out.println("Pending messages at node " + i + " : " + niots[i].getPendingSize());
                    pending = true;
                }
            }
            int missing = NIOInstrumenter.getMissing();
            assert (pending == false || missing == msgsToFailed) : "Unsent pending messages in NIO";
            for (NIOTransport<?> niot : niots) {
                niot.stop();
            }
            concurrentSender.stop();
            execpool.shutdown();

            if (!pending || missing == msgsToFailed) {
                System.out.println("\nSUCCESS: no pending messages to non-failed nodes!");
            }

        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public NodeConfig<NodeIDType> getNodeConfig() {
        return this.nodeConfig;
    }

    @Override
    public int sendToID(NodeIDType id, byte[] msg) throws IOException {
        return id != myID ? this.sendUnderlying(id, msg) : this.sendLocal(msg);
    }

    @Override
    public int sendToAddress(InetSocketAddress isa, byte[] msg) throws IOException {
        return this.sendUnderlying(isa, msg);
    }

    public MessageNIOTransport<NodeIDType, MessageType> setName(String name) {
        super.setName(name);
        return this;
    }
}