Benvenuti nel meraviglioso mondo della portabilità ... o meglio della sua mancanza. Prima di iniziare ad analizzare in dettaglio queste due opzioni e dare uno sguardo più approfondito al modo in cui i diversi sistemi operativi le gestiscono, va notato che l'implementazione del socket BSD è la madre di tutte le implementazioni del socket. Fondamentalmente tutti gli altri sistemi hanno copiato l'implementazione del socket BSD ad un certo punto nel tempo (o almeno le sue interfacce) e poi hanno iniziato ad evolverlo da soli. Naturalmente anche l'implementazione del socket BSD si è evoluta allo stesso tempo e quindi i sistemi che l'hanno copiata in seguito hanno caratteristiche che mancavano nei sistemi che l'avevano copiato in precedenza. Comprendere l'implementazione del socket BSD è la chiave per comprendere tutte le altre implementazioni del socket, quindi dovresti leggere a riguardo anche se non ti interessa scrivere mai codice per un sistema BSD.
Ci sono un paio di nozioni di base che dovresti conoscere prima di esaminare queste due opzioni. Una connessione TCP / UDP è identificata da una tupla di cinque valori:
{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}
Qualsiasi combinazione univoca di questi valori identifica una connessione. Di conseguenza, due connessioni non possono avere gli stessi cinque valori, altrimenti il sistema non sarebbe più in grado di distinguerle.
Il protocollo di un socket viene impostato quando viene creato un socket con la socket()
funzione. L'indirizzo e la porta di origine sono impostati con la bind()
funzione. L'indirizzo e la porta di destinazione sono impostati con la connect()
funzione. Poiché UDP è un protocollo senza connessione, i socket UDP possono essere utilizzati senza collegarli. Tuttavia, è consentito collegarli e in alcuni casi è molto vantaggioso per il codice e la progettazione generale dell'applicazione. In modalità senza connessione, i socket UDP che non sono stati esplicitamente associati quando i dati vengono inviati per la prima volta su di essi sono normalmente associati automaticamente dal sistema, poiché un socket UDP non associato non può ricevere alcun dato (risposta). Lo stesso vale per un socket TCP non associato, viene automaticamente associato prima di essere collegato.
Se si associa esplicitamente un socket, è possibile associarlo alla porta 0
, che significa "qualsiasi porta". Dal momento che un socket non può realmente essere associato a tutte le porte esistenti, il sistema dovrà scegliere una porta specifica stessa in quel caso (di solito da un intervallo predefinito di porte di origine specifiche del sistema operativo). Esiste un carattere jolly simile per l'indirizzo di origine, che può essere "qualsiasi indirizzo" ( 0.0.0.0
nel caso di IPv4 e::
in caso di IPv6). A differenza del caso delle porte, un socket può davvero essere associato a "qualsiasi indirizzo" che significa "tutti gli indirizzi IP di origine di tutte le interfacce locali". Se il socket viene collegato in un secondo momento, il sistema deve scegliere un indirizzo IP di origine specifico, poiché un socket non può essere collegato e allo stesso tempo essere associato a qualsiasi indirizzo IP locale. A seconda dell'indirizzo di destinazione e del contenuto della tabella di routing, il sistema sceglierà un indirizzo di origine appropriato e sostituirà l'associazione "qualsiasi" con un'associazione all'indirizzo IP di origine selezionato.
Per impostazione predefinita, non è possibile associare due socket alla stessa combinazione di indirizzo e porta di origine. Finché la porta di origine è diversa, l'indirizzo di origine è effettivamente irrilevante. Il collegamento socketA
a A:X
e socketB
a B:Y
, dove A
e B
sono indirizzi e X
e Y
sono porte, è sempre possibile purché sia X != Y
vero. Tuttavia, anche se X == Y
, l'associazione è ancora possibile purché sia A != B
vera. Ad esempio, socketA
appartiene a un programma server FTP ed è associato 192.168.0.1:21
e socketB
appartiene a un altro programma server FTP ed è associato 10.0.0.1:21
, entrambi i collegamenti avranno esito positivo. Tieni presente, tuttavia, che un socket può essere localmente associato a "qualsiasi indirizzo". Se è associato un socket0.0.0.0:21
, è associato a tutti gli indirizzi locali esistenti contemporaneamente e in tal caso nessun altro socket può essere associato alla porta 21
, indipendentemente dall'indirizzo IP specifico a cui tenta di collegarsi, poiché è in 0.0.0.0
conflitto con tutti gli indirizzi IP locali esistenti.
Tutto ciò che è stato detto finora è praticamente uguale per tutti i principali sistemi operativi. Le cose iniziano a diventare specifiche del sistema operativo quando entra in gioco il riutilizzo degli indirizzi. Iniziamo con BSD, poiché come ho detto sopra, è la madre di tutte le implementazioni di socket.
BSD
SO_REUSEADDR
Se SO_REUSEADDR
è abilitato su un socket prima di associarlo, il socket può essere associato correttamente a meno che non vi sia un conflitto con un altro socket associato esattamente alla stessa combinazione di indirizzo e porta di origine. Ora potresti chiederti come è diverso da prima? La parola chiave è "esattamente". SO_REUSEADDR
cambia principalmente il modo in cui gli indirizzi jolly ("qualsiasi indirizzo IP") vengono trattati durante la ricerca di conflitti.
Senza SO_REUSEADDR
, vincolante socketA
per 0.0.0.0:21
poi vincolante socketB
per 192.168.0.1:21
fallirà (con errore EADDRINUSE
), poiché 0.0.0.0 significa "qualsiasi indirizzo IP locale", così tutti gli indirizzi IP locali sono considerati in uso da questa presa e questo include 192.168.0.1
, anche. Con SO_REUSEADDR
esso avrà successo, poiché 0.0.0.0
e non192.168.0.1
sono esattamente lo stesso indirizzo, uno è un carattere jolly per tutti gli indirizzi locali e l'altro è un indirizzo locale molto specifico. Si noti che la precedente affermazione è vera indipendentemente dall'ordine socketA
e dal socketB
limite; senza di SO_REUSEADDR
essa fallirà sempre, con SO_REUSEADDR
esso avrà sempre successo.
Per darti una panoramica migliore, facciamo una tabella qui ed elenchiamo tutte le possibili combinazioni:
SO_REUSEADDR socketA socketB Risultato
-------------------------------------------------- -------------------
ON / OFF 192.168.0.1:21 Errore 192.168.0.1:21 (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 Errore (EADDRINUSE)
OFF 192.168.1.0:21 0.0.0.0:21 Errore (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 Errore (EADDRINUSE)
La tabella sopra presuppone che socketA
sia già stato associato correttamente all'indirizzo indicato socketA
, quindi socketB
viene creato, viene SO_REUSEADDR
impostato o meno e infine viene associato all'indirizzo indicato socketB
. Result
è il risultato dell'operazione di associazione per socketB
. Se la prima colonna dice ON/OFF
, il valore di SO_REUSEADDR
è irrilevante per il risultato.
Va bene, SO_REUSEADDR
ha un effetto sugli indirizzi jolly, buono a sapersi. Eppure non è solo l'effetto che ha. C'è un altro effetto ben noto che è anche il motivo per cui la maggior parte delle persone utilizza SO_REUSEADDR
in primo luogo i programmi server. Per l'altro importante uso di questa opzione dobbiamo dare uno sguardo più approfondito al funzionamento del protocollo TCP.
Un socket ha un buffer di invio e se una chiamata alla send()
funzione ha esito positivo, ciò non significa che i dati richiesti siano stati effettivamente inviati, significa solo che i dati sono stati aggiunti al buffer di invio. Per i socket UDP, i dati vengono in genere inviati molto presto, se non immediatamente, ma per i socket TCP, può esserci un ritardo relativamente lungo tra l'aggiunta di dati al buffer di invio e l'implementazione TCP che li invia realmente. Di conseguenza, quando si chiude un socket TCP, potrebbero esserci ancora dati in sospeso nel buffer di invio, che non è stato ancora inviato ma il codice lo considera inviato, poichésend()
chiamata riuscita. Se l'implementazione TCP chiudesse immediatamente il socket sulla tua richiesta, tutti questi dati andrebbero persi e il tuo codice non lo saprebbe nemmeno. Si dice che TCP sia un protocollo affidabile e perdere dati in questo modo non è molto affidabile. Ecco perché un socket che ha ancora dati da inviare andrà in uno stato chiamato TIME_WAIT
quando lo si chiude. In quello stato attenderà fino a quando tutti i dati in sospeso non saranno stati inviati correttamente o fino a quando non verrà raggiunto un timeout, nel qual caso il socket verrà chiuso forzatamente.
Il tempo che il kernel attenderà prima di chiudere il socket, indipendentemente dal fatto che abbia ancora dati in volo o meno, è chiamato Linger Time . Il Linger Time è configurabile a livello globale sulla maggior parte dei sistemi e per impostazione predefinita è piuttosto lungo (due minuti è un valore comune che troverai su molti sistemi). È inoltre configurabile per socket utilizzando l'opzione socket SO_LINGER
che può essere utilizzata per ridurre o prolungare il timeout e persino per disabilitarlo completamente. Disabilitarlo completamente è una pessima idea, dal momento che chiudere un socket TCP con grazia è un processo leggermente complesso e comporta l'invio e il ritorno di un paio di pacchetti (oltre a rispedire quei pacchetti nel caso in cui si perdessero) e l'intero processo di chiusura è anche limitato dal Linger Time. Se disabiliti il persistere, il tuo socket potrebbe non solo perdere dati in volo, ma sarà anche sempre chiuso forzatamente anziché con garbo, cosa che di solito non è raccomandata. I dettagli su come una connessione TCP viene chiusa con grazia vanno oltre lo scopo di questa risposta, se vuoi saperne di più, ti consiglio di dare un'occhiata a questa pagina . E anche se hai disabilitato di indugiare SO_LINGER
, se il tuo processo si interrompe senza chiudere esplicitamente il socket, BSD (e possibilmente altri sistemi) rimarrà comunque, ignorando ciò che hai configurato. Questo accadrà ad esempio se il tuo codice chiama e bastaexit()
(abbastanza comune per programmi server minuscoli e semplici) o il processo viene interrotto da un segnale (che include la possibilità che si blocchi semplicemente a causa di un accesso illegale alla memoria). Quindi non c'è niente che tu possa fare per assicurarti che un socket non indugi mai in tutte le circostanze.
La domanda è: in che modo il sistema tratta un socket nello stato TIME_WAIT
? Se SO_REUSEADDR
non impostato, TIME_WAIT
si considera che un socket in stato sia ancora associato all'indirizzo e alla porta di origine e qualsiasi tentativo di associare un nuovo socket allo stesso indirizzo e porta fallirà fino a quando il socket non sarà stato realmente chiuso, il che potrebbe richiedere del tempo come Linger Time configurato . Quindi non aspettarti di poter ricollegare l'indirizzo di origine di un socket subito dopo averlo chiuso. Nella maggior parte dei casi questo fallirà. Tuttavia, se SO_REUSEADDR
è impostato per il socket che si sta tentando di associare, un altro socket è associato allo stesso indirizzo e porta nello statoTIME_WAIT
viene semplicemente ignorato, dopo tutto è già "mezzo morto", e il tuo socket può legarsi esattamente allo stesso indirizzo senza alcun problema. In tal caso, non ha alcun ruolo il fatto che l'altro socket possa avere esattamente lo stesso indirizzo e porta. Si noti che l'associazione di un socket esattamente allo stesso indirizzo e porta di un socket morente nello TIME_WAIT
stato può avere effetti collaterali imprevisti, e di solito indesiderati, nel caso in cui l'altro socket sia ancora "al lavoro", ma questo va oltre lo scopo di questa risposta e fortunatamente questi effetti collaterali sono piuttosto rari nella pratica.
C'è un'ultima cosa che dovresti sapere SO_REUSEADDR
. Tutto quanto scritto sopra funzionerà finché il socket a cui vuoi associare ha il riutilizzo dell'indirizzo abilitato. Non è necessario che l'altro socket, quello che è già associato o sia in uno TIME_WAIT
stato, abbia anch'esso impostato questo flag quando era associato. Il codice che decide se il bind avrà esito positivo o negativo controlla solo il SO_REUSEADDR
flag del socket inserito nella bind()
chiamata, per tutti gli altri socket controllati, questo flag non viene nemmeno esaminato.
SO_REUSEPORT
SO_REUSEPORT
è ciò che la maggior parte delle persone si aspetterebbe SO_REUSEADDR
di essere. Fondamentalmente, SO_REUSEPORT
consente di associare un numero arbitrario di socket esattamente allo stesso indirizzo e porta di origine, purché anche tutti i socket associati SO_REUSEPORT
precedenti siano stati impostati prima che fossero associati. Se il primo socket associato a un indirizzo e una porta non è SO_REUSEPORT
impostato, nessun altro socket può essere associato esattamente allo stesso indirizzo e porta, indipendentemente dal fatto che l'altro socket sia SO_REUSEPORT
impostato o meno, fino a quando il primo socket non rilascerà nuovamente il suo binding. A differenza del caso in cui SO_REUESADDR
la gestione del codice SO_REUSEPORT
non solo verificherà che il socket attualmente associato sia SO_REUSEPORT
impostato, ma verificherà anche che il socket con un indirizzo e una porta in conflitto era SO_REUSEPORT
impostato quando era associato.
SO_REUSEPORT
non implica SO_REUSEADDR
. Ciò significa che se un socket non è stato SO_REUSEPORT
impostato quando è stato associato e un altro socket è stato SO_REUSEPORT
impostato quando è associato esattamente allo stesso indirizzo e porta, il bind non riesce, come previsto, ma fallisce anche se l'altro socket sta già morendo e è nello TIME_WAIT
stato. Per poter associare un socket agli stessi indirizzi e alla stessa porta di un altro socket nello TIME_WAIT
stato, è necessario che sia SO_REUSEADDR
impostato su quel socket o che SO_REUSEPORT
sia stato impostato su entrambi i socket prima di collegarli. Ovviamente è consentito impostare entrambi SO_REUSEPORT
e SO_REUSEADDR
, su un socket.
Non c'è molto altro da dire SO_REUSEPORT
a parte che è stato aggiunto più tardi SO_REUSEADDR
, ecco perché non lo troverai in molte implementazioni socket di altri sistemi, che hanno "biforcato" il codice BSD prima che questa opzione fosse aggiunta, e che non ci fosse modo di associare due socket esattamente allo stesso indirizzo socket in BSD prima di questa opzione.
Connect () Restituisce EADDRINUSE?
Molte persone sanno che bind()
potrebbe non riuscire con l'errore EADDRINUSE
, tuttavia, quando inizi a giocare con il riutilizzo degli indirizzi, potresti imbatterti nella strana situazione che connect()
fallisce anche con quell'errore. Come può essere? Come può un indirizzo remoto, dopo tutto ciò che Connect aggiunge a un socket, essere già in uso? Collegare più socket esattamente allo stesso indirizzo remoto non è mai stato un problema prima, quindi cosa c'è che non va?
Come ho detto all'inizio della mia risposta, una connessione è definita da una tupla di cinque valori, ricordi? E ho anche detto che questi cinque valori devono essere univoci, altrimenti il sistema non può più distinguere due connessioni, giusto? Bene, con il riutilizzo degli indirizzi, è possibile associare due socket dello stesso protocollo allo stesso indirizzo e porta di origine. Ciò significa che tre di questi cinque valori sono già gli stessi per questi due socket. Se ora provi a connettere entrambi questi socket anche allo stesso indirizzo e porta di destinazione, creeresti due socket connessi, le cui tuple sono assolutamente identiche. Questo non può funzionare, almeno non per le connessioni TCP (le connessioni UDP non sono comunque connessioni reali). Se i dati sono arrivati per una delle due connessioni, il sistema non è in grado di dire a quale connessione appartengono i dati.
Pertanto, se si associano due socket dello stesso protocollo allo stesso indirizzo e porta di origine e si tenta di collegarli entrambi allo stesso indirizzo e porta di destinazione, connect()
in realtà non riuscirà con l'errore EADDRINUSE
per il secondo socket che si tenta di connettersi, il che significa che un socket con una tupla identica di cinque valori è già collegato.
Indirizzi multicast
Molte persone ignorano il fatto che esistano indirizzi multicast, ma esistono. Mentre gli indirizzi unicast vengono utilizzati per le comunicazioni one-to-one, gli indirizzi multicast vengono utilizzati per le comunicazioni one-to-many. La maggior parte delle persone è venuta a conoscenza degli indirizzi multicast quando ha appreso dell'IPv6, ma gli indirizzi multicast esistevano anche in IPv4, anche se questa funzionalità non è mai stata ampiamente utilizzata su Internet pubblica.
Il significato delle SO_REUSEADDR
modifiche per gli indirizzi multicast in quanto consente a più socket di essere associati esattamente alla stessa combinazione di indirizzo e porta multicast di origine. In altre parole, per gli indirizzi multicast SO_REUSEADDR
si comporta esattamente come SO_REUSEPORT
per gli indirizzi unicast. In realtà, il codice tratta SO_REUSEADDR
e SO_REUSEPORT
identicamente per gli indirizzi multicast, ciò significa che si potrebbe dire che SO_REUSEADDR
implica SO_REUSEPORT
tutti gli indirizzi multicast e viceversa.
FreeBSD / OpenBSD / NetBSD
Tutte queste sono forcelle piuttosto recenti del codice BSD originale, ecco perché offrono tutte e tre le stesse opzioni di BSD e si comportano allo stesso modo di BSD.
macOS (MacOS X)
Fondamentalmente, macOS è semplicemente un UNIX in stile BSD chiamato " Darwin ", basato su un fork piuttosto recente del codice BSD (BSD 4.3), che in seguito è stato anche risincronizzato con il FreeBSD (a quel tempo attuale) Base di codice 5 per la versione Mac OS 10.3, in modo che Apple potesse ottenere la piena conformità POSIX (macOS è certificato POSIX). Nonostante abbia un microkernel nel suo nucleo (" Mach "), il resto del kernel (" XNU ") è fondamentalmente solo un kernel BSD, ed è per questo che macOS offre le stesse opzioni di BSD e si comportano allo stesso modo di BSD .
iOS / watchOS / tvOS
iOS è solo un fork di macOS con un kernel leggermente modificato e rifinito, un po 'ridotto set di strumenti per lo spazio utente e un set di framework predefinito leggermente diverso. watchOS e tvOS sono forcelle iOS, ulteriormente ridotte (in particolare watchOS). Per quanto ne so, tutti si comportano esattamente come macOS.
Linux
Linux <3.9
Prima di Linux 3.9, SO_REUSEADDR
esisteva solo l'opzione . Questa opzione si comporta generalmente come in BSD con due importanti eccezioni:
Finché un socket TCP in ascolto (server) è associato a una porta specifica, l' SO_REUSEADDR
opzione viene completamente ignorata per tutti i socket destinati a tale porta. Associare un secondo socket alla stessa porta è possibile solo se era possibile anche in BSD senza averlo SO_REUSEADDR
impostato. Ad esempio, non è possibile eseguire il binding a un indirizzo jolly e quindi a uno più specifico o viceversa, entrambi sono possibili in BSD se impostato SO_REUSEADDR
. Quello che puoi fare è associarti alla stessa porta e a due diversi indirizzi non jolly, come sempre consentito. Sotto questo aspetto Linux è più restrittivo di BSD.
La seconda eccezione è che per i socket client, questa opzione si comporta esattamente come SO_REUSEPORT
in BSD, a condizione che entrambi abbiano impostato questo flag prima di essere associati. Il motivo per cui era consentito era semplicemente che era importante essere in grado di associare più socket esattamente allo stesso indirizzo socket UDP per vari protocolli e, poiché non esisteva SO_REUSEPORT
prima del 3.9, il comportamento di è SO_REUSEADDR
stato modificato di conseguenza per colmare tale lacuna . Sotto questo aspetto Linux è meno restrittivo di BSD.
Linux> = 3.9
Linux 3.9 ha aggiunto l'opzione anche SO_REUSEPORT
a Linux. Questa opzione si comporta esattamente come l'opzione in BSD e consente l'associazione esattamente allo stesso indirizzo e numero di porta purché tutti i socket abbiano questa opzione impostata prima di associarli.
Tuttavia, ci sono ancora due differenze rispetto SO_REUSEPORT
ad altri sistemi:
Per impedire il "dirottamento delle porte", esiste una limitazione speciale: tutti i socket che desiderano condividere lo stesso indirizzo e la stessa combinazione di porte devono appartenere a processi che condividono lo stesso ID utente effettivo! Quindi un utente non può "rubare" le porte di un altro utente. Questa è una magia speciale per compensare in qualche modo la mancanza SO_EXCLBIND
/ SO_EXCLUSIVEADDRUSE
bandiere.
Inoltre il kernel esegue un po 'di "magia speciale" per i SO_REUSEPORT
socket che non si trovano in altri sistemi operativi: per i socket UDP, tenta di distribuire i datagrammi in modo uniforme, per i socket di ascolto TCP, tenta di distribuire le richieste di connessione in entrata (quelle accettate chiamando accept()
) uniformemente su tutti i socket che condividono la stessa combinazione di indirizzo e porta. Pertanto un'applicazione può facilmente aprire la stessa porta in più processi figlio e quindi utilizzare SO_REUSEPORT
per ottenere un bilanciamento del carico molto economico.
androide
Anche se l'intero sistema Android è in qualche modo diverso dalla maggior parte delle distribuzioni Linux, al suo interno funziona un kernel Linux leggermente modificato, quindi tutto ciò che si applica a Linux dovrebbe applicarsi anche ad Android.
finestre
Windows conosce solo l' SO_REUSEADDR
opzione, non esiste SO_REUSEPORT
. L'impostazione SO_REUSEADDR
su un socket in Windows si comporta come impostazione SO_REUSEPORT
e SO_REUSEADDR
su un socket in BSD, con un'eccezione: un socket con SO_REUSEADDR
può sempre associarsi esattamente allo stesso indirizzo e porta di origine di un socket già associato, anche se l'altro socket non aveva questa opzione impostato quando era legato . Questo comportamento è alquanto pericoloso perché consente a un'applicazione di "rubare" la porta connessa di un'altra applicazione. Inutile dire che ciò può avere importanti implicazioni per la sicurezza. Microsoft ha capito che questo potrebbe essere un problema e ha quindi aggiunto un'altra opzione socket SO_EXCLUSIVEADDRUSE
. AmbientazioneSO_EXCLUSIVEADDRUSE
su un socket si assicura che se l'associazione ha esito positivo, la combinazione di indirizzo di origine e porta è di proprietà esclusiva di questo socket e nessun altro socket può collegarsi ad essi, anche se è SO_REUSEADDR
impostato.
Per ulteriori dettagli su come i flag SO_REUSEADDR
e il SO_EXCLUSIVEADDRUSE
funzionamento su Windows, su come influenzano l'associazione / la rilegatura, Microsoft ha gentilmente fornito una tabella simile alla mia tabella nella parte superiore di quella risposta. Basta visitare questa pagina e scorrere un po 'verso il basso. In realtà ci sono tre tabelle, la prima mostra il vecchio comportamento (prima di Windows 2003), la seconda il comportamento (da Windows 2003 in poi) e la terza mostra come cambia il comportamento in Windows 2003 e successivamente se le bind()
chiamate vengono effettuate da utenti diversi.
Solaris
Solaris è il successore di SunOS. SunOS era originariamente basato su un fork di BSD, SunOS 5 e successivamente era basato su un fork di SVR4, tuttavia SVR4 è una fusione di BSD, System V e Xenix, quindi fino a un certo punto Solaris è anche un fork di BSD, e un piuttosto presto. Di conseguenza Solaris lo sa solo SO_REUSEADDR
, non esiste SO_REUSEPORT
. Il SO_REUSEADDR
comportamento è più o meno lo stesso di BSD. Per quanto ne so non c'è modo di ottenere lo stesso comportamento SO_REUSEPORT
di Solaris, ciò significa che non è possibile associare due socket esattamente allo stesso indirizzo e porta.
Simile a Windows, Solaris ha un'opzione per assegnare a un socket un'associazione esclusiva. Questa opzione è denominata SO_EXCLBIND
. Se questa opzione è impostata su un socket prima di collegarlo, l'impostazione SO_REUSEADDR
su un altro socket non ha alcun effetto se i due socket vengono testati per un conflitto di indirizzi. Ad esempio, se socketA
è associato a un indirizzo jolly e socketB
ha SO_REUSEADDR
abilitato ed è associato a un indirizzo non jolly e alla stessa porta di socketA
questo collegamento normalmente avrà esito positivo, a meno che non socketA
sia stato SO_EXCLBIND
abilitato, nel qual caso non riuscirà a prescindere dalla SO_REUSEADDR
bandiera di socketB
.
Altri sistemi
Nel caso in cui il tuo sistema non sia elencato sopra, ho scritto un piccolo programma di test che puoi usare per scoprire come il tuo sistema gestisce queste due opzioni. Inoltre, se ritieni che i miei risultati siano errati , esegui prima quel programma prima di pubblicare qualsiasi commento e possibilmente fare affermazioni false.
Tutto ciò che il codice richiede per costruire è un po 'di API POSIX (per le parti di rete) e un compilatore C99 (in realtà la maggior parte dei compilatori non C99 funzionerà tanto quanto offrono inttypes.h
e stdbool.h
; ad esempio gcc
supportati entrambi molto prima di offrire il pieno supporto C99) .
Tutto ciò che il programma deve eseguire è che almeno un'interfaccia nel tuo sistema (diversa dall'interfaccia locale) abbia un indirizzo IP assegnato e che sia impostata una route predefinita che utilizza quell'interfaccia. Il programma raccoglierà quell'indirizzo IP e lo utilizzerà come secondo "indirizzo specifico".
Verifica tutte le possibili combinazioni a cui puoi pensare:
- Protocollo TCP e UDP
- Prese normali, prese di ascolto (server), prese multicast
SO_REUSEADDR
impostato su socket1, socket2 o entrambi
SO_REUSEPORT
impostato su socket1, socket2 o entrambi
- Tutte le combinazioni di indirizzi che è possibile creare
0.0.0.0
(carattere jolly), 127.0.0.1
(indirizzo specifico) e il secondo indirizzo specifico trovato nell'interfaccia principale (per il multicast è solo 224.1.2.3
in tutti i test)
e stampa i risultati in una bella tabella. Funzionerà anche su sistemi che non conoscono SO_REUSEPORT
, nel qual caso questa opzione non è semplicemente testata.
Ciò che il programma non può facilmente testare è come SO_REUSEADDR
agisce sui socket nello TIME_WAIT
stato in quanto è molto difficile forzare e mantenere un socket in quello stato. Fortunatamente la maggior parte dei sistemi operativi sembra semplicemente comportarsi come BSD qui e la maggior parte delle volte i programmatori possono semplicemente ignorare l'esistenza di quello stato.
Ecco il codice (non posso includerlo qui, le risposte hanno un limite di dimensioni e il codice spingerebbe questa risposta oltre il limite).
INADDR_ANY
non associa gli indirizzi locali esistenti, ma anche quelli futuri.listen
crea sicuramente socket con lo stesso protocollo, indirizzo locale e porta locale identici, anche se hai detto che non è possibile.