/// <summary>
        /// Sends a single object to the provided endPoint. NOTE: Any possible reply will be ignored unless listening for incoming udp packets.
        /// </summary>
        /// <param name="sendingPacketType">The sending packet type</param>
        /// <param name="objectToSend">The object to send</param>
        /// <param name="ipEndPoint">The destination IPEndPoint. Supports multicast endpoints.</param>
        /// <param name="sendReceiveOptions">The sendReceiveOptions to use for this send</param>
        public static void SendObject(string sendingPacketType, object objectToSend, IPEndPoint ipEndPoint, SendReceiveOptions sendReceiveOptions)
        {
            UDPConnection connectionToUse;

            lock (udpRogueSenderCreationLocker)
            {
                if (NetworkComms.commsShutdown)
                {
                    throw new CommunicationException("Attempting to send UDP packet but NetworkCommsDotNet is in the process of shutting down.");
                }
                else if (!udpRogueSenders.ContainsKey(ipEndPoint.AddressFamily) || udpRogueSenders[ipEndPoint.AddressFamily].ConnectionInfo.ConnectionState == ConnectionState.Shutdown)
                {
                    if (NetworkComms.LoggingEnabled)
                    {
                        NetworkComms.Logger.Trace("Creating UDPRougeSender.");
                    }

                    IPAddress any;
                    if (ipEndPoint.AddressFamily == AddressFamily.InterNetwork)
                    {
                        any = IPAddress.Any;
                    }
                    else if (ipEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
                    {
                        any = IPAddress.IPv6Any;
                    }
                    else
                    {
                        throw new CommunicationException("Attempting to send UDP packet over unsupported network address family: " + ipEndPoint.AddressFamily.ToString());
                    }

                    udpRogueSenders[ipEndPoint.AddressFamily] = new UDPConnection(new ConnectionInfo(true, ConnectionType.UDP, new IPEndPoint(any, 0), new IPEndPoint(any, 0)), NetworkComms.DefaultSendReceiveOptions, UDPOptions.None, false);
                }

                //Get the rouge sender here
                connectionToUse = udpRogueSenders[ipEndPoint.AddressFamily];
            }

            //Get connection defaults if no sendReceiveOptions were provided
            if (sendReceiveOptions == null)
            {
                sendReceiveOptions = connectionToUse.ConnectionDefaultSendReceiveOptions;
            }

            //If we are listening on what will be the outgoing adaptor we send with that client to ensure reply packets are collected
            //Determining this is annoyingly non-trivial

            //For now we will use the following method and look to improve upon it in future
            //Some very quick testing gave an average runtime of this method to be 0.12ms (averageover 1000 iterations) (perhaps not so bad after all)
            try
            {
                IPEndPoint localEndPoint = NetworkComms.BestLocalEndPoint(ipEndPoint);

                lock (udpClientListenerLocker)
                {
                    IPEndPoint existingLocalEndPoint = ExistingLocalListenEndPoints(localEndPoint.Address);
                    if (existingLocalEndPoint != null)
                    {
                        connectionToUse = udpConnectionListeners[existingLocalEndPoint];
                    }
                }
            }
            catch (Exception ex)
            {
                //if (NetworkComms.LoggingEnabled) NetworkComms.Logger.Trace("Failed to determine preferred existing udpClientListener to " + ipEndPoint.Address + ":" + ipEndPoint.Port + ". Will just use the rogue udp sender instead.");
                NetworkComms.LogError(ex, "BestLocalEndPointError");
            }

            using (Packet sendPacket = new Packet(sendingPacketType, objectToSend, sendReceiveOptions))
                connectionToUse.SendPacketSpecific(sendPacket, ipEndPoint);
        }
        /// <summary>
        /// Internal UDP creation method that performs the necessary tasks
        /// </summary>
        /// <param name="connectionInfo"></param>
        /// <param name="defaultSendReceiveOptions"></param>
        /// <param name="level"></param>
        /// <param name="listenForReturnPackets"></param>
        /// <param name="existingConnection"></param>
        /// <returns></returns>
        internal static UDPConnection GetConnection(ConnectionInfo connectionInfo, SendReceiveOptions defaultSendReceiveOptions, UDPOptions level, bool listenForReturnPackets, UDPConnection existingConnection)
        {
            connectionInfo.ConnectionType = ConnectionType.UDP;

            UDPConnection connection = null;

            lock (NetworkComms.globalDictAndDelegateLocker)
            {
                if (NetworkComms.ConnectionExists(connectionInfo.RemoteEndPoint, ConnectionType.UDP))
                {
                    connection = (UDPConnection)NetworkComms.GetExistingConnection(connectionInfo.RemoteEndPoint, ConnectionType.UDP);
                }
                else
                {
                    //If we are listening on what will be the outgoing adaptor we send with that client to ensure if our connection info is handed off we are connectable by others
                    if (existingConnection == null)
                    {
                        try
                        {
                            IPEndPoint localEndPoint = NetworkComms.BestLocalEndPoint(connectionInfo.RemoteEndPoint);

                            lock (udpClientListenerLocker)
                            {
                                IPEndPoint existingLocalEndPoint = ExistingLocalListenEndPoints(localEndPoint.Address);
                                if (existingLocalEndPoint != null)
                                {
                                    existingConnection = udpConnectionListeners[existingLocalEndPoint];

                                    //If we are using an existing listener there is no need to listen for packets
                                    listenForReturnPackets = false;
                                }
                            }
                        }
                        catch (Exception)
                        {
                            if (NetworkComms.LoggingEnabled)
                            {
                                NetworkComms.Logger.Trace("Failed to determine preferred existing udpClientListener to " + connectionInfo.RemoteEndPoint.Address + ":" + connectionInfo.RemoteEndPoint.Port.ToString() + ". Will create an isolated udp connection instead.");
                            }
                        }
                    }

                    //If an existing connection does not exist but the info we are using suggests it should we need to reset the info
                    //so that it can be reused correctly. This case generally happens when using Comms in the format
                    //UDPConnection.GetConnection(info).SendObject(packetType, objToSend);
                    if (connectionInfo.ConnectionState == ConnectionState.Established || connectionInfo.ConnectionState == ConnectionState.Shutdown)
                    {
                        connectionInfo.ResetConnectionInfo();
                    }

                    connection = new UDPConnection(connectionInfo, defaultSendReceiveOptions, level, listenForReturnPackets, existingConnection);
                }
            }

            if (!NetworkComms.commsShutdown)
            {
                TriggerConnectionKeepAliveThread();
            }

            return(connection);
        }