TCP: ¿dos sockets diferentes pueden compartir un puerto?

124

Esta puede ser una pregunta muy básica pero me confunde.

¿Pueden dos enchufes conectados diferentes compartir un puerto? Estoy escribiendo un servidor de aplicaciones que debería poder manejar más de 100k conexiones simultáneas, y sabemos que la cantidad de puertos disponibles en un sistema es de alrededor de 60k (16 bits). Un socket conectado se asigna a un puerto nuevo (dedicado), por lo que significa que el número de conexiones simultáneas está limitado por el número de puertos, a menos que varios sockets puedan compartir el mismo puerto. Entonces la pregunta.

¡Gracias por la ayuda por adelantado!

KJ
fuente

Respuestas:

175

Un socket de servidor escucha en un solo puerto. Todas las conexiones de cliente establecidas en ese servidor están asociadas con ese mismo puerto de escucha en el lado del servidor de la conexión. Una conexión establecida se identifica de forma única por la combinación de pares IP / Puerto del lado del cliente y del lado del servidor. Varias conexiones en el mismo servidor pueden compartir el mismo par IP / Puerto del lado del servidor siempre que estén asociadas con diferentes pares IP / Puerto del lado del cliente , y el servidor podría manejar tantos clientes como los recursos del sistema disponibles lo permitan a.

En el lado del cliente , es una práctica común que las nuevas conexiones salientes utilicen un puerto del lado del cliente aleatorio , en cuyo caso es posible quedarse sin los puertos disponibles si realiza muchas conexiones en un corto período de tiempo.

Remy Lebeau
fuente
2
¡Gracias por la respuesta, Remy! Tu respuesta es todo lo que tenía curiosidad. ;)
KJ
2
@Remy Las conexiones se discriminan no solo por el puerto de origen / destino / IP, sino también por un protocolo (TCP, UDP, etc.), si no me equivoco.
Ondrej Peterka
1
@OndraPeterka: sí, pero no todas las plataformas restringen eso. Por ejemplo, Windows felizmente permite que los sockets de servidor IPv4 e IPv6 separados escuchen en la misma IP local: Puerto sin saltos, pero los sistemas * Nix (incluidos Linux y Android) no lo hacen.
Remy Lebeau
6
@ user2268997: no puede utilizar un solo socket para conectarse a varios servidores. Debe crear un enchufe independiente para cada conexión.
Remy Lebeau
3
@FernandoGonzalezSanchez: Un solo cliente puede tener varios sockets TCP vinculados al mismo par de IP / puerto local siempre que estén conectados a diferentes pares de IP / puerto remotos. Eso no es específico de Windows, es parte de cómo funciona TCP en general.
Remy Lebeau
182

Escucha TCP / HTTP en puertos: ¿cómo pueden muchos usuarios compartir el mismo puerto?

Entonces, ¿qué sucede cuando un servidor escucha las conexiones entrantes en un puerto TCP? Por ejemplo, supongamos que tiene un servidor web en el puerto 80. Supongamos que su computadora tiene la dirección IP pública 24.14.181.229 y la persona que intenta conectarse con usted tiene la dirección IP 10.1.2.3. Esta persona puede conectarse con usted abriendo un socket TCP al 24.14.181.229:80. Suficientemente simple.

De manera intuitiva (y errónea), la mayoría de la gente asume que se parece a esto:

    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.

Esto es intuitivo, porque desde el punto de vista del cliente, tiene una dirección IP y se conecta a un servidor en IP: PORT. Dado que el cliente se conecta al puerto 80, ¿su puerto también debe ser 80? Esto es algo sensato de pensar, pero en realidad no es lo que sucede. Si eso fuera correcto, solo podríamos atender a un usuario por dirección IP extranjera. Una vez que una computadora remota se conecta, entonces él toma la conexión del puerto 80 al puerto 80, y nadie más podría conectarse.

Hay que entender tres cosas:

1.) En un servidor, un proceso está escuchando en un puerto. Una vez que obtiene una conexión, la pasa a otro hilo. La comunicación nunca acapara el puerto de escucha.

2.) Las conexiones son identificadas de forma única por el sistema operativo mediante las siguientes 5 tuplas: (IP local, puerto local, IP remota, puerto remoto, protocolo). Si algún elemento de la tupla es diferente, entonces esta es una conexión completamente independiente.

3.) Cuando un cliente se conecta a un servidor, elige un puerto de origen de orden superior aleatorio y no utilizado . De esta manera, un solo cliente puede tener hasta ~ 64k conexiones al servidor para el mismo puerto de destino.

Entonces, esto es realmente lo que se crea cuando un cliente se conecta a un servidor:

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

Observando lo que realmente sucede

Primero, usemos netstat para ver qué está sucediendo en esta computadora. Usaremos el puerto 500 en lugar del 80 (porque un montón de cosas están sucediendo en el puerto 80 ya que es un puerto común, pero funcionalmente no hace ninguna diferencia).

    netstat -atnp | grep -i ":500 "

Como se esperaba, la salida está en blanco. Ahora comencemos un servidor web:

    sudo python3 -m http.server 500

Ahora, aquí está el resultado de ejecutar netstat nuevamente:

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

Así que ahora hay un proceso que está escuchando activamente (Estado: LISTEN) en el puerto 500. La dirección local es 0.0.0.0, que es un código para "escuchar todas las direcciones IP". Un error fácil de cometer es escuchar solo en el puerto 127.0.0.1, que solo aceptará conexiones desde la computadora actual. Entonces, esto no es una conexión, esto solo significa que un proceso solicitó bind () al puerto IP, y ese proceso es responsable de manejar todas las conexiones a ese puerto. Esto sugiere la limitación de que solo puede haber un proceso por computadora escuchando en un puerto (hay formas de evitar eso usando multiplexación, pero este es un tema mucho más complicado). Si un servidor web está escuchando en el puerto 80, no puede compartir ese puerto con otros servidores web.

Así que ahora, conectemos a un usuario a nuestra máquina:

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

Este es un script simple ( https://github.com/grokit/quickweb ) que abre un socket TCP, envía el payload ("Test payload." En este caso), espera unos segundos y se desconecta. Si vuelve a hacer netstat mientras esto sucede, se muestra lo siguiente:

    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 -

Si se conecta con otro cliente y vuelve a hacer netstat, verá lo siguiente:

    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 -

... es decir, el cliente usó otro puerto aleatorio para la conexión. Así que nunca hay confusión entre las direcciones IP.

Nada
fuente
11
Esta es la mejor respuesta que he visto en SO.
Empleos
1
@ N0thing "De esta manera, un solo cliente puede tener hasta ~ 64k conexiones al servidor para el mismo puerto de destino". Entonces, en la práctica, si un cliente no se conecta al mismo servidor y puerto, dos o varias veces al mismo tiempo, entonces un cliente puede tener incluso más de ~ 64K conexiones. Es eso cierto. Si es así, eso implica que desde un solo puerto en el lado del cliente puede tener conexión a muchos procesos de servidor diferentes (de modo que la conexión de socket es diferente). Entonces, en general, ¿varios sockets de cliente pueden residir en el mismo puerto en la máquina del cliente? Por favor lea mi comentario a la respuesta de "Remey Lebeau". Gracias: D
Prem KTiw
6
@premktiw: Sí, varios sockets de cliente se pueden vincular al mismo par de IP / puerto local al mismo tiempo, si están conectados a diferentes pares de IP / puerto de servidor, por lo que las tuplas de pares locales + remotos son únicas. Y sí, es posible que un cliente tenga más de 64K conexiones simultáneas en total. Desde un solo puerto, se puede conectar a un número potencialmente infinito de servidores (limitado por los recursos del sistema operativo disponibles, los puertos de enrutador disponibles, etc.) siempre que los pares de IP / puerto del servidor sean únicos.
Remy Lebeau
1
@RemyLebeau Satisfecho. Muchas gracias: D
Prem KTiw
1
@bibstha ¿Cómo maneja el firewall los puertos aleatorios cuando todas las conexiones entrantes se rechazan?
PatrykG
35

Un enchufe conectado se asigna a un nuevo puerto (dedicado)

Esa es una intuición común, pero incorrecta. Un enchufe conectado no está asignado a un puerto nuevo / dedicado. La única restricción real que debe satisfacer la pila TCP es que la tupla de (dirección_local, puerto_local, dirección_remota, puerto_remoto) debe ser única para cada conexión de socket. Por lo tanto, el servidor puede tener muchos sockets TCP usando el mismo puerto local, siempre que cada uno de los sockets del puerto esté conectado a una ubicación remota diferente.

Consulte el párrafo "Par de sockets" en: 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
fuente
1
¡Gracias por la respuesta perfecta, Jeremy!
KJ
6
Lo que dices es completamente cierto del lado del servidor. Sin embargo, la estructura de la API BSD Sockets significa que los puertos salientes del lado del cliente deben ser únicos en la práctica, porque la bind()operación precede a la connect()operación, incluso implícitamente.
Marqués de Lorne
1
@EJP Hola, pensé bind()que solo se usaba en el lado del servidor antes. accept()?¿El lado del cliente también vinculará el puerto en particular?
GMsoF
5
@GMsoF: bind()se puede usar en el lado del cliente antes connect().
Remy Lebeau
10

Teóricamente, sí. Practica, no. La mayoría de los núcleos (incluido Linux) no le permiten un segundo bind()a un puerto ya asignado. No fue un gran parche permitir esto.

Conceptualmente, deberíamos diferenciar entre socket y puerto . Los sockets son puntos finales de comunicación bidireccional, es decir, "cosas" donde podemos enviar y recibir bytes. Es algo conceptual, no existe tal campo en un encabezado de paquete llamado "socket".

El puerto es un identificador que puede identificar un enchufe. En el caso del TCP, un puerto es un entero de 16 bits, pero también hay otros protocolos (por ejemplo, en sockets Unix, un "puerto" es esencialmente una cadena).

El principal problema es el siguiente: si llega un paquete entrante, el kernel puede identificar su socket por su número de puerto de destino. Es la forma más común, pero no es la única posibilidad:

  • Los sockets se pueden identificar por la IP de destino de los paquetes entrantes. Este es el caso, por ejemplo, si tenemos un servidor que utiliza dos IP a la vez. Entonces podemos ejecutar, por ejemplo, diferentes servidores web en los mismos puertos, pero en las diferentes IP.
  • Los sockets se pueden identificar por su puerto de origen y también por su IP. Este es el caso en muchas configuraciones de equilibrio de carga.

Como está trabajando en un servidor de aplicaciones, podrá hacerlo.

peterh - Restablecer a Monica
fuente
2
No preguntó por hacer un segundo bind().
Marqués de Lorne
1
@ user207421 ¿Alguna vez ha visto un sistema operativo donde los sockets de escucha no están configurados por bind()? Me lo puedo imaginar, sí es bastante posible, pero el hecho es que tanto WinSock como la API Posix usan la bind()llamada para eso, incluso su parametrización es prácticamente la misma. Incluso si una API no tiene esta llamada, de alguna manera debe decirlo, desde dónde desea leer los bytes entrantes .
peterh - Reincorporar a Monica
1
@ user207421 Por supuesto, se pueden manejar 100k o más conexiones TCP con los mismos puertos, las llamadas listen()/ accept()API pueden crear los sockets de manera que el kernel los diferencie por sus puertos entrantes. La cuestión del OP se puede interpretar de la forma en que él la pide esencialmente. Creo que es bastante realista, pero no es esto lo que significa literalmente su pregunta.
peterh - Reincorporar a Monica
1

No. No es posible compartir el mismo puerto en un instante en particular. Pero puede hacer su aplicación de tal manera que haga que el puerto acceda en un instante diferente.

SAKEER T
fuente