TCP: czy dwa różne gniazda mogą współużytkować port?

124

To może być bardzo podstawowe pytanie, ale wprawia mnie w zakłopotanie.

Czy dwa różne połączone gniazda mogą współużytkować port? Piszę serwer aplikacji, który powinien obsłużyć ponad 100 tys. Jednoczesnych połączeń i wiemy, że liczba portów dostępnych w systemie wynosi około 60 tys. (16 bitów). Połączone gniazdo jest przypisane do nowego (dedykowanego) portu, co oznacza, że ​​liczba jednoczesnych połączeń jest ograniczona liczbą portów, chyba że wiele gniazd może współużytkować ten sam port. Więc pytanie.

Z góry dziękuję za pomoc!

KJ
źródło

Odpowiedzi:

175

Serwer gniazdo nasłuchuje na jednym porcie. Wszystkie nawiązane połączenia klientów na tym serwerze są skojarzone z tym samym portem nasłuchiwania po stronie serwera połączenia. Ustanowione połączenie jest jednoznacznie identyfikowane przez kombinację par IP / Port po stronie klienta i po stronie serwera. Wiele połączeń na tym samym serwerze może współdzielić tę samą parę adresów IP / portów po stronie serwera, o ile są one powiązane z różnymi parami adresów IP / portów po stronie klienta , a serwer byłby w stanie obsłużyć tyle klientów, na ile pozwalają na to dostępne zasoby systemowe do.

Po stronie klienta powszechną praktyką w przypadku nowych połączeń wychodzących jest używanie losowego portu po stronie klienta. W takim przypadku możliwe jest wyczerpanie dostępnych portów, jeśli wykonujesz wiele połączeń w krótkim czasie.

Remy Lebeau
źródło
2
Dzięki za odpowiedź, Remy! Twoja odpowiedź jest wszystkim, co mnie zaciekawiło. ;)
KJ,
2
@Remy Połączenia są rozróżniane nie tylko ze względu na port źródłowy / docelowy / IP, ale także protokół (TCP, UDP itp.), Jeśli się nie mylę.
Ondrej Peterka
1
@OndraPeterka: tak, ale nie wszystkie platformy to ograniczają. Na przykład Windows szczęśliwie pozwala oddzielnym gniazdom serwerów IPv4 i IPv6 nasłuchiwać na tym samym lokalnym IP: Port bez przeskakiwania przez obręcze, ale systemy * Nix (w tym Linux i Android) nie.
Remy Lebeau
6
@ user2268997: Nie można używać jednego gniazda do łączenia się z wieloma serwerami. Musisz utworzyć oddzielne gniazdo dla każdego połączenia.
Remy Lebeau
3
@FernandoGonzalezSanchez: Pojedynczy klient może mieć wiele gniazd TCP powiązanych z tą samą lokalną parą IP / Port, o ile są one połączone z różnymi zdalnymi parami IP / Port. To nie jest specyficzne dla systemu Windows, to jest część ogólnego działania protokołu TCP.
Remy Lebeau,
182

Nasłuchiwanie TCP / HTTP na portach: jak wielu użytkowników może współużytkować ten sam port

Więc co się dzieje, gdy serwer nasłuchuje połączeń przychodzących na porcie TCP? Na przykład, powiedzmy, że masz serwer WWW na porcie 80. Załóżmy, że Twój komputer ma publiczny adres IP 24.14.181.229, a osoba, która próbuje się z tobą połączyć, ma adres IP 10.1.2.3. Ta osoba może się z Tobą połączyć, otwierając gniazdo TCP pod numer 24.14.181.229:80. Wystarczająco proste.

Intuicyjnie (i niesłusznie) większość ludzi zakłada, że ​​wygląda to mniej więcej tak:

    Local Computer    | Remote Computer
    --------------------------------
    <local_ip>:80     | <foreign_ip>:80

    ^^ not actually what happens, but this is the conceptual model a lot of people have in mind.

Jest to intuicyjne, ponieważ z punktu widzenia klienta ma on adres IP i łączy się z serwerem pod adresem IP: PORT. Skoro klient łączy się z portem 80, to jego port też musi mieć 80? To rozsądna rzecz, aby pomyśleć, ale tak naprawdę nie to, co się dzieje. Gdyby to było poprawne, moglibyśmy obsługiwać tylko jednego użytkownika na obcy adres IP. Gdy komputer zdalny się połączy, wtedy połączy port 80 z portem 80 i nikt inny nie będzie mógł się połączyć.

Należy zrozumieć trzy rzeczy:

1.) Na serwerze proces nasłuchuje na porcie. Gdy uzyska połączenie, przekazuje je innemu wątkowi. Komunikacja nigdy nie blokuje portu nasłuchującego.

2.) Połączenia są jednoznacznie identyfikowane przez system operacyjny za pomocą następujących 5 krotek: (lokalny-IP, lokalny-port, zdalny-IP, zdalny-port, protokół). Jeśli jakikolwiek element w krotce jest inny, jest to całkowicie niezależne połączenie.

3.) Kiedy klient łączy się z serwerem, wybiera losowy, nieużywany port źródłowy wysokiego rzędu . W ten sposób pojedynczy klient może mieć do ~ 64 tys. Połączeń z serwerem dla tego samego portu docelowego.

Tak więc tak naprawdę powstaje, gdy klient łączy się z serwerem:

    Local Computer   | Remote Computer           | Role
    -----------------------------------------------------------
    0.0.0.0:80       | <none>                    | LISTENING
    127.0.0.1:80     | 10.1.2.3:<random_port>    | ESTABLISHED

Patrząc na to, co się właściwie dzieje

Najpierw użyjmy netstat, aby zobaczyć, co się dzieje na tym komputerze. Będziemy używać portu 500 zamiast 80 (ponieważ na porcie 80 dzieje się cała masa rzeczy, ponieważ jest to port wspólny, ale funkcjonalnie nie robi to różnicy).

    netstat -atnp | grep -i ":500 "

Zgodnie z oczekiwaniami dane wyjściowe są puste. Teraz uruchommy serwer WWW:

    sudo python3 -m http.server 500

Oto wynik ponownego uruchomienia netstata:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      - 

Więc teraz istnieje jeden proces, który aktywnie nasłuchuje (stan: LISTEN) na porcie 500. Lokalny adres to 0.0.0.0, co oznacza kod „nasłuchiwania wszystkich adresów IP”. Łatwym błędem jest nasłuchiwanie tylko na porcie 127.0.0.1, który akceptuje tylko połączenia z bieżącego komputera. Więc to nie jest połączenie, to po prostu oznacza, że ​​proces zażądał powiązania () z adresem IP portu, a ten proces jest odpowiedzialny za obsługę wszystkich połączeń do tego portu. Wskazuje to na ograniczenie polegające na tym, że na jeden komputer może nasłuchiwać tylko jeden proces na porcie (istnieją sposoby obejścia tego problemu za pomocą multipleksowania, ale jest to znacznie bardziej skomplikowany temat). Jeśli serwer WWW nasłuchuje na porcie 80, nie może współdzielić tego portu z innymi serwerami WWW.

Więc teraz połączmy użytkownika z naszą maszyną:

    quicknet -m tcp -t localhost:500 -p Test payload.

Jest to prosty skrypt ( https://github.com/grokit/quickweb ), który otwiera gniazdo TCP, wysyła ładunek (w tym przypadku „Ładunek testowy”), czeka kilka sekund i rozłącza się. Ponowne wykonanie netstat w tym czasie powoduje wyświetlenie następujących informacji:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      -
    tcp        0      0 192.168.1.10:500        192.168.1.13:54240      ESTABLISHED -

Jeśli połączysz się z innym klientem i ponownie wykonasz netstat, zobaczysz następujące informacje:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      -
    tcp        0      0 192.168.1.10:500        192.168.1.13:26813      ESTABLISHED -

... to znaczy klient użył innego losowego portu do połączenia. Dlatego nigdy nie ma pomyłki między adresami IP.

Nic
źródło
11
To najlepsza odpowiedź, jaką kiedykolwiek widziałem w SO.
Praca
1
@ N0thing "W ten sposób pojedynczy klient może mieć do ~ 64k połączeń z serwerem dla tego samego portu docelowego." Tak więc w praktyce, jeśli klient nie łączy się z tym samym serwerem i portem, dwa lub wiele razy jednocześnie, klient może mieć nawet więcej niż ~ 64 tys. Połączeń. Czy to prawda. Jeśli tak, oznacza to, że z jednego portu po stronie klienta może on mieć połączenie z wieloma różnymi procesami serwera (np. Połączenie przez gniazdo jest inne). Zatem w sumie wiele gniazd klienta może znajdować się na tym samym porcie na komputerze klienckim? Przeczytaj mój komentarz do odpowiedzi „Remey Lebeau”. Dzięki: D
Prem KTiw
6
@premktiw: Tak, wiele gniazd klienta może być związanych z tą samą parą lokalnego IP / portu w tym samym czasie, jeśli są one połączone z różnymi parami IP / port serwera, więc krotki par lokalny + zdalny są unikalne. I tak, klient może mieć łącznie ponad 64 tys. Jednoczesnych połączeń. Z jednego portu można go podłączyć do potencjalnie nieskończonej liczby serwerów (ograniczonych dostępnymi zasobami systemu operacyjnego, dostępnymi portami routera itp.), O ile pary IP / port serwera są niepowtarzalne.
Remy Lebeau
1
@RemyLebeau Satisfied. Dziękuję bardzo: D
Prem KTiw
1
@bibstha W jaki sposób firewall radzi sobie z losowymi portami, gdy wszystkie połączenia przychodzące są odrzucane?
PatrykG
35

Podłączone gniazdo jest przypisywane do nowego (dedykowanego) portu

To powszechna intuicja, ale jest niepoprawna. Podłączone gniazdo nie jest przypisane do nowego / dedykowanego portu. Jedynym rzeczywistym ograniczeniem, które musi spełniać stos TCP, jest to, że krotka (adres_lokalny, port_lokalny, adres_zdalny, port_zdalny) musi być unikalna dla każdego połączenia przez gniazdo. W ten sposób serwer może mieć wiele gniazd TCP korzystających z tego samego portu lokalnego, o ile każde z gniazd na tym porcie jest podłączone do innej zdalnej lokalizacji.

Zobacz akapit „Socket Pair” pod adresem : http://books.google.com/books?id=ptSC4LpwGA0C&lpg=PA52&dq=socket%20pair%20tuple&pg=PA52#v=onepage&q=socket%20pair%20tuple&f=false

Jeremy Friesner
źródło
1
Dziękuję za doskonałą odpowiedź, Jeremy!
KJ
6
To, co mówisz, jest całkowicie prawdą po stronie serwera. Jednak struktura BSD Sockets API oznacza, że ​​wychodzące porty po stronie klienta muszą być w praktyce unikalne, ponieważ bind()operacja poprzedza connect()operację, nawet niejawnie.
Markiz Lorne
1
@EJP Cześć, myślałem, że wcześniej bind()był używany tylko po stronie serwera. accept()?Więc strona klienta będzie również wiązać określony port?
GMsoF,
5
@GMsoF: wcześniej bind()można go było używać po stronie klienta connect().
Remy Lebeau
10

Teoretycznie tak. Nie ćwicz. Większość jąder (łącznie z linuxem) nie pozwala ci ani sekundy bind()na już przydzielony port. To nie była naprawdę duża łatka, aby było to dozwolone.

Koncepcyjnie powinniśmy rozróżnić gniazdo i port . Gniazda to dwukierunkowe punkty końcowe komunikacji, czyli „rzeczy”, do których możemy wysyłać i odbierać bajty. To kwestia koncepcji, w nagłówku pakietu o nazwie „gniazdo” nie ma takiego pola.

Port to identyfikator umożliwiający identyfikację gniazda. W przypadku TCP, port jest 16-bitową liczbą całkowitą, ale istnieją również inne protokoły (na przykład w gniazdach unix, „port” to zasadniczo ciąg znaków).

Główny problem jest następujący: jeśli przychodzi pakiet, jądro może zidentyfikować swoje gniazdo na podstawie numeru portu docelowego. Jest to najbardziej powszechny sposób, ale nie jedyna możliwość:

  • Gniazda można zidentyfikować na podstawie docelowego adresu IP przychodzących pakietów. Dzieje się tak na przykład, jeśli mamy serwer korzystający z dwóch adresów IP jednocześnie. Wtedy możemy na przykład uruchomić różne serwery internetowe na tych samych portach, ale na różnych adresach IP.
  • Gniazda można również zidentyfikować na podstawie ich portu źródłowego i adresu IP. Dzieje się tak w wielu konfiguracjach równoważenia obciążenia.

Ponieważ pracujesz na serwerze aplikacji, będzie w stanie to zrobić.

peterh - Przywróć Monikę
źródło
2
Nie pytał o zrobienie sekundy bind().
Markiz Lorne
1
@ user207421 Czy kiedykolwiek widziałeś system operacyjny, w którym gniazda nasłuchowe nie są konfigurowane przez bind()? Mogę to sobie wyobrazić, tak, jest to całkiem możliwe, ale faktem jest, że zarówno WinSock, jak i Posix API używają do tego bind()wywołania, nawet ich parametryzacja jest praktycznie taka sama. Nawet jeśli API nie ma tego wywołania, jakoś musisz to powiedzieć, skąd chcesz czytać przychodzące bajty .
peterh - Przywróć Monikę
1
@ user207421 Oczywiście 100k lub więcej połączeń TCP może być obsłużonych z tymi samymi portami, wywołania listen()/ accept()API mogą tworzyć gniazda w taki sposób, że jądro będzie je rozróżniać na podstawie portów przychodzących. Kwestię PO można zinterpretować w sposób, w jaki zasadniczo o to prosi. Myślę, że jest to całkiem realistyczne, ale nie to dosłownie oznacza jego pytanie.
peterh - Przywróć Monikę
1

Nie. Nie jest możliwe współdzielenie tego samego portu w określonej chwili. Możesz jednak tak skonfigurować swoją aplikację, aby uzyskać dostęp do portu w innym momencie.

SAKEER T
źródło