/// <summary>Runs the application. Retrives a token from the authentication server, then retrieves the list of services, and
        /// then opens the WebSocket(s) at the address(es) specified by service discovery using the token.</summary>
        public void Run()
        {
            /* Get local hostname. */
            IPAddress hostEntry = Array.Find(Dns.GetHostEntry(Dns.GetHostName()).AddressList, ip => ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork);

            if (string.IsNullOrEmpty(_position))
            {
                _position = (hostEntry == null) ? "127.0.0.1/net" : hostEntry.ToString();
            }

            if (!GetAuthenticationInfo() || !DiscoverServices())
            {
                Environment.Exit(1);
            }

            _tokenTS = TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds;

            if (_hostName != null)
            {
                _hosts.Add(new Tuple <string, string>(_hostName, _port));
                if (_hostName2 != null)
                {
                    _hosts.Add(new Tuple <string, string>(_hostName2, _port2));
                }
            }

            Console.CancelKeyPress += Console_CancelKeyPress;
            WebSocketSession session1  = null;
            WebSocketSession session2  = null;
            double           curTS     = 0.0;
            double           deltaTime = 0.0;

            try
            {
                /* Open websocket(s) */
                foreach (var host in _hosts)
                {
                    var webSocketSession = new WebSocketSession("Session" + (_webSocketSessions.Count + 1), host.Item1, host.Item2);
                    _webSocketSessions.Add(webSocketSession.Name, webSocketSession);

                    webSocketSession.Connect();

                    Task.Factory.StartNew(() =>
                    {
                        webSocketSession.Run();
                    });

                    // if hotstandby is not supported, stop after connecting to the first host in hosts. Otherwise,
                    // connect to the first two hosts
                    if (_webSocketSessions.Count == 2 || !_hotstandby)
                    {
                        break;
                    }
                }

                // after 95% of the time allowed before the token expires, retrive a new set of tokens and send a login to each open websocket
                while (true)
                {
                    Thread.Sleep((int)(3000)); // in ms

                    session1 = _webSocketSessions["Session1"];
                    if (_webSocketSessions.ContainsKey("Session2"))
                    {
                        session2 = _webSocketSessions["Session2"];
                    }

                    if ((session1 != null && !session1.WebSocketConnected) || (session2 != null && !session2.WebSocketConnected))
                    {
                        if ((session1 != null && !session1.Reconnecting) || (session2 != null && !session2.Reconnecting))
                        {
                            curTS = TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds;

                            if ((_expiration_in_ms / 1000) < 600)
                            {
                                deltaTime = _expiration_in_ms * 0.95;
                            }
                            else
                            {
                                deltaTime = 300 * 1000;
                            }

                            if (Convert.ToInt64(curTS - deltaTime) >= Convert.ToInt64(_tokenTS))
                            {
                                if (!GetAuthenticationInfo())
                                {
                                    Console_CancelKeyPress(null, null);
                                }

                                _tokenTS = TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds;

                                if (_expiration_in_ms != _original_expiration_in_ms)
                                {
                                    System.Console.WriteLine("expire time changed from " + _original_expiration_in_ms / 1000
                                                             + " sec to " + _expiration_in_ms / 1000 + " sec; retry login");
                                    if (!GetAuthenticationInfo())
                                    {
                                        Console_CancelKeyPress(null, null);
                                    }
                                }
                            }
                        }
                        else
                        {
                            if (!GetAuthenticationInfo())
                            {
                                Console_CancelKeyPress(null, null);
                            }

                            _tokenTS = TimeSpan.FromTicks(DateTime.Now.Ticks).TotalMilliseconds;
                        }
                        if (!session1.Canceling)
                        {
                            if (session1.WebSocket.State != WebSocketState.Open)
                            {
                                session1.Reconnect();

                                Task.Factory.StartNew(() =>
                                {
                                    session1.Run();
                                });
                            }
                        }
                        if (session2 != null && !session2.Canceling)
                        {
                            if (session2.WebSocket.State != WebSocketState.Open)
                            {
                                session2.Reconnect();

                                Task.Factory.StartNew(() =>
                                {
                                    session2.Run();
                                });
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                System.Console.WriteLine(ex);
                Console.ReadKey();
            }
            finally
            {
                Console_CancelKeyPress(this, null);
                Console.ReadKey();
            }
        }