IOCP

Obsługiwane przez

 

TsgcWebSocketServer

TsgcWebSocketHTTPServer

 

*Wymaga niestandardowej wersji Indy.

 

IOCP dla systemu Windows to API umożliwiające obsługę tysięcy połączeń przy użyciu ograniczonej puli wątków, zamiast przydzielania jednego wątku na połączenie, jak robi to Indy domyślnie.

Aby włączyć IOCP dla serwerów Indy, przejdź do właściwości IOHandlerOptions i wybierz iohIOCP jako typ IOHandler.


Server.IOHandlerOptions.IOHandlerType := iohIOCP;
Server.IOHandlerOptions.IOCP.IOCPThreads := 0;
Server.IOHandlerOptions.IOCP.WorkOpThreads := 0;
Server.IOHandlerOptions.IOCP.PartialRequestTimeout := 0;
Server.IOHandlerOptions.IOCP.TCPKeepAlive.Enabled := True;
Server.IOHandlerOptions.KeepAliveTimeout := 120;

 

IOCPThreads to wątki używane do asynchronicznych żądań IOCP (operacje nakładkowe); domyślnie wartość wynosi zero, co oznacza, że liczba wątków jest obliczana na podstawie liczby procesorów (z wyjątkiem Delphi 7 i 2007, gdzie liczba wątków jest ustawiona na 32, ponieważ funkcja cpucount nie jest obsługiwana).

WorkOpThreads określa minimalną liczbę wątków roboczych utrzymywanych w gotowości do przetwarzania żądań, domyślnie 0. Wątek IOCP jedynie odbiera zakończoną operację i przekazuje pracę do tej puli, dzięki czemu nigdy nie zostaje zablokowany przez wolnego klienta. Pula rośnie na żądanie aż do ustalonego maksimum zawsze wtedy, gdy wszystkie wątki robocze są już zajęte, nie trzeba więc ustalać jej rozmiaru z góry. Wartość tę należy zwiększyć tylko wtedy, gdy pula ma być przygotowana jeszcze przed pierwszym żądaniem, co pozwala uniknąć kosztu tworzenia wątków podczas początkowej fali połączeń.

Zmiana w wersji 2026.9.0. WorkOpThreads przypisywał wcześniej każde połączenie na stałe do jednego z tych wątków, przez co wszystkie żądania w ramach danego połączenia były wykonywane w tym samym wątku, a WorkOpRoundRobin decydował o tym, w jaki sposób nowe połączenia są rozdzielane pomiędzy te wątki. Przypisywanie na stałe zostało usunięte: połączenie oczekujące na resztę żądania blokowało wszystkie pozostałe połączenia przypisane do tego samego wątku, co zatrzymywało pracę serwera. Żądania w ramach jednego połączenia mogą teraz być wykonywane w różnych wątkach, dlatego nie wolno już zakładać, że dane przechowywane osobno dla każdego wątku, takie jak połączenie z bazą danych czy pamięć podręczna lokalna dla wątku, podążają za połączeniem. Właściwość WorkOpRoundRobin już nie istnieje. Należy usunąć ją z kodu, a jeśli została włączona w Object Inspector, trzeba również usunąć wiersz WorkOpRoundRobin z pliku .dfm formularza, w przeciwnym razie formularz nie zostanie wczytany.

PartialRequestTimeout pozwala serwerowi poczekać na resztę treści żądania HTTP, której długość jest już znana, w milisekundach, domyślnie 0, co wyłącza tę funkcję. Bez niej treść żądania, która nie napłynie w całości w ramach jednego zakończenia operacji, na przykład duże żądanie POST wysłane w kilku pakietach, zostaje po cichu odrzucona, a połączenie zamknięte bez odpowiedzi. Oczekiwanie nigdy nie odbywa się w wątku IOCP, który obsługuje wiele połączeń i nigdy nie może zostać zablokowany; działa ono w opisanej powyżej puli wątków roboczych, która rośnie na żądanie aż do ustalonego maksimum, dzięki czemu wolny lub nieodpowiadający klient nie może jej wyczerpać i zablokować pozostałych połączeń.

TCPKeepAlive umożliwia systemowi operacyjnemu sondowanie bezczynnych połączeń i wykrywanie sytuacji, gdy druga strona połączenia zniknęła bez powiadomienia (domyślnie wyłączone). Gdy TCPKeepAlive.Enabled jest ustawione na True, system operacyjny zaczyna sondować połączenie po okresie bezczynności określonym przez TCPKeepAlive.Time w sekundach (domyślnie 60), powtarzając sondowanie co TCPKeepAlive.Interval sekund (domyślnie 10), aż druga strona odpowie lub połączenie zostanie zerwane.

KeepAliveTimeout zamyka każde połączenie, które pozostawało bezczynne dłużej niż podana liczba sekund (domyślnie 0, co oznacza wyłączenie tej funkcji). Dotyczy to wyłącznie połączeń bezczynnych. Żądanie, które jest właśnie odczytywane, lub odpowiedź, która jest właśnie zapisywana, nigdy nie zostają przerwane przez ten limit czasu. Jest to rozwiązanie najbardziej zbliżone do sposobu, w jaki domyślny IOHandler oparty na modelu jeden wątek na połączenie zwalnia nieaktywne połączenie, i nie zależy ono ani od systemu operacyjnego, ani od współpracy drugiej strony połączenia.

 

Włączenie IOCP dla serwerów Windows jest zalecane, gdy wymagana jest obsługa tysięcy połączeń. Jeśli serwer obsługuje maksymalnie 100 jednoczesnych połączeń, można pozostać przy domyślnym modelu wątkowym Indy.

 

 

Zdarzenie OnDisconnect nie jest wywoływane

 

IOCP działa inaczej niż domyślny IOHandler biblioteki Indy. W przypadku domyślnego IOHandler Indy każde połączenie jest obsługiwane przez osobny wątek, który działa przez cały czas, sprawdzając, czy połączenie jest aktywne; w razie rozłączenia zostaje ono zgłoszone w krótkim czasie.

 

IOCP działa inaczej: istnieje pula wątków obsługująca wszystkie połączenia, zamiast modelu 1 wątek = 1 połączenie, jak to robi domyślnie Indy. W IOCP jedynym sposobem wykrycia aktywności połączenia jest próba zapisu do gniazda. Jeśli wystąpi błąd, oznacza to, że połączenie zostało zamknięte. Dostępne są 3 opcje wykrywania rozłączeń:

 

1. Jeśli jest używany komponent TsgcWebSocketClient, można go włączyć we właściwości Options, ustawiając CleanDisconnect := True (domyślnie wyłączone). Po włączeniu, przed rozłączeniem klienta wysyłana jest wiadomość informująca serwer o rozłączeniu, dzięki czemu serwer odbierze tę wiadomość i zostanie wywołane zdarzenie OnDisconnect.

2. Można włączyć funkcję heartbeat po stronie serwera, na przykład co 60 sekund. Serwer będzie wówczas próbował wysyłać ping do wszystkich podłączonych klientów, a w przypadku wykrycia rozłączonego klienta zostanie wywołane zdarzenie OnDisconnect.

3. Można włączyć TCPKeepAlive i/lub ustawić KeepAliveTimeout we właściwości IOHandlerOptions serwera. Pozwalają one samemu serwerowi wykryć drugą stronę połączenia, która przestała odpowiadać, na przykład wskutek awarii klienta lub przerwania połączenia sieciowego bez wysłania komunikatu zamknięcia, i zamknąć takie połączenie, bez polegania na tym, że klient cokolwiek wyśle.