Wprowadzenie: Konfigurowanie Zachowania Gniazd
Podczas tworzenia gniazda system operacyjny ustawia zachowanie domyślne przeznaczone dla typowych obciążeń. Domyślne wartości muszą równoważyć zużycie zasobów, opóźnienie i zgodność z różnymi aplikacjami.
Socket API udostępnia te wartości domyślne dla gniazd TCP i UDP. Aplikacja do handlu wysokoczęstotliwościowego, aplikacja strumieniująca wideo oraz narzędzie do przesyłania plików mogą mieć odmienne wymagania dotyczące opóźnienia, przepustowości i limitów czasu, zatem pojedynczy zestaw ustawień nie pasuje do każdego obciążenia.
W tym celu stosuje się opcje gniazd(ang. socket options). Pozwalają one zażądać zmiany zachowania gniazda oraz funkcji udostępnianych przez protokół transportowy. Aplikacja może ustawić rozmiary buforów, limity czasu operacji wejścia i wyjścia oraz funkcje protokołu, przy czym system nadal stosuje własne limity i reguły walidacji. Opcje na poziomie 'SOL_SOCKET' opisują ogólne zachowanie gniazda, natomiast opcje na poziomie 'IPPROTO_TCP' wymagają obsługi przez TCP. Poziom jest częścią identyfikacji opcji, dlatego poprawna nazwa przypisana do błędnego poziomu może zakończyć wywołanie błędem.
Ustawianie i Odczyt Opcji: 'setsockopt' oraz 'getsockopt'
Głównym narzędziem, które Socket API dostarcza do manipulowania tymi zachowaniami, jest funkcja o nazwie 'setsockopt' (Ustaw Opcję Gniazda). Istnieje również odpowiadająca jej funkcja 'getsockopt' do odpytywania o bieżącą wartość opcji.
Wywołanie przekazuje do stosu sieciowego systemu operacyjnego żądaną wartość dla konkretnego gniazda. Stos sprawdza wartość i stosuje ją, jeśli dana opcja jest obsługiwana na wybranym poziomie i dla wybranego protokołu. Program powinien sprawdzić wynik wywołania oraz ustawić opcję w wymaganym momencie cyklu życia gniazda, ponieważ część ustawień obowiązuje dla kolejnych połączeń albo musi poprzedzać 'bind()' lub 'connect()'.
Funkcja 'setsockopt' generalnie przyjmuje pięć parametrów:
- Deskryptor Gniazda: Liczba identyfikująca gniazdo, które ma zostać zmodyfikowane.
- Poziom:Określa warstwę obsługującą opcję. Dla ogólnych ustawień gniazda jest to 'SOL_SOCKET', a dla opcji specyficznych dla TCP 'IPPROTO_TCP'.
- Nazwa Opcji:Stała nazywająca funkcję, którą chcesz zmienić, na przykład 'SO_KEEPALIVE' lub 'TCP_NODELAY'.
- Wartość Opcji: Wskaźnik do zmiennej zawierającej nowe ustawienie, które chcesz zastosować. Może to być prosta liczba całkowita (np. 1, aby włączyć funkcję, 0, aby ją wyłączyć) lub bardziej złożona struktura danych (np. struktura określająca czas oczekiwania).
- Długość Opcji:Rozmiar danych 'wartości_opcji' w bajtach.
Wybrane Opcje Gniazd
Istnieją dziesiątki opcji gniazd. Poniższe przykłady obejmują bufory, limity czasu wejścia i wyjścia, podtrzymywanie TCP, algorytm Nagle'a, rozgłaszanie IPv4 oraz ponowne użycie adresu lokalnego. Nazwy i dokładne zachowanie opcji nie są przenośnym kontraktem, dlatego aplikacja powinna sprawdzić dokumentację systemu operacyjnego i protokołu.
SO_RCVBUF i SO_SNDBUF: Rozmiary Buforów
Problem: System operacyjny przydziela bufor odbiorczy i bufor nadawczy dla każdego połączenia TCP. Rozmiar tych buforów może być krytycznym wąskim gardłem wydajności. W szczególności, rozmiar bufora odbiorczego bezpośrednio wpływa na , które host może ogłosić. W sieci o wysokim iloczynie przepustowości i opóźnienia (BDP) mały bufor doprowadzi do małego 'rwnd', uniemożliwiając nadawcy utrzymanie w drodze ilości danych pozwalającej wykorzystać pojemność ścieżki i tym samym ograniczając przepustowość połączenia.
Rozwiązanie:Opcje 'SO_RCVBUF' i 'SO_SNDBUF' pozwalają aplikacji zażądać większych buforów odbiorczych i nadawczych. Bufor odbiorczy co najmniej tak duży jak BDP połączenia może usunąć jedno wąskie gardło okna odbiorczego, lecz system może ograniczyć lub dostroić wartość, a na przepustowość wpływają także inne ograniczenia ścieżki.
Ważna uwaga:Jest to żądanie, więc jądro stosuje limity chroniące pamięć systemową. W Linuksie jądro podwaja wartość przekazaną do 'SO_RCVBUF' lub 'SO_SNDBUF' na potrzeby obsługi wewnętrznej i zwraca podwojoną wartość przez 'getsockopt', z uwzględnieniem maksimum systemowego. Inne systemy mogą stosować odmienne reguły.
Iloczyn przepustowości i opóźnienia (BDP) oznacza przepustowość ścieżki pomnożoną przez czas obiegu, więc określa przybliżoną liczbę bajtów mogących znajdować się w drodze przed powrotem potwierdzenia. Większy bufor może usunąć lokalne ograniczenie na długiej, szybkiej ścieżce, lecz nie zwiększa przepustowości łącza i nie zastępuje kontroli przeciążenia TCP. Automatyczne dostrajanie może zmieniać wartość podczas połączenia, dlatego pomiar powinien uwzględniać wartość zwracaną przez system.
SO_RCVTIMEO i SO_SNDTIMEO: Limity Czasu Odbioru i Wysyłania
Problem:W gnieździe blokującym wywołanie takie jak 'recv()' czeka, gdy nie ma danych. Bez limitu czasu albo mechanizmu gotowości partner, który nigdy nie wysyła danych, może utrzymywać wywołanie w oczekiwaniu bez końca, co komplikuje obsługę wielu klientów.
Rozwiązanie:Opcje 'SO_RCVTIMEO' i 'SO_SNDTIMEO' ustawiają limity czasu odpowiednio dla operacji odbioru i wysyłania. W Linuksie, gdy blokujące wywołanie wejścia i wyjścia zakończyło się bez przeniesienia danych przed upływem skonfigurowanego limitu odbioru, zwraca ono błąd, taki jak EAGAIN lub EWOULDBLOCK. Częściowy transfer może zwrócić liczbę przeniesionych bajtów. Opcje wpływają na wywołania wejścia i wyjścia gniazda, lecz nie na 'poll()' ani 'select()'.
Zerowa wartość limitu pozostawia operację bez tego ograniczenia na poziomie gniazda, co różni się od krótkiego terminu aplikacyjnego. Wartość jest przekazywana w strukturze czasu systemu operacyjnego, a zaokrąglenie i raportowanie błędów mogą zależeć od platformy. Programy zdarzeniowe często używają mechanizmu gotowości z jednym terminem dla wielu deskryptorów, a limit gniazda traktują jako dodatkowe zabezpieczenie.
SO_KEEPALIVE: Wykrywanie Martwych Połączeń
Problem: Po utracie zasilania lub łączności klient może nie wysłać FIN zamykającego połączenie TCP. Serwer może wtedy zachować pamięć i deskryptory gniazd, gdy połączenie wygląda na bezczynne. Wiele takich półotwartych połączeń może zużyć zasoby serwera.
Rozwiązanie:Opcja 'SO_KEEPALIVE' włącza mechanizm podtrzymywania aktywności TCP dla danego gniazda. W Linuksie domyślny czas bezczynności wynosi 7200 sekund, czyli 2 godziny. Następnie stos używa sond, domyślnie 9 w odstępach 75 sekund. Odpowiedź potwierdza osiągalność, a powtarzające się niepowodzenia mogą zakończyć połączenie. Inne systemy mają odmienne wartości, a tak długie limity nie służą do szybkich testów stanu aplikacji.
Sondy podtrzymywania aktywności sprawdzają warstwę transportową i nie potwierdzają sprawności procesu aplikacji ani możliwości wykonania żądania. Usługi wymagające krótszego okresu kontroli zwykle implementują własny komunikat heartbeat z określonym interwałem i działaniem po awarii, pozostawiając TCP obsługę długo nieaktywnych połączeń.
TCP_NODELAY: Wyłączanie Algorytmu Nagle'a
Problem: Aby poprawić wydajność sieci, TCP używa . Algorytm ten buforuje małe porcje wychodzących danych i czeka na potwierdzenie wcześniejszych danych albo na zebranie ilości wystarczającej do wypełnienia segmentu. Ogranicza to narzut przy przesyłaniu dużych ilości danych, lecz może zwiększyć opóźnienie aplikacji interaktywnych, takich jak sesje terminalowe SSH i gry, w których każde polecenie powinno zostać szybko przekazane.
Rozwiązanie:Opcja 'TCP_NODELAY' na poziomie 'IPPROTO_TCP' wyłącza algorytm Nagle'a dla danego gniazda. Stos może wtedy wysyłać małe segmenty bez oczekiwania wymaganego przez ten algorytm, zmniejszając opóźnienie widoczne dla aplikacji kosztem większej liczby pakietów i większego stosunku nagłówka do ładunku.
Wybór zależy od protokołu aplikacyjnego. Przesyłanie dużych bloków zwykle korzysta z agregacji, natomiast protokół żądanie-odpowiedź z małymi zapisami wrażliwymi na opóźnienie może preferować natychmiastową transmisję. Ustawienie opcji 'TCP_NODELAY' nie omija kontroli przeciążenia, segmentacji, potwierdzeń ani okna ogłaszanego przez odbiorcę.
SO_BROADCAST: Włączanie Rozgłoszeń UDP
Problem: Rozgłoszenie IPv4 jest kierowane do wszystkich hostów w sieci lokalnej i obciąża każdy host, który je odbiera. Platformy chronią wysyłanie rozgłoszeń przez flagi lub uprawnienia gniazda, lecz wartości domyślne zależą od implementacji.
Rozwiązanie: Protokoły takie jak DHCP używają rozgłaszania UDP podczas konfiguracji adresu. W Linuksie ustawienie 'SO_BROADCAST' na gnieździe UDP ('SOCK_DGRAM') zezwala na wysyłanie datagramów na adres rozgłoszeniowy. jest przykładem dla podsieci 192.168.1.0/24. Właściwy adres rozgłoszenia kierowanego zależy od maski. W przypadku TCP ta opcja pozostaje bez zastosowania.
Adres ograniczonego rozgłoszenia IPv4 obejmuje sieć lokalną i nie jest routowany. Rozgłoszenie kierowane, takie jak , zależy od prefiksu interfejsu oraz lokalnej polityki przekazywania. Ruch rozgłoszeniowy zwykle ogranicza się więc do lokalnego zakresu i stosuje tylko wtedy, gdy wymaga go protokół.
SO_REUSEADDR: Ponowne Użycie Adresu Lokalnego
Problem:Po zamknięciu połączenia TCP punkt końcowy może pozostać w stanie 'TIME_WAIT' przez 2MSL. Czas trwania i reguły wiązania zależą od implementacji. Jeśli serwer WWW na porcie 80 ulegnie awarii i zostanie natychmiast uruchomiony ponownie, 'bind()' może się nie powieść, gdy stare połączenie nadal koliduje z adresem lokalnym i portem.
Rozwiązanie:Ustawienie 'SO_REUSEADDR' przed 'bind()' żąda ponownego użycia zgodnie z regułami walidacji adresu danej platformy. W Linuksie może pozwolić na wiązanie, gdy jedynym konfliktem jest gniazdo w stanie 'TIME_WAIT', lecz nie zastępuje aktywnego gniazda nasłuchującego i nie omija każdego konfliktu. Linux wymaga także ustawienia opcji w poprzednim i bieżącym programie wiążącym port. Inne systemy mogą działać inaczej. Opcja może skrócić restart serwera, lecz nie gwarantuje bezwarunkowo natychmiastowego ponownego użycia.
Stan TIME_WAIT chroni nowe połączenie przed opóźnionymi segmentami należącymi do wcześniejszego połączenia z tym samym zestawem punktów końcowych. Ponowne użycie nadal zależy od pełnej krotki adresów i portów, dlatego samo ustawienie opcji lokalnej nie czyni każdego wiązania poprawnym. Znaczenie opcji zależy od platformy i nie należy traktować jej jako przenośnego zamiennika osobnego mechanizmu współdzielenia portu.