Pregunta Opciones de socket SO_REUSEADDR y SO_REUSEPORT, ¿cómo difieren? ¿Significan lo mismo en todos los principales sistemas operativos?


los man pages Documentaciones del programador para las opciones de socket SO_REUSEADDR y SO_REUSEPORT son diferentes para diferentes sistemas operativos y a menudo altamente confusos. Algunos sistemas operativos ni siquiera tienen la opción SO_REUSEPORT. La WEB está llena de información contradictoria con respecto a este tema y, a menudo, puede encontrar información que solo es cierta para la implementación de un socket de un sistema operativo específico, que ni siquiera se menciona explícitamente en el texto.

Entonces, ¿cómo es exactamente? SO_REUSEADDR diferente que SO_REUSEPORT?

Son sistemas sin SO_REUSEPORT mas limitado?

¿Y cuál es exactamente el comportamiento esperado si utilizo uno en diferentes sistemas operativos?


524
2018-01-17 21:45


origen


Respuestas:


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 profundizar en cómo los manejan los diferentes sistemas operativos, se debe tener en cuenta que la implementación del 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 sí mismos. Por supuesto, la implementación del socket BSD también se desarrolló al mismo tiempo y, por lo tanto, los sistemas que la copiaron posteriormente 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 algunas cosas básicas que debes saber antes de ver 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á distinguir estas conexiones.

El protocolo de un socket se establece cuando se crea un socket con el socket() función. La dirección de origen y el puerto se establecen con el bind() función. La dirección de destino y el puerto se establecen con el connect() función. Como UDP es un protocolo sin conexión, los sockets UDP se pueden usar 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 estaban vinculados explícitamente cuando los datos se envían por primera vez normalmente están vinculados automáticamente por el sistema, ya que un socket UDP no enlazado no puede recibir ningún dato (de respuesta). Lo mismo es cierto para un socket TCP independiente, se enlaza automáticamente antes de que se conecte.

Si vincula explícitamente un socket, es posible vincularlo al puerto 0, que significa "cualquier puerto". Como un socket no puede vincularse realmente a todos los puertos existentes, el sistema tendrá que elegir un puerto específico en ese caso (generalmente desde un rango de puertos fuente predefinido y específico del sistema operativo). Existe un comodín similar para la dirección de origen, que puede ser "cualquier dirección" (0.0.0.0en caso de IPv4 y :: en caso de IPv6). A diferencia 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 tarde, 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. Según la dirección de destino y el 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. Siempre que el puerto de origen sea diferente, la dirección de origen es realmente irrelevante. Unión socketA a A:X y socketB a B:Y, dónde A y B son direcciones y X y Y son puertos, siempre es posible siempre y cuando X != Y se mantiene cierto. Sin embargo, incluso si X == Y, la unión aún es posible siempre y cuando A != B se mantiene cierto. P.ej. socketA pertenece a un programa de servidor FTP y está destinado a 192.168.0.1:21 y socketB pertenece a otro programa de servidor FTP y está destinado a 10.0.0.1:21, ambas consolidaciones tendrán éxito. Tenga en cuenta, sin embargo, que un socket puede estar vinculado localmente a "cualquier dirección". Si un socket está destinado a 0.0.0.0:21, está ligado 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, como 0.0.0.0 conflictos con todas las direcciones IP locales existentes.

Todo lo dicho hasta ahora es prácticamente igual para todos los sistemas operativos principales. Las cosas comienzan a tener un sistema operativo específico cuando entra en juego la reutilización de direcciones. Comenzamos con BSD, ya que como dije antes, 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 se puede vincular correctamente a menos que exista un conflicto con otro socket destinado a exactamente la misma combinación de dirección de origen y puerto. Ahora puedes preguntarte ¿cómo es eso diferente a antes? La palabra clave es "exactamente". SO_REUSEADDR principalmente cambia la forma en que se tratan las direcciones de comodines ("cualquier dirección IP") al buscar conflictos.

Sin SO_REUSEADDR, Unión socketA a 0.0.0.0:21 y luego vinculante socketB a 192.168.0.1:21 fallará (con error EADDRINUSE), ya que 0.0.0.0 significa "cualquier dirección IP local", por lo tanto todas las direcciones IP locales se consideran en uso por este socket y esto incluye 192.168.0.1, también. Con SO_REUSEADDR tendrá éxito, ya que 0.0.0.0 y 192.168.0.1 son no 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 obligados; sin SO_REUSEADDR siempre fallará, con SO_REUSEADDR siempre tendrá éxito.

Para darle una mejor visión general, hagamos una tabla aquí y enumere 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)
   DESACTIVADO 192.168.1.0:21 0.0.0.0:21 Error (EADDRINUSE)
   ON 0.0.0.0:21 192.168.1.0:21 OK
   ON 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 de arriba asume que socketA ya se ha vinculado correctamente a la dirección indicada para socketA, entonces socketB se crea, o se pone SO_REUSEADDR establecer o no, y finalmente está obligado a la dirección dada por socketB. Result es el resultado de la operación de enlace para socketB. Si la primera columna dice ON/OFF, El valor de SO_REUSEADDR es irrelevante para el resultado.

Bueno, SO_REUSEADDR tiene un efecto en las direcciones de comodines, es bueno saberlo. Sin embargo, ese no es el único efecto que tiene. Hay otro efecto conocido que también es la razón por la que la mayoría de la gente usa SO_REUSEADDR en 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 buffer de envío y si una llamada al send() la 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 sockets UDP, los datos generalmente se envían bastante pronto, si no de manera inmediata, 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 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 el send() llamada exitosa. Si la implementación de TCP cerraba el socket inmediatamente después de su solicitud, todos estos datos se perderían y su código ni siquiera lo sabría. Se dice que TCP es un protocolo confiable y perder 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 cierras En ese estado esperará hasta que se hayan enviado con éxito todos los datos pendientes o hasta que se agote el tiempo de espera, en cuyo caso el socket se cerrará con fuerza.

La cantidad de tiempo que el kernel esperará antes de cerrar el socket, independientemente de si todavía tiene datos de envío pendientes o no, se llama Tiempo de espera. los 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 es configurable por socket usando la opción de socket SO_LINGER que se puede usar para hacer que el tiempo de espera sea más corto o más largo, e incluso para deshabilitarlo por completo. Sin embargo, desactivarla por completo es una muy mala idea, ya que cerrar un socket TCP con elegancia es un proceso un tanto complejo e implica enviar y retroceder un par de paquetes (y reenviar esos paquetes en caso de que se pierdan) y todo este proceso cerrado también está limitado por el Tiempo de espera. Si deshabilita la demora, es posible que su socket no solo pierda datos pendientes, sino que siempre se cierre con fuerza en lugar de hacerlo con gracia, lo que generalmente no se recomienda. Los detalles acerca de cómo una conexión TCP se cierra correctamente 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 desactivaste la persistencia con SO_LINGER, si su proceso se agota sin cerrar explícitamente el socket, BSD (y posiblemente otros sistemas) permanecerá, sin embargo, ignorando lo que ha configurado. Esto sucederá, por ejemplo, si su código solo llama exit() (muy común para programas de servidor pequeños y simples) o el proceso se cancela por una señal (que incluye la posibilidad de que simplemente se cuelgue debido a un acceso ilegal a la memoria). Por lo tanto, no hay nada que pueda hacer para asegurarse de que una toma de corriente nunca permanezca en todas las circunstancias.

La pregunta es, ¿cómo trata el sistema un socket en estado TIME_WAIT? Si SO_REUSEADDR no está configurado, un socket en estado TIME_WAIT se considera que 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 configurado Tiempo de espera. Así que no espere que pueda volver a enlazar la dirección de origen de un socket inmediatamente después de cerrarlo. En la mayoría de los casos, esto fracasará. Sin embargo, si SO_REUSEADDR se establece para el socket que está tratando de vincular, otro socket vinculado a la misma dirección y puerto en estado TIME_WAIT simplemente se ignora, después de todo, ya está "medio muerto", y su socket se puede enlazar exactamente a la misma dirección sin ningún problema. En ese caso, no juega ningún papel que el otro socket tenga exactamente la misma dirección y puerto. Tenga en cuenta que vinculando un socket a exactamente la misma dirección y puerto como un socket de muerte en TIME_WAIT El estado puede tener efectos secundarios inesperados, y generalmente no deseados, en caso de que el otro socket esté todavía "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 sobre SO_REUSEADDR. Todo lo escrito anteriormente funcionará siempre que el socket al que desee vincularse tenga habilitada la reutilización de dirección. No es necesario que el otro socket, el que ya está atado o está en un TIME_WAIT estado, también tenía este indicador establecido cuando estaba enlazado. El código que decide si el enlace tendrá éxito o falla solo inspecciona el SO_REUSEADDR bandera del zócalo alimentado en el bind() llamada, para todos los demás enchufes inspeccionados, esta bandera ni siquiera se mira.

SO_REUSEPORT

SO_REUSEPORT es lo que la mayoría de la gente esperaría SO_REUSEADDR ser. Básicamente, SO_REUSEPORT le permite vincular un número arbitrario de sockets exactamente la misma dirección de origen y puerto siempre que todas zócalos anteriores también tenían SO_REUSEPORT establecido antes de que estuvieran atados Si el primer socket que está vinculado a una dirección y puerto no tiene SO_REUSEPORT establecer, ningún otro socket se puede vincular exactamente a la misma dirección y puerto, independientemente de si este otro socket tiene SO_REUSEPORT establecer o no, hasta que el primer socket libera su enlace de nuevo. A diferencia del caso de SO_REUESADDR el manejo del código SO_REUSEPORT no solo verificará que el socket actualmente encuadernado SO_REUSEPORT establecer pero también verificará que el socket con una dirección y puerto conflictivos SO_REUSEPORT establecer cuando estaba atado.

SO_REUSEPORT No implica SO_REUSEADDR. Esto significa que si un socket no tiene SO_REUSEPORT establecer cuando estaba enlazado y otro socket tiene SO_REUSEPORT establecido cuando está ligado exactamente a la misma dirección y puerto, el enlace falla, lo cual es esperado, 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 el estado requiere cualquiera SO_REUSEADDR para establecerse en ese socket o SO_REUSEPORT debe haber sido establecido en ambos enchufes antes de atarlos. Por supuesto, está permitido establecer ambos, SO_REUSEPORT y SO_REUSEADDR, en un zócalo.

No hay mucho más para decir sobre SO_REUSEPORT aparte de eso, se agregó más tarde que SO_REUSEADDR, es por eso que no lo encontrará en muchas implementaciones de sockets de otros sistemas, que "bifurcaron" el código BSD antes de agregar esta opción, y que no había forma de enlazar dos sockets con exactamente la misma dirección de socket en BSD antes de esto opción.

Conectar () ¿Regresar EADDRINUSE?

La mayoría de la gente sabe eso bind() puede fallar con el error EADDRINUSESin embargo, cuando comienzas a jugar con la reutilización de direcciones, puedes toparte con la extraña situación que connect() falla también con ese error. ¿Cómo puede ser esto? ¿Cómo puede una dirección remota, después de todo lo que conecta agrega a un socket, ya estar en uso? Conectar tomas múltiples a exactamente la misma dirección remota nunca ha sido un problema antes, entonces, ¿qué está mal aquí?

Como dije en la parte superior de mi respuesta, una conexión está definida 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 puede distinguir dos conexiones, ¿verdad? Bueno, con la reutilización de direcciones, puede vincular dos zócalos del mismo protocolo a la misma dirección y puerto de origen. Eso significa que tres de esos cinco valores ya son los mismos para estos dos sockets. Si ahora intenta conectar ambos enchufes también a la misma dirección de destino y puerto, creará dos sockets conectados, cuyas tuplas son absolutamente idénticas. Esto no puede funcionar, al menos no para las conexiones TCP (las conexiones UDP de todos modos no son conexiones reales). Si los datos llegaron para cualquiera de las dos conexiones, el sistema no pudo decir a qué conexión pertenecen los datos. Al menos la dirección de destino o el puerto de destino deben ser diferentes para cada conexión, de modo que el sistema no tenga problemas para identificar a qué conexión pertenecen los datos entrantes.

Por lo tanto, si une dos zócalos del mismo protocolo a la misma dirección de origen y puerto e intenta conectarlos a la misma dirección de destino y puerto, connect() realmente fallará con el error EADDRINUSE para el segundo socket que intentas conectar, lo que significa que ya está conectado un socket con una tupla idéntica de cinco valores.

Direcciones de multidifusión

La mayoría de las personas ignoran 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 de uno a muchos. La mayoría de las personas se dieron cuenta de las direcciones de multidifusión cuando se enteraron de IPv6, pero también existían direcciones de multidifusión en IPv4, aunque esta característica nunca fue ampliamente utilizada en Internet público.

El significado de SO_REUSEADDR cambios para las direcciones de multidifusión, ya que permite que varios sockets se vinculen exactamente a la misma combinación de puerto y dirección de multidifusión de origen. En otras palabras, para direcciones de multidifusión SO_REUSEADDR se comporta exactamente como SO_REUSEPORT para direcciones de unidifusión En realidad, el código trata SO_REUSEADDR y SO_REUSEPORT de forma idéntica para las direcciones de multidifusión, eso significa que podría decir que SO_REUSEADDR implica SO_REUSEPORT para todas las direcciones de multidifusión y al revés.


FreeBSD / OpenBSD / NetBSD

Todos estos son tenedores más bien 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 un tenedor bastante tardío del código BSD (BSD 4.3), que luego se volvió a sincronizar incluso con la (en ese momento actual) base de código FreeBSD 5 para la versión Mac OS 10.3, para que Apple pudiera ganar cumplimiento completo de POSIX (macOS tiene certificación POSIX). A pesar de tener un microkernel en su núcleo ("Mach"), el resto del kernel ("XNU") es básicamente solo un núcleo 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 kernel ligeramente modificado y recortado, un conjunto de herramientas de espacio de usuario un tanto reducido y un conjunto de marcos por defecto ligeramente diferente. watchOS y tvOS son horquillas iOS, que se desmontan aún más (especialmente watchOS). Que yo sepa, todos se comportan exactamente como lo hace macOS.


Linux

Linux <3.9

Antes de Linux 3.9, solo la opción SO_REUSEADDR existió. Esta opción se comporta generalmente de la misma manera que en BSD con dos excepciones importantes:

  1. Siempre que un socket TCP de escucha (servidor) esté vinculado a un puerto específico, el SO_REUSEADDR la 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 tener SO_REUSEADDR conjunto. P.ej. no se puede vincular 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 vincularse al mismo puerto y a dos direcciones diferentes que no sean comodines, ya que siempre está permitido. En este aspecto, Linux es más restrictivo que BSD.

  2. La segunda excepción es que para los sockets de los clientes, esta opción se comporta exactamente como SO_REUSEPORT en BSD, siempre que ambos tuvieran este indicador establecido antes de que estuvieran vinculados. La razón para permitir eso fue simplemente que es importante poder unir múltiples tomas exactamente a 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 fue alterado en consecuencia para llenar ese vacío. En ese aspecto, Linux es menos restrictivo que BSD.

Linux> = 3.9

Linux 3.9 agregó la opción SO_REUSEPORT a Linux también. Esta opción se comporta exactamente igual que la opción en BSD y permite vincular exactamente 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 para SO_REUSEPORT en otros sistemas:

  1. Para evitar el "secuestro de puerto", hay una limitación especial: Todos los sockets que deseen compartir la misma combinación de dirección y puerto deben pertenecer a procesos que comparten el mismo ID de usuario efectivo. Entonces, un usuario no puede "robar" los puertos de otro usuario. Esto es algo de magia especial para compensar algo por la falta SO_EXCLBIND/SO_EXCLUSIVEADDRUSE banderas.

  2. Además, el kernel realiza un poco de "magia especial" para SO_REUSEPORT conectores que no se encuentran en otros sistemas operativos: para sockets UDP, trata de distribuir los datagramas de manera uniforme, para los sockets de escucha TCP, trata de distribuir las solicitudes de conexión entrantes (las aceptadas por las llamadas) accept()) uniformemente 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 usar 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 núcleo de Linux ligeramente modificado, por lo tanto, todo lo que se aplica a Linux debe aplicarse a Android también.


Windows

Windows solo conoce el SO_REUSEADDR opción, no hay SO_REUSEPORT. Ajuste SO_REUSEADDR en un socket en Windows se comporta como configuración SO_REUSEPORT y SO_REUSEADDR en un zócalo en BSD, con una excepción: un zócalo con SO_REUSEADDR siempre se puede unir exactamente a la misma dirección de origen y puerto que un socket ya encuadernado, incluso si el otro socket no tenía esta opción establecida cuando estaba enlazada. Este comportamiento es algo peligroso porque permite que una aplicación "robe" el puerto conectado de otra aplicación. Huelga 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. Ajuste SO_EXCLUSIVEADDRUSE en un socket asegura 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 vincularse a ellos, ni siquiera si tiene SO_REUSEADDR conjunto.

Para más detalles sobre cómo las banderas SO_REUSEADDR y SO_EXCLUSIVEADDRUSE Trabajar en Windows, cómo influyen en el enlace / reingreso, Microsoft proporcionó amablemente una tabla similar a mi mesa cerca de la parte superior de esa respuesta. Solo visita esta pagina y desplazarse un poco hacia abajo. En realidad, hay tres tablas, la primera muestra el comportamiento anterior (antes de Windows 2003), la segunda el comportamiento (Windows 2003 y posteriores) y la tercera muestra cómo cambia el comportamiento en Windows 2003 y posterior si el bind() las llamadas son hechas por diferentes usuarios.


Solaris

Solaris es el sucesor de SunOS. SunOS se basó originalmente en una bifurcación de BSD, SunOS 5 y posterior se basó en una bifurcación de SVR4, sin embargo, SVR4 es una combinación de BSD, System V y Xenix, por lo que hasta cierto punto Solaris también es una bifurcación de BSD, y una bastante temprano. Como resultado, Solaris solo sabe SO_REUSEADDR, no hay SO_REUSEPORT. los SO_REUSEADDR se comporta de forma muy parecida a como lo hace en BSD. Hasta donde yo sé, no hay forma de obtener el mismo comportamiento que SO_REUSEPORTen Solaris, eso significa que no es posible enlazar dos tomas con exactamente la misma dirección y puerto.

Al igual que en Windows, Solaris tiene una opción para dar a un socket un enlace exclusivo. Esta opción se llama SO_EXCLBIND. Si esta opción está configurada en un socket antes de vincularlo, configure SO_REUSEADDR en otro zócalo no tiene efecto si los dos zócalos se prueban para un conflicto de dirección. P.ej. Si socketA está vinculado a una dirección de comodín y socketB tiene SO_REUSEADDR habilitado y está vinculado a una dirección no comodín y el mismo puerto como socketA, este enlace normalmente tendrá éxito, a menos que socketA tenido SO_EXCLBIND habilitado, en cuyo caso fallará independientemente de SO_REUSEADDR Bandera 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 el sistema estas dos opciones. Además, si crees que mis resultados son incorrectos, primero ejecute ese programa antes de publicar cualquier comentario y posiblemente hacer afirmaciones falsas.

Todo lo que el código requiere 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 no C99 funcionarán tan bien como ofrezcan inttypes.h y stdbool.h; p.ej. gcc admitido desde mucho antes de ofrecer compatibilidad total con C99).

Todo lo que el programa necesita para ejecutar es que al menos una interfaz en su sistema (que no sea la interfaz local) tiene una dirección IP asignada y que se establece una ruta predeterminada que utiliza 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 puedas pensar:

  • Protocolo TCP y UDP
  • Zócalos normales, conectores de escucha (servidor), zócalos de multidifusión
  • SO_REUSEADDR establecer en sockets socket1, socket2 o ambos
  • SO_REUSEPORT establecer en sockets socket1, socket2 o ambos
  • Todas las combinaciones de direcciones que puede hacer con 0.0.0.0 (comodín), 127.0.0.1 (dirección específica) y la segunda dirección específica encontrada en su interfaz principal (para multidifusión es solo 224.1.2.3 en todas las pruebas)

e imprime los resultados en una buena mesa. 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 sobre enchufes en TIME_WAIT declara que es muy complicado forzar y mantener un socket en ese estado. Afortunadamente, la mayoría de los sistemas operativos parecen simplemente comportarse como BSD aquí y la mayoría de los programadores pueden simplemente 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 más allá del límite).


1299
2018-01-17 21:45