sgcWebSockets Kullanırken Yapılan 10 Yaygın Hata (ve Bunları Nasıl Düzeltebilirsiniz)

· Bileşenler

Uzun yıllar destek talepleri yanıtladıktan sonra, aynı birkaç sorun "WebSocket bağlantım garip davranıyor" raporlarının büyük çoğunluğunu oluşturuyor. Hiçbiri kütüphanedeki hatalar değil. Tümü, var olduklarını öğrendikten sonra düzeltilmesi beş dakika süren yapılandırma hatalarıdır. Bu yazı, her birinin belirtisi, nedeni ve çözen tek satırlık ipucuyla birlikte en yaygın on tanesini listeler.

Bu listeden yalnızca bir hata okuyacaksanız, 2 numara olsun. OnMessage olayını bloke etmek, diğer tüm kategorilerin toplamından daha fazla "kütüphane yavaş" talebinden sorumludur ve birkaç yüz eşzamanlı istemcinin ötesine ölçeklenene kadar görünmezdir. Geç saatteki üretim hata ayıklama oturumundan kendinizi kurtarın ve dağıtmadan önce düzeltin.

Hata 1: WatchDog Yeniden Bağlanma Devre Dışı

Belirti: istemci başlangıçta bağlanır, saatlerce sorunsuz çalışır, ardından bir Wi-Fi kesintisi, bir VPN yeniden bağlanması veya bir sunucu yeniden başlatmasından sonra sessizce mesaj almayı durdurur.

Neden: WatchDog özelliği varsayılan olarak kapalıdır. Kütüphane otomatik yeniden bağlanmalar istediğinizi varsaymaz. Bazı uygulamalar hızlı başarısızlık (fail-fast) davranışı ister. Çoğu istemez.

oClient.WatchDog.Enabled  := True;
oClient.WatchDog.Interval := 10;   // try every 10 seconds
oClient.WatchDog.Attempts := 0;    // 0 = unlimited

Kur ve unut. Yeniden bağlanmaların sahada ne sıklıkta tetiklendiğini görebilmeniz için bunu OnDisconnect günlüğüyle birleştirin. Kullanıcının ağlar arasında dolaştığı mobil veya dizüstü dağıtımlarında, bir sonraki aralık tıkını beklemek yerine hemen bir yeniden bağlanmayı tetiklemek için işletim sistemi ağ-değişikliği bildirimini de (.NET'te SystemEvents.NetworkAvailabilityChanged, Windows-Delphi'de WMI yolu) bağlayın.

Hata 2: OnMessage Olayını Bloke Etmek

Belirti: yük altında verim çöker. Sunucu "takılmış" gibi hisseder. CPU düşüktür, ancak mesajlar birikir.

Neden: OnMessage, G/Ç çalışan iş parçacığında çalışır. İçinde yavaş bir veritabanı, harici bir HTTP API veya uzun süre çalışan bir ayrıştırıcı çağırırsanız, o çalışanı paylaşan diğer her bağlantıyı aç bırakırsınız.

// 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;

Hata 3: HeartBeat'i Göz Ardı Etmek

Belirti: hayalet bağlantılar birikir. İstemcilerin öldüğünü bilmenize rağmen Connections.Count büyümeye devam eder.

Neden: uygulama katmanı heartbeat'leri olmadan, işletim sistemi TCP keepalive zamanlayıcısı yaklaşık 2 saat sonra tetiklenir. O zamana kadar belleği tüketen on binlerce zombi soketiniz olur.

oServer.HeartBeat.Enabled  := True;
oServer.HeartBeat.Interval := 30;
oServer.HeartBeat.Timeout  := 90;

Hata 4: Uyumsuz TLS Sürümleri

Belirti: istemci "handshake failed" veya "SSL_ERROR_SYSCALL" alır. Bağlantı bir geliştirme makinesinde çalışır ancak kurumsal bir proxy arkasında üretimde başarısız olur.

Neden: istemci SSL seçenekleri geriye dönük uyumluluk için varsayılan olarak TLS 1.0–1.2'dir; modern sunucular yalnızca TLS 1.2/1.3 gerektirir. Veya OpenSSL DLL'leri eskidir.

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.

Hata 5: Parçalı Mesajları Ele Almamak

Belirti: sunucu büyük bir JSON belgesinin ilk bölümünü alır, ayrıştırıcınız başarısız olur ve bağlantı kapatılır.

Neden: varsayılan olarak, sgcWebSockets parçaları sizin için yeniden birleştirir ve yalnızca tam yükle OnMessage'ı tetikler. Ancak bellek nedenleriyle ReadOptions.FragmentMode := frgPartial ayarladıysanız, yeniden birleştirmeniz gerekir.

// 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;

Hata 6: GUI İş Parçacığında Senkron API Kullanmak

Belirti: bağlanırken veya yavaş bir bağlantıda WriteData çağrılırken UI birkaç saniye donar.

Neden: ana iş parçacığında bloke eden çağrılar. VCL/FMX uygulamalarında her zaman async desenini kullanın veya bir çalışan iş parçacığından çağırın.

// 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

Hata 7: Bir Alt Protokol Seçmeyi Unutmak

Belirti: bağlantı başarılı olur ancak eş her çerçeveyi reddeder veya beklenenden farklı bir biçimde yanıt verir.

Neden: birçok WebSocket sunucusu (MQTT-over-WS, STOMP, GraphQL-WS, Phoenix) el sıkışmasında belirli bir alt protokol gerektirir. Bu olmadan, sunucu varsayılan olarak farklı bir protokole geçer veya bağlantınızı düşürür.

// 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');

Hata 8: Arabellek Boyutları Çok Küçük (veya Çok Büyük)

Belirti: birçok küçük mesaj gönderen bir sunucuda yüksek CPU veya büyük mesajlar gönderen bir sunucuda bellek yetersizliği.

Neden: varsayılan gönderme/alma arabellekleri genel kullanım için boyutlandırılmıştır. Bunları trafik şeklinize göre ayarlayın.

// 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;

Hata 9: Genel Sunucularda Origin Denetimi Yapmamak

Belirti: güvenlik denetimi "herhangi bir web sitesi, bir kullanıcının tarayıcısı aracılığıyla WebSocket'inize bağlanabilir" uyarısı verir.

Neden: WebSocket protokolü aynı kaynağı (same-origin) zorunlu kılmaz. Sunucunuz Origin başlığını doğrulamalıdır.

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;

Hata 10: OnMessage'da Hassas Verileri Günlüğe Kaydetmek

Belirti: denetçi günlük dosyalarında API anahtarları, JWT'ler veya PII bulur. Uyumluluk sorunu.

Neden: OnMessage içindeki kolay LogMemo.Lines.Add(Text), her yükü sonsuza kadar diske yazar.

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;

Bonus: History.txt'i Okumamak

Her sürüm, 2013'ten bu yana her değişikliği, düzeltmeyi ve bozucu notu listeleyen bir history.txt ile gelir. Her yükseltmeden sonra ona göz atmak için harcanan beş dakika, daha sonra saatlerce süren "bu neden çalışmayı durdurdu" hata ayıklamasından kurtarır.

Bonus 2: Projeler Arasında Bileşen Sürümlerini Karıştırmak

Delphi geliştiricileri bazen daha yeni bir sgcWebSockets sürümünden eski bir projeye tek bir .pas kopyalar, "sadece bu tek dosya". Bu, işe yaramayana kadar işe yarar. Dosya, iki sürüm önce değişen türlere bağımlıdır ve bağlayıcı gizemli bir şekilde başarısız olur veya daha kötüsü, bağlanır ama çalışma zamanında çöker. Her zaman tüm kütüphaneyi birlikte yükseltin. Bir dosyayı kopyalayarak tasarruf edilen 30 saniye, akış aşağısında bir şey bozulduğunda dört saatlik hata ayıklamaya değmez.

Bonus 3: WebSocket'i Kur-ve-Unut Olarak Görmek

WebSocket bir mesaj kuyruğu değildir. TCP üzerinden çift yönlü bir bayt akışıdır. Ağ mesaj ortasında düşerse, çerçeve kaybolur ve hiçbir zaman otomatik olarak yeniden teslim edilmez. İş açısından kritik mesajlar için üstüne kendi onay protokolünüzü eklemeniz gerekir; genellikle mesaj başına bir UUID, alıcıdan açık bir ACK ve bir zaman aşımından sonra göndericide bir yeniden gönderme. Bu katmanı atlamak "kullanıcı yazıyor" bildirimleri için sorun değildir ve "kullanıcı sepet için ödeme yaptı" için ölümcüldür.

Bonus 4: Memo'ların Bellek Sızdırmasına İzin Vermek

Belirti: hata ayıklama formunuzdaki tanılama memosu, birkaç saatlik trafikten sonra istemcinizi belleksiz bırakacak (OOM) ilk şeydir. sgcWebSockets'i suçlarsınız; sgcWebSockets masumdur.

Neden: bir TMemo, eklenen her satırı saklar. Saniyede 100 satırda bu, saatte 360.000 satır eder. Her satır bir dize tahsis eder. VCL, her WM_PAINT'te binlerce görünmez satırı işler. Kütüphane yanlış bir şey yapmazken işleminiz durma noktasına gelir.

// 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;

Daha da iyisi: LoggerPro aracılığıyla dönen bir dosyaya günlük kaydedin ve görsel hata ayıklama için yalnızca son 200 satırı memoya yansıtın. Üretim kodu, bir ağ iş parçacığından bir UI denetimine asla yazmamalıdır.

Bonus 5: Application.Terminate'ten Önce Sunucuyu Kapatmamak

Belirti: uygulama kapanışında işlem 30 saniye askıda kalır veya bağlantılar düzgün olmayan şekilde sonlandırıldığı için işletim sistemi istemci günlüklerinde ele alınmayan istisnalar bildirir.

Neden: sunucu yıkıcısı her bağlantıya bir close çerçevesi gönderir ve işletim sisteminin dinleme bağlantı noktasını serbest bırakmasını bekler. oServer.Active := False'tan önce Application.Terminate çağırırsanız, bağlantılar el sıkışma ortasında ölür ve işletim sistemi bağlantı noktası TIME_WAIT durumunda kalarak hızlı bir yeniden başlatmayı engeller.

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;

Konsol sunucuları için, Windows'ta SetConsoleCtrlHandler'ı veya Linux'ta SIGTERM'i bağlayın ve aynı kapatma dizisini çalıştırın. Bunu hizmet yöneticinizdeki bir HUP/yeniden başlatma döngüsüyle eşleştirirseniz, sıfır-düşen-bağlantılı dağıtımlara sahip olursunuz.

Desenlerin Ardındaki Desen

Bu hataların çoğunun ortak bir kökü var: ağın güvenilir olduğunu varsaymak. Değildir. Yarı açık TCP bağlantıları olur. Mobil ağlar düşer. Kurumsal proxy'ler TLS'i bozar. Wi-Fi dolaşır. Sunucular yeniden başlar. Bulut yük dengeleyiciler 60 saniye sonra boştaki bağlantıları sonlandırır. Bu koşullarda hayatta kalmayan bir WebSocket uygulaması bitmiş değildir, bir mutlu yol (happy-path) demosudur. İyi haber: kütüphane bu senaryoların her biri için bir denetim sunar. Kötü haber: çoğu, geriye dönük uyumluluk için varsayılan olarak kapalıdır. Belgelerin WatchDog, HeartBeat, Reconnect ve TLS seçeneği sayfalarını okumak için harcanan iki saat, satın alacağınız en ucuz sigortadır.

İkincil desen: G/Ç iş parçacığına saygı gösterin. Bir milisaniyeden fazla süren her şey (veritabanı sorgusu, dosya G/Ç'si, harici HTTP çağrısı, uzun bir dizede regex, 100 KB'lık bir yükte JSON ayrıştırma) OnMessage'a değil, bir çalışan iş parçacığına aittir. Bu kuralı mutlak kılın. Yeni geliştiriciler bunu üç ay sonra ihlal edecektir; "OnMessage'da bloke etme yok" maddesini içeren bir kod inceleme listesi, dağıtılmadan önce bunu yakalar.

Bundan Sonra Nereye Gidilir

Yüksek trafikli bir sunucuyu ayarlıyorsanız, sırada sgcWebSockets Performans Ayarlaması'nı okuyun. Kütüphanede yeni misiniz? Başlarken merkezinden başlayın ve ilk WebSocket'inizi beş dakikada bağlayın.