private static async Task ListenerProcessingLoopAsync()
        {
            var cancellationToken = ListenerLoopTokenSource.Token;

            try
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    HttpListenerContext context = await Listener.GetContextAsync();

                    if (ServerIsRunning)
                    {
                        if (context.Request.IsWebSocketRequest)
                        {
                            // HTTP is only the initial connection; upgrade to a client-specific websocket
                            HttpListenerWebSocketContext wsContext = null;
                            try
                            {
                                wsContext = await context.AcceptWebSocketAsync(subProtocol : null);

                                int socketId = Interlocked.Increment(ref SocketCounter);
                                var client   = new ConnectedClient(socketId, wsContext.WebSocket);
                                Clients.TryAdd(socketId, client);
                                Console.WriteLine($"Socket {socketId}: New connection.");
                                _ = Task.Run(() => SocketProcessingLoopAsync(client).ConfigureAwait(false));
                            }
                            catch (Exception)
                            {
                                // server error if upgrade from HTTP to WebSocket fails
                                context.Response.StatusCode        = 500;
                                context.Response.StatusDescription = "WebSocket upgrade failed";
                                context.Response.Close();
                                return;
                            }
                        }
                        else
                        {
                            if (context.Request.AcceptTypes.Contains("text/html"))
                            {
                                Console.WriteLine("Sending HTML to client.");
                                ReadOnlyMemory <byte> HtmlPage = new ReadOnlyMemory <byte>(Encoding.UTF8.GetBytes(SimpleHtmlClient.HTML));
                                context.Response.ContentType       = "text/html; charset=utf-8";
                                context.Response.StatusCode        = 200;
                                context.Response.StatusDescription = "OK";
                                context.Response.ContentLength64   = HtmlPage.Length;
                                await context.Response.OutputStream.WriteAsync(HtmlPage, CancellationToken.None);

                                await context.Response.OutputStream.FlushAsync(CancellationToken.None);
                            }
                            else
                            {
                                context.Response.StatusCode = 400;
                            }
                            context.Response.Close();
                        }
                    }
                    else
                    {
                        // HTTP 409 Conflict (with server's current state)
                        context.Response.StatusCode        = 409;
                        context.Response.StatusDescription = "Server is shutting down";
                        context.Response.Close();
                        return;
                    }
                }
            }
            catch (HttpListenerException ex) when(ServerIsRunning)
            {
                Program.ReportException(ex);
            }
        }
        private static async Task SocketProcessingLoopAsync(ConnectedClient client)
        {
            _ = Task.Run(() => client.LoopAsync().ConfigureAwait(false));

            var socket               = client.Socket;
            var loopToken            = SocketLoopTokenSource.Token;
            var broadcastTokenSource = client.LoopTokenSource; // armazenar uma cópia para uso no bloco finalmente

            try
            {
                var buffer = WebSocket.CreateServerBuffer(4096);
                while (socket.State != WebSocketState.Closed && socket.State != WebSocketState.Aborted && !loopToken.IsCancellationRequested)
                {
                    var receiveResult = await client.Socket.ReceiveAsync(buffer, loopToken);

                    // se o token for cancelado enquanto o ReceiveAsync estiver bloqueando, o estado do soquete mudará para abortado e não poderá ser usado
                    if (!loopToken.IsCancellationRequested)
                    {
                        // o cliente está nos notificando que a conexão será fechada; enviar confirmação
                        if (client.Socket.State == WebSocketState.CloseReceived && receiveResult.MessageType == WebSocketMessageType.Close)
                        {
                            Console.WriteLine($"Socket {client.SocketId}: Confirmando Fechar quadro recebido do cliente ");
                            broadcastTokenSource.Cancel();
                            await socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Confirmar quadro fechado", CancellationToken.None);

                            // o estado do soquete muda para fechado neste momento
                        }

                        // echo text ou dados binários para a fila de transmissão
                        if (client.Socket.State == WebSocketState.Open)
                        {
                            Console.WriteLine($"Socket {client.SocketId}: Received {receiveResult.MessageType} frame ({receiveResult.Count} bytes).");
                            Console.WriteLine($"Socket {client.SocketId}: Echoing data to queue.");
                            string message = Encoding.UTF8.GetString(buffer.Array, 0, receiveResult.Count);
                            client.BroadcastQueue.Add(message);
                        }
                    }
                }
            }
            catch (OperationCanceledException)
            {
                // normal mediante cancelamento de tarefa / token, desconsidera
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Socket {client.SocketId}:");
                Program.ReportException(ex);
            }
            finally
            {
                broadcastTokenSource.Cancel();

                Console.WriteLine($"Socket {client.SocketId}: Loop de processamento finalizado no estado {socket.State}");

                // não deixe o Socket em nenhum estado potencialmente conectado
                if (client.Socket.State != WebSocketState.Closed)
                {
                    client.Socket.Abort();
                }

                // nesse ponto, o Socket é fechado ou abortado, o objeto ConnectedClient é inútil
                if (Clients.TryRemove(client.SocketId, out _))
                {
                    socket.Dispose();
                }
            }
        }