Depois de muitos anos respondendo a tickets de suporte, um punhado de problemas é responsável pela grande maioria dos relatos de “minha conexão WebSocket está estranha”. Nenhum é bug na biblioteca — todos são erros de configuração que se resolvem em cinco minutos depois de você saber que existem. Este post lista os dez mais comuns, com o sintoma, a causa e a solução em uma linha para cada um.
Se você só ler um erro desta lista, leia o nº 2. Bloquear o evento OnMessage é responsável por mais tickets do tipo “a biblioteca está lenta” do que todas as outras categorias somadas, e fica invisível até você escalar além de algumas centenas de clientes concorrentes. Poupe-se da sessão noturna de depuração em produção e conserte antes de subir.
Erro 1: WatchDog de reconexão desabilitado
Sintoma: o cliente conecta na inicialização, roda bem por horas e, então, para silenciosamente de receber mensagens depois de uma queda de Wi-Fi, uma reconexão de VPN ou um restart do servidor.
Causa: a propriedade WatchDog vem desligada por padrão. A biblioteca não assume que você quer reconexões automáticas — alguns apps querem comportamento fail-fast. A maioria não.
oClient.WatchDog.Enabled := True; oClient.WatchDog.Interval := 10; // try every 10 seconds oClient.WatchDog.Attempts := 0; // 0 = unlimited
Fire-and-forget. Combine com logging em OnDisconnect para ver em campo a frequência com que reconexões disparam. Para deployments móveis ou em notebook em que o usuário troca de rede, também ligue na notificação de mudança de rede do SO (SystemEvents.NetworkAvailabilityChanged em .NET, a rota WMI em Windows-Delphi) para disparar uma reconexão imediata em vez de esperar o próximo tick.
Erro 2: bloquear o evento OnMessage
Sintoma: o throughput desaba sob carga. O servidor parece “travado”. A CPU está baixa, mas as mensagens se acumulam.
Causa: OnMessage roda na thread do worker de I/O. Se você chama um banco de dados lento, uma API HTTP externa ou um parser longo dentro dele, esfomeia todas as outras conexões que compartilham aquele worker.
// BAD: blocks the worker
procedure TForm1.ServerMessage(Connection: TsgcWSConnection; const Text: string);
begin
oDb.Query('INSERT INTO events VALUES(?)', [Text]); // 50 ms
oHttp.Get('https://billing/track?event=' + Text); // 200 ms
end;
// GOOD: hand off to a worker queue
procedure TForm1.ServerMessage(Connection: TsgcWSConnection; const Text: string);
begin
oWorkQueue.Push(TWorkItem.Create(Connection.Guid, Text));
end;
Erro 3: ignorar HeartBeat
Sintoma: conexões fantasmas se acumulam. Connections.Count continua crescendo mesmo sabendo que os clientes morreram.
Causa: sem heartbeats em nível de aplicação, o timer de TCP keepalive do SO dispara depois de ~2 horas. A essa altura, você tem dezenas de milhares de sockets zumbis consumindo memória.
oServer.HeartBeat.Enabled := True; oServer.HeartBeat.Interval := 30; oServer.HeartBeat.Timeout := 90;
Erro 4: versões de TLS incompatíveis
Sintoma: o cliente recebe “handshake failed” ou “SSL_ERROR_SYSCALL”. A conexão funciona na máquina de dev mas falha em produção atrás de um proxy corporativo.
Causa: as opções SSL do cliente vêm por padrão em TLS 1.0–1.2 para compatibilidade retroativa; servidores modernos exigem apenas TLS 1.2/1.3. Ou as DLLs do OpenSSL estão velhas.
oClient.TLSOptions.Version := tlsTLSv1_2; // or tlsTLSv1_3 oClient.TLSOptions.OpenSSL_Options.LibPath := ExtractFilePath(ParamStr(0)); // Bundle the latest libcrypto-3.dll / libssl-3.dll with your installer.
Erro 5: não tratar mensagens fragmentadas
Sintoma: o servidor recebe a primeira parte de um JSON grande, seu parser falha e a conexão é fechada.
Causa: por padrão, o sgcWebSockets remonta os fragmentos por você e só dispara OnMessage com o payload completo. Mas, se você definiu ReadOptions.FragmentMode := frgPartial por questões de memória, precisa remontar.
// Default (recommended for most apps)
oServer.ReadOptions.FragmentMode := frgComplete;
// If you opted into partial delivery, reassemble manually
procedure TForm1.OnFragment(Connection: TsgcWSConnection;
const aPartial: TBytes; aIsFinal: Boolean);
begin
Buffers[Connection.Guid].Append(aPartial);
if aIsFinal then
Process(Buffers[Connection.Guid].ToBytes);
end;
Erro 6: usar API síncrona na thread da GUI
Sintoma: a UI congela por vários segundos ao conectar ou ao chamar WriteData em um link lento.
Causa: chamadas bloqueantes na thread principal. Em apps VCL/FMX, sempre use o padrão assíncrono, ou chame a partir de uma thread de worker.
// BAD: blocks the UI
oClient.Active := True;
// GOOD: connect asynchronously
TTask.Run(procedure
begin
oClient.Active := True;
end);
// Or use the event-driven API
oClient.OnConnect := DoConnected;
oClient.Connect; // returns immediately
Erro 7: esquecer de selecionar um subprotocolo
Sintoma: a conexão é bem-sucedida, mas o par rejeita todo frame, ou responde com um formato diferente do esperado.
Causa: muitos servidores WebSocket (MQTT-over-WS, STOMP, GraphQL-WS, Phoenix) exigem um subprotocolo específico no handshake. Sem ele, o servidor cai num protocolo padrão diferente ou derruba você.
// MQTT over WebSocket
oClient.Specifications.Hixie76 := False;
oClient.Specifications.RFC6455 := True;
oClient.HeadersRequest.Add('Sec-WebSocket-Protocol: mqttv3.1');
// GraphQL over WS
oClient.HeadersRequest.Add('Sec-WebSocket-Protocol: graphql-ws');
Erro 8: tamanhos de buffer pequenos demais (ou grandes demais)
Sintoma: CPU alta num servidor empurrando muitas mensagens pequenas, ou out-of-memory num servidor empurrando mensagens grandes.
Causa: os buffers de envio/recebimento padrão são dimensionados para uso geral. Ajuste à forma do seu tráfego.
// Small messages (chat, telemetry): smaller buffers reduce per-conn memory oServer.IOHandler.RecvBufferSize := 4096; oServer.IOHandler.SendBufferSize := 4096; // Big messages (file transfer, media): larger buffers reduce syscalls oServer.IOHandler.RecvBufferSize := 65536; oServer.IOHandler.SendBufferSize := 65536;
Erro 9: sem checagem de Origin em servidores públicos
Sintoma: auditoria de segurança aponta “qualquer site pode se conectar ao seu WebSocket via navegador do usuário”.
Causa: o protocolo WebSocket não impõe same-origin. Seu servidor precisa validar o cabeçalho Origin.
procedure TForm1.ServerBeforeConnect(Connection: TsgcWSConnection;
var Continue: Boolean);
var
vOrigin: string;
begin
vOrigin := Connection.Headers.Values['Origin'];
Continue := (vOrigin = 'https://app.example.com')
or (vOrigin = 'https://admin.example.com');
end;
Erro 10: logar dados sensíveis em OnMessage
Sintoma: auditor encontra chaves de API, JWTs ou PII em arquivos de log. Problema de compliance.
Causa: o fácil LogMemo.Lines.Add(Text) em OnMessage escreve todo payload em disco para sempre.
procedure TForm1.ServerMessage(Connection: TsgcWSConnection; const Text: string);
begin
// Hash / redact before logging
LogMemo.Lines.Add(Format('[%s] %d bytes (sha=%s)',
[Connection.Guid, Length(Text), ShortHash(Text)]));
Process(Text);
end;
Bônus: não ler o History.txt
Cada release acompanha um history.txt listando cada mudança, correção e nota de quebra desde 2013. Cinco minutos lendo isso após cada upgrade economiza horas de “por que isso parou de funcionar” depois.
Bônus 2: misturar versões de componente entre projetos
Desenvolvedores Delphi às vezes copiam um único .pas de uma release mais nova do sgcWebSockets para um projeto mais antigo, “só esse arquivo”. Isso funciona até não funcionar — o arquivo depende de tipos que mudaram duas releases atrás, e o linker falha misteriosamente ou, pior, lincava mas trava em runtime. Sempre atualize a biblioteca inteira junto. Os 30 segundos economizados copiando um arquivo não compensam as quatro horas de debug quando alguma coisa rio abaixo quebra.
Bônus 3: tratar WebSocket como fire-and-forget
WebSocket não é uma fila de mensagens. É um stream bidirecional de bytes sobre TCP. Se a rede cair no meio da mensagem, o frame é perdido e nunca é reentregue automaticamente. Para mensagens críticas para o negócio você precisa adicionar seu próprio protocolo de reconhecimento por cima — geralmente um UUID por mensagem, um ACK explícito do receptor e um reenvio do remetente após timeout. Pular essa camada é ok para notificações de “usuário está digitando” e fatal para “usuário pagou o carrinho”.
Bônus 4: deixar memos sangrarem memória
Sintoma: o memo de diagnóstico do seu form de debug é a primeira coisa a estourar a memória do cliente depois de algumas horas de tráfego. Você culpa o sgcWebSockets; o sgcWebSockets é inocente.
Causa: um TMemo retém cada linha já adicionada. A 100 linhas por segundo, são 360.000 linhas por hora. Cada linha aloca uma string. A VCL renderiza milhares de linhas invisíveis a cada WM_PAINT. Seu processo arrasta até parar enquanto a biblioteca não está fazendo nada de errado.
// Cap the diagnostic memo
procedure TForm1.LogLine(const aText: string);
const
cMaxLines = 500;
begin
TThread.Queue(nil, procedure
begin
while Memo1.Lines.Count > cMaxLines do
Memo1.Lines.Delete(0);
Memo1.Lines.Add(aText);
end);
end;
Melhor ainda: logue para um arquivo rotativo via LoggerPro e só espelhe as últimas 200 linhas no memo para debug visual. Código de produção nunca deveria escrever em controle de UI a partir de uma thread de rede.
Bônus 5: não fechar o servidor antes do Application.Terminate
Sintoma: no shutdown do app, o processo trava por 30 segundos, ou o SO reporta exceções não tratadas em logs do cliente porque as conexões foram derrubadas sem grace.
Causa: o destrutor do servidor envia um close frame para cada conexão e espera o SO liberar a porta de escuta. Se você chama Application.Terminate antes de oServer.Active := False, as conexões morrem no meio do handshake e a porta do SO fica em TIME_WAIT, bloqueando um restart rápido.
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if oServer.Active then
begin
oServer.Broadcast('{"event":"shutdown","reconnect_after":5}');
Sleep(200); // let frames flush
oServer.Active := False; // graceful close
end;
end;
Para servidores de console, conecte SetConsoleCtrlHandler no Windows ou SIGTERM no Linux e rode a mesma sequência de shutdown. Combine isso com um loop de HUP/restart no seu service manager e você tem deploys com zero conexões derrubadas.
O padrão por trás dos padrões
A maioria desses erros tem uma raiz comum: assumir que a rede é confiável. Não é. Conexões TCP half-open acontecem. Redes móveis caem. Proxies corporativos quebram TLS. Wi-Fi troca. Servidores reiniciam. Load balancers de nuvem encerram conexões ociosas após 60 segundos. Um app WebSocket que não sobrevive a essas condições não está pronto — é uma demo de happy path. A boa notícia: a biblioteca expõe um controle para cada um desses cenários. A má notícia: a maioria vem desligada por padrão por compatibilidade retroativa. Duas horas lendo as páginas de WatchDog, HeartBeat, Reconnect e opções TLS da documentação é o seguro mais barato que você vai contratar.
Padrão de segunda ordem: respeite a thread de I/O. Qualquer coisa que demore mais de um milissegundo — query de banco, I/O de arquivo, chamada HTTP externa, regex em string longa, parse de JSON em payload de 100 KB — pertence a uma thread de worker, não a OnMessage. Faça dessa regra algo absoluto. Desenvolvedores juniores vão violar em três meses; um checklist de code review com “sem bloqueio em OnMessage” pega isso antes de subir.
Próximos passos
Se você está tunando um servidor de alto tráfego, leia em seguida Ajuste de Desempenho do sgcWebSockets. Novo na biblioteca? Comece pelo hub Primeiros Passos e conecte seu primeiro WebSocket em cinco minutos.