/// <summary>
        /// Find a SmartSocketServer on the local network using UDP broadcast.  This will block
        /// waiting for a server to respond or until you cancel using the CancellationToken.
        /// </summary>
        /// <returns>The connected client or null if task is cancelled.</returns>
        public static async Task <SmartSocketClient> FindServerAsync(string serviceName, string clientName, SmartSocketTypeResolver resolver, CancellationToken token,
                                                                     string udpGroupAddress = "226.10.10.2", int udpGroupPort = 37992)
        {
            return(await Task.Run(async() =>
            {
                string localHost = FindLocalHostName();
                if (localHost == null)
                {
                    return null;
                }
                while (!token.IsCancellationRequested)
                {
                    try
                    {
                        var groupAddr = IPAddress.Parse(udpGroupAddress);
                        IPEndPoint remoteEP = new IPEndPoint(groupAddr, udpGroupPort);
                        UdpClient udpClient = new UdpClient(0);
                        MemoryStream ms = new MemoryStream();
                        BinaryWriter writer = new BinaryWriter(ms);
                        writer.Write(serviceName.Length);
                        writer.Write(serviceName);
                        byte[] bytes = ms.ToArray();
                        udpClient.Send(bytes, bytes.Length, remoteEP);

                        CancellationTokenSource receiveTaskSource = new CancellationTokenSource();
                        Task <UdpReceiveResult> receiveTask = udpClient.ReceiveAsync();
                        if (receiveTask.Wait(5000, receiveTaskSource.Token))
                        {
                            UdpReceiveResult result = receiveTask.Result;
                            IPEndPoint serverEP = result.RemoteEndPoint;
                            byte[] buffer = result.Buffer;
                            BinaryReader reader = new BinaryReader(new MemoryStream(buffer));
                            int len = reader.ReadInt32();
                            string addr = reader.ReadString();
                            string[] parts = addr.Split(':');
                            if (parts.Length == 2)
                            {
                                var a = IPAddress.Parse(parts[0]);
                                SmartSocketClient client = await ConnectAsync(new IPEndPoint(a, int.Parse(parts[1])), clientName, resolver);
                                if (client != null)
                                {
                                    client.ServerName = serviceName;
                                    client.Name = localHost;
                                    return client;
                                }
                            }
                        }
                        else
                        {
                            receiveTaskSource.Cancel();
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("Something went wrong with Udp connection: " + ex.Message);
                    }
                }
                return null;
            }));
        }
        internal void RemoveClient(SmartSocketClient client)
        {
            bool found = false;

            lock (this.clients)
            {
                found = this.clients.Contains(client);
                this.clients.Remove(client);
            }

            if (found && this.ClientDisconnected != null)
            {
                this.ClientDisconnected(this, client);
            }
        }
        internal static async Task <SmartSocketClient> ConnectAsync(IPEndPoint serverEP, string clientName, SmartSocketTypeResolver resolver)
        {
            Socket client               = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            bool   connected            = false;
            CancellationTokenSource src = new CancellationTokenSource();

            try
            {
                Task task = Task.Run(() =>
                {
                    try
                    {
                        client.Connect(serverEP);
                        connected = true;
                    }
                    catch (Exception e)
                    {
                        Debug.WriteLine("Connect exception: " + e.Message);
                    }
                }, src.Token);

                // give it 30 seconds to connect...
                if (!task.Wait(60000))
                {
                    src.Cancel();
                }
            }
            catch (TaskCanceledException)
            {
                // move on...
            }

            if (connected)
            {
                var result = new SmartSocketClient(null, client, resolver)
                {
                    Name       = clientName,
                    ServerName = GetHostName(serverEP.Address)
                };
                SocketMessage response = await result.SendReceiveAsync(new SocketMessage(ConnectedMessageId, clientName));

                return(result);
            }

            return(null);
        }
        internal async Task <bool> OpenBackChannel(SmartSocketClient client, int port)
        {
            if (BackChannelOpened != null)
            {
                IPEndPoint        ipe      = (IPEndPoint)(client.Socket.RemoteEndPoint);
                IPEndPoint        endPoint = new IPEndPoint(ipe.Address, port);
                SmartSocketClient channel  = await SmartSocketClient.ConnectAsync(endPoint, this.serviceName, this.resolver);

                client.BackChannel = channel;
                this.BackChannelOpened(this, client);
                return(true);
            }
            else
            {
                // server is not expecting a backchannel!
                return(false);
            }
        }
        private void OnAccept(Socket client)
        {
            IPEndPoint        ep1   = client.RemoteEndPoint as IPEndPoint;
            SmartSocketClient proxy = new SmartSocketClient(this, client, this.resolver)
            {
                Name       = ep1.ToString(),
                ServerName = SmartSocketClient.FindLocalHostName()
            };

            proxy.Disconnected += this.OnClientDisconnected;

            SmartSocketClient[] snapshot = null;

            lock (this.clients)
            {
                snapshot = this.clients.ToArray();
            }

            foreach (SmartSocketClient s in snapshot)
            {
                IPEndPoint ep2 = s.Socket.RemoteEndPoint as IPEndPoint;
                if (ep1 == ep2)
                {
                    // can only have one client using this end point.
                    this.RemoveClient(s);
                }
            }

            lock (this.clients)
            {
                this.clients.Add(proxy);
            }

            if (this.ClientConnected != null)
            {
                this.ClientConnected(this, proxy);
            }
        }
        private void UdpListenerThread()
        {
            var           localHost = SmartSocketClient.FindLocalHostName();
            List <string> addresses = SmartSocketClient.FindLocalIpAddresses();

            if (localHost == null || addresses.Count == 0)
            {
                return; // no network.
            }

            IPEndPoint remoteEP = new IPEndPoint(GroupAddress, GroupPort);

            this.udpListener = new UdpClient(GroupPort);
            this.udpListener.JoinMulticastGroup(GroupAddress);
            while (true)
            {
                byte[] data = this.udpListener.Receive(ref remoteEP);
                if (data != null)
                {
                    BinaryReader reader = new BinaryReader(new MemoryStream(data));
                    int          len    = reader.ReadInt32();
                    string       msg    = reader.ReadString();
                    if (msg == this.serviceName)
                    {
                        // send response back with info on how to connect to this server.
                        IPEndPoint   localEp = (IPEndPoint)this.listener.LocalEndPoint;
                        string       addr    = localEp.ToString();
                        MemoryStream ms      = new MemoryStream();
                        BinaryWriter writer  = new BinaryWriter(ms);
                        writer.Write(addr.Length);
                        writer.Write(addr);
                        writer.Flush();
                        byte[] buffer = ms.ToArray();
                        this.udpListener.Send(buffer, buffer.Length, remoteEP);
                    }
                }
            }
        }
        private void OnClientDisconnected(object sender, EventArgs e)
        {
            SmartSocketClient client = (SmartSocketClient)sender;

            this.RemoveClient(client);
        }