Bienvenido al maravilloso mundo de la portabilidad ... o más bien la falta de ella. Antes de comenzar a analizar estas dos opciones en detalle y analizar más a fondo cómo los manejan los diferentes sistemas operativos, debe tenerse en cuenta que la implementación de socket BSD es la madre de todas las implementaciones de socket. Básicamente, todos los demás sistemas copiaron la implementación del socket BSD en algún momento (o al menos sus interfaces) y luego comenzaron a evolucionar por su cuenta. Por supuesto, la implementación del socket BSD también evolucionó al mismo tiempo y, por lo tanto, los sistemas que la copiaron más tarde obtuvieron características que faltaban en los sistemas que la copiaron antes. Comprender la implementación del socket BSD es la clave para comprender todas las demás implementaciones de socket, por lo que debe leer sobre ello incluso si no le importa escribir código para un sistema BSD.
Hay un par de conceptos básicos que debe conocer antes de analizar estas dos opciones. Una conexión TCP / UDP se identifica mediante una tupla de cinco valores:
{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}
Cualquier combinación única de estos valores identifica una conexión. Como resultado, no hay dos conexiones que puedan tener los mismos cinco valores; de lo contrario, el sistema ya no podría distinguir estas conexiones.
El protocolo de un socket se establece cuando se crea un socket con la socket()
función. La dirección de origen y el puerto se configuran con la bind()
función. La dirección de destino y el puerto se configuran con la connect()
función. Como UDP es un protocolo sin conexión, se pueden usar sockets UDP sin conectarlos. Sin embargo, está permitido conectarlos y, en algunos casos, es muy ventajoso para el código y el diseño general de la aplicación. En el modo sin conexión, los sockets UDP que no se vincularon explícitamente cuando los datos se envían por primera vez generalmente están vinculados automáticamente por el sistema, ya que un socket UDP no vinculado no puede recibir ningún dato (respuesta). Lo mismo es cierto para un socket TCP no vinculado, se vincula automáticamente antes de conectarse.
Si vincula explícitamente un socket, es posible vincularlo al puerto 0
, lo que significa "cualquier puerto". Dado que un socket no puede vincularse realmente a todos los puertos existentes, el sistema tendrá que elegir un puerto específico en ese caso (generalmente de un rango predefinido de puertos de origen específicos del sistema operativo). Existe un comodín similar para la dirección de origen, que puede ser "cualquier dirección" ( 0.0.0.0
en el caso de IPv4 y::
en caso de IPv6). A diferencia del caso de los puertos, un socket realmente puede estar vinculado a "cualquier dirección", lo que significa "todas las direcciones IP de origen de todas las interfaces locales". Si el socket se conecta más adelante, el sistema tiene que elegir una dirección IP de origen específica, ya que no se puede conectar un socket y al mismo tiempo estar vinculado a cualquier dirección IP local. Dependiendo de la dirección de destino y del contenido de la tabla de enrutamiento, el sistema elegirá una dirección de origen apropiada y reemplazará el enlace "cualquiera" por un enlace a la dirección IP de origen elegida.
De manera predeterminada, no se pueden vincular dos sockets a la misma combinación de dirección de origen y puerto de origen. Mientras el puerto de origen sea diferente, la dirección de origen es irrelevante. La unión socketA
a A:X
y socketB
a B:Y
, donde A
y B
son las direcciones y X
y Y
son puertos, siempre es posible, siempre y cuando X != Y
es cierto. Sin embargo, incluso si X == Y
, el enlace sigue siendo posible siempre que sea A != B
cierto. Por ejemplo, socketA
pertenece a un programa de servidor FTP y está vinculado 192.168.0.1:21
y socketB
pertenece a otro programa de servidor FTP y está vinculado 10.0.0.1:21
, ambos enlaces tendrán éxito. Sin embargo, tenga en cuenta que un socket puede estar vinculado localmente a "cualquier dirección". Si un zócalo está obligado a0.0.0.0:21
, está vinculado a todas las direcciones locales existentes al mismo tiempo y, en ese caso, ningún otro socket puede vincularse al puerto 21
, independientemente de la dirección IP específica a la que intente vincularse, ya que 0.0.0.0
entra en conflicto con todas las direcciones IP locales existentes.
Todo lo dicho hasta ahora es prácticamente igual para todos los principales sistemas operativos. Las cosas comienzan a ser específicas del sistema operativo cuando la reutilización de direcciones entra en juego. Comenzamos con BSD, ya que, como dije anteriormente, es la madre de todas las implementaciones de socket.
BSD
SO_REUSEADDR
Si SO_REUSEADDR
está habilitado en un socket antes de vincularlo, el socket puede vincularse con éxito a menos que haya un conflicto con otro socket vinculado exactamente a la misma combinación de dirección de origen y puerto. Ahora puede preguntarse cómo es eso diferente de antes. La palabra clave es "exactamente". SO_REUSEADDR
cambia principalmente la forma en que se tratan las direcciones comodín ("cualquier dirección IP") cuando se buscan conflictos.
Sin SO_REUSEADDR
, la unión socketA
a 0.0.0.0:21
y después de unión socketB
a 192.168.0.1:21
fallará (con error EADDRINUSE
), ya que 0.0.0.0 significa "cualquier dirección de IP local", por lo tanto todas las direcciones IP locales se consideran en uso por esta toma y esto incluye 192.168.0.1
, también. Con SO_REUSEADDR
esto tendrá éxito, ya que 0.0.0.0
y no192.168.0.1
son exactamente la misma dirección, una es un comodín para todas las direcciones locales y la otra es una dirección local muy específica. Tenga en cuenta que la declaración anterior es verdadera independientemente de en qué orden socketA
y socketB
están vinculados; sin SO_REUSEADDR
ella siempre fallará, con SO_REUSEADDR
ella siempre tendrá éxito.
Para darle una mejor visión general, hagamos una tabla aquí y enumeremos todas las combinaciones posibles:
SO_REUSEADDR socketA socketB Resultado
-------------------------------------------------- -------------------
ON / OFF 192.168.0.1:21 192.168.0.1:21 Error (EADDRINUSE)
ON / OFF 192.168.0.1:21 10.0.0.1:21 OK
ON / OFF 10.0.0.1:21 192.168.0.1:21 OK
OFF 0.0.0.0:21 192.168.1.0:21 Error (EADDRINUSE)
OFF 192.168.1.0:21 0.0.0.0:21 Error (EADDRINUSE)
EN 0.0.0.0:21 192.168.1.0:21 OK
ENCENDIDO 192.168.1.0:21 0.0.0.0:21 OK
ON / OFF 0.0.0.0:21 0.0.0.0:21 Error (EADDRINUSE)
La tabla anterior supone que socketA
ya se ha vinculado correctamente a la dirección proporcionada socketA
, luego socketB
se crea, se SO_REUSEADDR
configura o no, y finalmente se vincula a la dirección indicada socketB
. Result
es el resultado de la operación de vinculación para socketB
. Si la primera columna dice ON/OFF
, el valor de SO_REUSEADDR
es irrelevante para el resultado.
Está bien, SO_REUSEADDR
tiene un efecto en las direcciones comodín, es bueno saberlo. Sin embargo, ese no es el único efecto que tiene. Hay otro efecto bien conocido, que también es la razón por la cual la mayoría de las personas usan los SO_REUSEADDR
programas de servidor en primer lugar. Para el otro uso importante de esta opción, debemos analizar más a fondo cómo funciona el protocolo TCP.
Un socket tiene un búfer de envío y si una llamada a la send()
función tiene éxito, no significa que los datos solicitados realmente se hayan enviado realmente, solo significa que los datos se han agregado al búfer de envío. Para los sockets UDP, los datos generalmente se envían muy pronto, si no de inmediato, pero para los sockets TCP, puede haber un retraso relativamente largo entre agregar datos al búfer de envío y hacer que la implementación de TCP realmente envíe esos datos. Como resultado, cuando cierra un socket TCP, todavía puede haber datos pendientes en el búfer de envío, que aún no se ha enviado pero su código lo considera como enviado, ya que elsend()
llamada exitosa. Si la implementación de TCP cerrara el socket inmediatamente a su solicitud, todos estos datos se perderían y su código ni siquiera sabría sobre eso. Se dice que TCP es un protocolo confiable y la pérdida de datos así no es muy confiable. Es por eso que un socket que todavía tiene datos para enviar entrará en un estado llamado TIME_WAIT
cuando lo cierre. En ese estado esperará hasta que todos los datos pendientes se hayan enviado con éxito o hasta que se agote el tiempo de espera, en cuyo caso el socket se cierra con fuerza.
La cantidad de tiempo que el kernel esperará antes de cerrar el socket, independientemente de si todavía tiene datos en vuelo o no, se llama Tiempo de espera . El tiempo de espera es globalmente configurable en la mayoría de los sistemas y por defecto es bastante largo (dos minutos es un valor común que encontrará en muchos sistemas). También se puede configurar por zócalo utilizando la opción de zócalo SO_LINGER
que se puede usar para acortar o alargar el tiempo de espera e incluso para deshabilitarlo por completo. Sin embargo, deshabilitarlo por completo es una muy mala idea, ya que cerrar un socket TCP con gracia es un proceso ligeramente complejo e implica enviar y devolver un par de paquetes (así como reenviar esos paquetes en caso de que se pierdan) y todo este proceso cerrado. también está limitado por el tiempo de Linger. Si deshabilita la demora, es posible que su socket no solo pierda datos en vuelo, sino que también se cierre con fuerza en lugar de con gracia, lo que generalmente no se recomienda. Los detalles sobre cómo se cierra correctamente una conexión TCP están más allá del alcance de esta respuesta, si desea obtener más información, le recomiendo que eche un vistazo a esta página . E incluso si desactivó la persistencia SO_LINGER
, si su proceso muere sin cerrar explícitamente el socket, BSD (y posiblemente otros sistemas) se mantendrán, ignorando lo que ha configurado. Esto sucederá, por ejemplo, si su código solo llamaexit()
(bastante común para programas de servidor pequeños y simples) o el proceso se interrumpe por una señal (que incluye la posibilidad de que simplemente se bloquee debido a un acceso ilegal a la memoria). Por lo tanto, no hay nada que pueda hacer para asegurarse de que un socket nunca se demore en todas las circunstancias.
La pregunta es, ¿cómo trata el sistema un socket en estado TIME_WAIT
? Si SO_REUSEADDR
no se establece, TIME_WAIT
se considera que un socket en estado todavía está vinculado a la dirección de origen y al puerto, y cualquier intento de vincular un nuevo socket a la misma dirección y puerto fallará hasta que el socket se haya cerrado realmente, lo que puede llevar tanto tiempo como el tiempo de Linger configurado . Por lo tanto, no espere que pueda volver a vincular la dirección de origen de un socket inmediatamente después de cerrarlo. En la mayoría de los casos esto fallará. Sin embargo, si SO_REUSEADDR
está configurado para el socket que está intentando vincular, otro socket vinculado a la misma dirección y puerto en estadoTIME_WAIT
simplemente se ignora, después de todo ya está "medio muerto", y su socket puede unirse exactamente a la misma dirección sin ningún problema. En ese caso, no juega ningún papel que el otro socket pueda tener exactamente la misma dirección y puerto. Tenga en cuenta que vincular un socket a exactamente la misma dirección y puerto que un socket moribundo en TIME_WAIT
estado puede tener efectos secundarios inesperados, y generalmente no deseados, en caso de que el otro socket todavía esté "en funcionamiento", pero eso está más allá del alcance de esta respuesta y Afortunadamente, esos efectos secundarios son bastante raros en la práctica.
Hay una última cosa que debes saber SO_REUSEADDR
. Todo lo escrito anteriormente funcionará siempre que el socket al que desea enlazar tenga habilitada la reutilización de direcciones. No es necesario que el otro zócalo, el que ya está enlazado o en TIME_WAIT
estado, también tenga este indicador activado cuando estaba enlazado. El código que decide si el enlace tendrá éxito o falla solo inspecciona el SO_REUSEADDR
indicador del socket alimentado en la bind()
llamada, para todos los demás conectores inspeccionados, este indicador ni siquiera se mira.
SO_REUSEPORT
SO_REUSEPORT
es lo que la mayoría de la gente esperaría SO_REUSEADDR
ser. Básicamente, le SO_REUSEPORT
permite vincular un número arbitrario de sockets a exactamente la misma dirección de origen y puerto, siempre y cuando todos los sockets enlazados anteriores también se hayan SO_REUSEPORT
establecido antes de estar enlazados. Si el primer socket que está vinculado a una dirección y puerto no se ha SO_REUSEPORT
establecido, ningún otro socket se puede vincular exactamente a la misma dirección y puerto, independientemente de si este otro socket se ha SO_REUSEPORT
establecido o no, hasta que el primer socket libere su enlace nuevamente. A diferencia del caso del SO_REUESADDR
manejo del código SO_REUSEPORT
, no solo verificará que el socket vinculado actualmente se haya SO_REUSEPORT
establecido, sino que también verificará que el socket con una dirección y un puerto en conflicto se había SO_REUSEPORT
establecido cuando estaba vinculado.
SO_REUSEPORT
no implica SO_REUSEADDR
. Esto significa que si un socket no se SO_REUSEPORT
estableció cuando estaba vinculado y otro socket se SO_REUSEPORT
configuró cuando está vinculado exactamente a la misma dirección y puerto, el enlace falla, lo que se espera, pero también falla si el otro socket ya está muriendo y está en TIME_WAIT
estado Para poder vincular un socket a las mismas direcciones y puerto que otro socket en TIME_WAIT
estado, se SO_REUSEADDR
debe establecer en ese socket o se SO_REUSEPORT
debe haber establecido en ambos sockets antes de vincularlos. Por supuesto, está permitido configurar ambos SO_REUSEPORT
y SO_REUSEADDR
, en un zócalo.
No hay mucho más que decir sobre el hecho SO_REUSEPORT
de que se agregó más tarde SO_REUSEADDR
, por eso no lo encontrará en muchas implementaciones de socket de otros sistemas, que "bifurcaron" el código BSD antes de agregar esta opción, y que no había forma de vincular dos sockets a exactamente la misma dirección de socket en BSD antes de esta opción.
Conectar () ¿Devuelve EADDRINUSE?
La mayoría de la gente sabe que bind()
puede fallar con el error EADDRINUSE
, sin embargo, cuando comienzas a jugar con la reutilización de direcciones, también puedes encontrarte con la extraña situación que connect()
falla con ese error. ¿Cómo puede ser esto? ¿Cómo puede una dirección remota, después de todo, eso es lo que conectar agrega a un socket, ya estar en uso? Conectar múltiples tomas a la misma dirección remota nunca ha sido un problema antes, entonces, ¿qué está pasando aquí?
Como dije en la parte superior de mi respuesta, una conexión se define por una tupla de cinco valores, ¿recuerdas? Y también dije que estos cinco valores deben ser únicos; de lo contrario, el sistema ya no podrá distinguir dos conexiones, ¿verdad? Bueno, con la reutilización de direcciones, puede vincular dos zócalos del mismo protocolo a la misma dirección de origen y puerto. Eso significa que tres de esos cinco valores ya son los mismos para estos dos sockets. Si ahora intenta conectar ambos sockets también a la misma dirección y puerto de destino, crearía dos sockets conectados, cuyas tuplas son absolutamente idénticas. Esto no puede funcionar, al menos no para conexiones TCP (las conexiones UDP no son conexiones reales de todos modos). Si llegaron datos para cualquiera de las dos conexiones, el sistema no podría decir a qué conexión pertenecen los datos.
Por lo tanto, si vincula dos sockets del mismo protocolo a la misma dirección y puerto de origen e intenta conectarlos a la misma dirección y puerto de destino, en connect()
realidad fallará con el error EADDRINUSE
para el segundo socket que intenta conectar, lo que significa que un el zócalo con una tupla idéntica de cinco valores ya está conectado.
Direcciones de multidifusión
La mayoría de las personas ignora el hecho de que existen direcciones de multidifusión, pero existen. Mientras que las direcciones de unidifusión se utilizan para la comunicación uno a uno, las direcciones de multidifusión se utilizan para la comunicación uno a muchos. La mayoría de las personas se dieron cuenta de las direcciones de multidifusión cuando se enteraron de IPv6, pero las direcciones de multidifusión también existían en IPv4, a pesar de que esta función nunca se usó ampliamente en Internet pública.
El significado de los SO_REUSEADDR
cambios para las direcciones de multidifusión, ya que permite que varios sockets se unan exactamente a la misma combinación de dirección y puerto de multidifusión de origen. En otras palabras, para las direcciones de multidifusión se SO_REUSEADDR
comporta exactamente como SO_REUSEPORT
para las direcciones de unidifusión. En realidad, el código trata SO_REUSEADDR
e SO_REUSEPORT
idénticamente las direcciones de multidifusión, lo que significa que se podría decir que SO_REUSEADDR
implica SO_REUSEPORT
para todas las direcciones de multidifusión y viceversa.
FreeBSD / OpenBSD / NetBSD
Todos estos son tenedores bastante tardíos del código BSD original, es por eso que los tres ofrecen las mismas opciones que BSD y también se comportan de la misma manera que en BSD.
macOS (MacOS X)
En esencia, macOS es simplemente un UNIX de estilo BSD llamado " Darwin ", basado en una bifurcación bastante tardía del código BSD (BSD 4.3), que luego se sincronizó con FreeBSD (en ese momento actual) 5 bases de código para la versión Mac OS 10.3, para que Apple pueda obtener el pleno cumplimiento de POSIX (macOS tiene la certificación POSIX). A pesar de tener un microkernel en su núcleo (" Mach "), el resto del kernel (" XNU ") es básicamente un kernel BSD, y es por eso que macOS ofrece las mismas opciones que BSD y también se comportan de la misma manera que en BSD .
iOS / watchOS / tvOS
iOS es solo una bifurcación de macOS con un núcleo ligeramente modificado y recortado, un conjunto de herramientas de espacio de usuario algo despojado y un conjunto de marcos predeterminado ligeramente diferente. watchOS y tvOS son bifurcaciones de iOS, que se reducen aún más (especialmente watchOS). Que yo sepa, todos se comportan exactamente igual que macOS.
Linux
Linux <3.9
Antes de Linux 3.9, solo SO_REUSEADDR
existía la opción . Esta opción se comporta generalmente igual que en BSD con dos excepciones importantes:
Mientras un socket TCP (servidor) de escucha esté vinculado a un puerto específico, la SO_REUSEADDR
opción se ignora por completo para todos los sockets que se dirigen a ese puerto. La vinculación de un segundo socket al mismo puerto solo es posible si también fue posible en BSD sin haberse SO_REUSEADDR
configurado. Por ejemplo, no puede vincularse a una dirección comodín y luego a una más específica o al revés, ambas son posibles en BSD si establece SO_REUSEADDR
. Lo que puede hacer es enlazar al mismo puerto y a dos direcciones diferentes que no sean comodines, como siempre está permitido. En este aspecto, Linux es más restrictivo que BSD.
La segunda excepción es que para los sockets de clientes, esta opción se comporta exactamente como SO_REUSEPORT
en BSD, siempre y cuando ambos tengan este indicador establecido antes de que se vincularan. La razón para permitir eso fue simplemente que es importante poder vincular múltiples sockets a exactamente la misma dirección de socket UDP para varios protocolos y, como solía haber no SO_REUSEPORT
antes de 3.9, el comportamiento de SO_REUSEADDR
se alteró en consecuencia para llenar ese vacío. . En ese aspecto, Linux es menos restrictivo que BSD.
Linux> = 3.9
Linux 3.9 también agregó la opción SO_REUSEPORT
a Linux. Esta opción se comporta exactamente como la opción en BSD y permite vincular exactamente a la misma dirección y número de puerto siempre que todos los sockets tengan esta opción establecida antes de vincularlos.
Sin embargo, todavía hay dos diferencias con respecto SO_REUSEPORT
a otros sistemas:
Para evitar el "secuestro de puertos", hay una limitación especial: ¡ todos los sockets que desean compartir la misma dirección y combinación de puertos deben pertenecer a procesos que compartan la misma ID de usuario efectiva! Por lo tanto, un usuario no puede "robar" puertos de otro usuario. Esta es una magia especial para compensar un poco las faltas SO_EXCLBIND
/ SO_EXCLUSIVEADDRUSE
banderas.
Además, el kernel realiza una "magia especial" para los SO_REUSEPORT
sockets que no se encuentran en otros sistemas operativos: para los sockets UDP, intenta distribuir datagramas de manera uniforme, para los sockets de escucha TCP, intenta distribuir las solicitudes de conexión entrantes (las aceptadas llamando accept()
) de manera uniforme en todos los sockets que comparten la misma combinación de dirección y puerto. Por lo tanto, una aplicación puede abrir fácilmente el mismo puerto en múltiples procesos secundarios y luego usarla SO_REUSEPORT
para obtener un equilibrio de carga muy económico.
Androide
Aunque todo el sistema Android es algo diferente de la mayoría de las distribuciones de Linux, en su núcleo funciona un kernel de Linux ligeramente modificado, por lo tanto, todo lo que se aplica a Linux también debería aplicarse a Android.
Ventanas
Windows solo conoce la SO_REUSEADDR
opción, no la hay SO_REUSEPORT
. La configuración SO_REUSEADDR
en un socket en Windows se comporta como la configuración SO_REUSEPORT
y SO_REUSEADDR
en un socket en BSD, con una excepción: un socket con SO_REUSEADDR
siempre puede unirse exactamente a la misma dirección de origen y puerto que un socket ya vinculado, incluso si el otro socket no tenía esta opción establecido cuando estaba vinculado . Este comportamiento es algo peligroso porque permite que una aplicación "robe" el puerto conectado de otra aplicación. No hace falta decir que esto puede tener importantes implicaciones de seguridad. Microsoft se dio cuenta de que esto podría ser un problema y, por lo tanto, agregó otra opción de socket SO_EXCLUSIVEADDRUSE
. AjusteSO_EXCLUSIVEADDRUSE
en un socket se asegura de que si el enlace tiene éxito, la combinación de la dirección de origen y el puerto es propiedad exclusiva de este socket y ningún otro socket puede unirse a ellos, ni siquiera si se ha SO_REUSEADDR
establecido.
Para obtener aún más detalles sobre cómo funcionan los indicadores SO_REUSEADDR
y SO_EXCLUSIVEADDRUSE
en Windows, cómo influyen en la vinculación / nueva vinculación, Microsoft proporcionó amablemente una tabla similar a mi tabla cerca de la parte superior de esa respuesta. Simplemente visite esta página y desplácese hacia abajo un poco. En realidad, hay tres tablas, la primera muestra el comportamiento anterior (anterior a Windows 2003), la segunda el comportamiento (Windows 2003 y posterior) y la tercera muestra cómo cambia el comportamiento en Windows 2003 y más tarde si las bind()
llamadas son realizadas por diferentes usuarios
Solaris
Solaris es el sucesor de SunOS. SunOS se basó originalmente en una bifurcación de BSD, SunOS 5 y más tarde se basó en una bifurcación de SVR4, sin embargo, SVR4 es una fusión de BSD, System V y Xenix, por lo que hasta cierto punto Solaris también es una bifurcación BSD, y un uno bastante temprano. Como resultado, Solaris solo sabe que SO_REUSEADDR
no existe SO_REUSEPORT
. El se SO_REUSEADDR
comporta más o menos igual que en BSD. Hasta donde sé, no hay forma de obtener el mismo comportamiento que SO_REUSEPORT
en Solaris, eso significa que no es posible vincular dos zócalos a exactamente la misma dirección y puerto.
Similar a Windows, Solaris tiene una opción para dar a un socket un enlace exclusivo. Esta opción tiene nombre SO_EXCLBIND
. Si esta opción se configura en un socket antes de vincularla, la configuración SO_REUSEADDR
en otro socket no tiene ningún efecto si se prueba un conflicto de direcciones en los dos sockets. Por ejemplo, si socketA
está vinculado a una dirección comodín y se socketB
ha SO_REUSEADDR
habilitado y está vinculado a una dirección no comodín y al mismo puerto que socketA
, este enlace normalmente tendrá éxito, a menos que se socketA
haya SO_EXCLBIND
habilitado, en cuyo caso fallará independientemente del SO_REUSEADDR
indicador de socketB
.
Otros sistemas
En caso de que su sistema no esté en la lista anterior, escribí un pequeño programa de prueba que puede usar para averiguar cómo maneja su sistema estas dos opciones. Además, si cree que mis resultados son incorrectos , primero ejecute ese programa antes de publicar cualquier comentario y posiblemente hacer afirmaciones falsas.
Todo lo que requiere el código para compilar es un poco de API POSIX (para las partes de la red) y un compilador C99 (en realidad, la mayoría de los compiladores que no son C99 funcionarán tan bien como se ofrecen inttypes.h
y stdbool.h
; por ejemplo, se gcc
admiten ambos mucho antes de ofrecer soporte C99 completo) .
Todo lo que el programa necesita para ejecutarse es que al menos una interfaz en su sistema (que no sea la interfaz local) tenga una dirección IP asignada y que se establezca una ruta predeterminada que use esa interfaz. El programa reunirá esa dirección IP y la usará como la segunda "dirección específica".
Prueba todas las combinaciones posibles que se te ocurran:
- Protocolo TCP y UDP
- Tomas normales, tomas de escucha (servidor), tomas de multidifusión
SO_REUSEADDR
establecido en socket1, socket2, o ambos enchufes
SO_REUSEPORT
establecido en socket1, socket2, o ambos enchufes
- Todas las combinaciones de direcciones que puede hacer
0.0.0.0
(comodín), 127.0.0.1
(dirección específica) y la segunda dirección específica que se encuentra en su interfaz principal (para la multidifusión es solo 224.1.2.3
en todas las pruebas)
e imprime los resultados en una bonita tabla. También funcionará en sistemas que no saben SO_REUSEPORT
, en cuyo caso esta opción simplemente no se prueba.
Lo que el programa no puede probar fácilmente es cómo SO_REUSEADDR
actúa en los zócalos en TIME_WAIT
estado, ya que es muy difícil forzar y mantener un zócalo en ese estado. Afortunadamente, la mayoría de los sistemas operativos parecen comportarse simplemente como BSD aquí y la mayoría de las veces los programadores simplemente pueden ignorar la existencia de ese estado.
Aquí está el código (no puedo incluirlo aquí, las respuestas tienen un límite de tamaño y el código empujaría esta respuesta por encima del límite).
INADDR_ANY
no vincula las direcciones locales existentes, sino también todas las futuras.listen
ciertamente crea sockets con el mismo protocolo exacto, dirección local y puerto local, a pesar de que dijiste que no es posible.