Bem-vindo ao maravilhoso mundo da portabilidade ... ou melhor, a falta dela. Antes de começarmos a analisar essas duas opções em detalhes e analisar mais detalhadamente como os diferentes sistemas operacionais as tratam, deve-se notar que a implementação do soquete BSD é a mãe de todas as implementações de soquete. Basicamente, todos os outros sistemas copiaram a implementação do soquete BSD em algum momento (ou pelo menos suas interfaces) e começaram a evoluí-la por conta própria. É claro que a implementação do soquete BSD também evoluiu ao mesmo tempo e, portanto, os sistemas que a copiaram mais tarde obtiveram recursos ausentes nos sistemas que a copiaram anteriormente. Compreender a implementação do soquete BSD é a chave para entender todas as outras implementações de soquete; portanto, você deve ler sobre isso, mesmo que não queira escrever código para um sistema BSD.
Existem alguns princípios básicos que você deve saber antes de examinarmos essas duas opções. Uma conexão TCP / UDP é identificada por uma tupla de cinco valores:
{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}
Qualquer combinação exclusiva desses valores identifica uma conexão. Como resultado, duas conexões não podem ter os mesmos cinco valores; caso contrário, o sistema não poderá mais distingui-las.
O protocolo de um soquete é definido quando um soquete é criado com a socket()
função O endereço e a porta de origem são definidos com a bind()
função O endereço e a porta de destino são definidos com a connect()
função Como o UDP é um protocolo sem conexão, os soquetes UDP podem ser usados sem conectá-los. No entanto, é permitido conectá-los e, em alguns casos, muito vantajoso para o seu código e o design geral do aplicativo. No modo sem conexão, os soquetes UDP que não foram explicitamente vinculados quando os dados são enviados pela primeira vez geralmente são automaticamente vinculados pelo sistema, pois um soquete UDP não vinculado não pode receber nenhum dado (resposta). O mesmo vale para um soquete TCP não vinculado, ele é automaticamente vinculado antes de ser conectado.
Se você ligar explicitamente um soquete, é possível vinculá-lo à porta 0
, o que significa "qualquer porta". Como um soquete não pode realmente ser vinculado a todas as portas existentes, nesse caso, o sistema precisará escolher uma porta específica (geralmente de um intervalo predefinido de portas de origem específicas do SO). Existe um curinga semelhante para o endereço de origem, que pode ser "qualquer endereço" ( 0.0.0.0
no caso de IPv4 e::
no caso de IPv6). Diferentemente do caso de portas, um soquete pode realmente ser vinculado a "qualquer endereço", o que significa "todos os endereços IP de origem de todas as interfaces locais". Se o soquete for conectado posteriormente, o sistema deverá escolher um endereço IP de origem específico, pois um soquete não pode ser conectado e, ao mesmo tempo, ser vinculado a qualquer endereço IP local. Dependendo do endereço de destino e do conteúdo da tabela de roteamento, o sistema selecionará um endereço de origem apropriado e substituirá a ligação "any" por uma ligação ao endereço IP de origem escolhido.
Por padrão, nenhum soquete pode ser vinculado à mesma combinação de endereço e porta de origem. Contanto que a porta de origem seja diferente, o endereço de origem é realmente irrelevante. A ligação socketA
para A:X
e socketB
para B:Y
, onde A
e B
são endereços e X
e Y
são portas, sempre é possível enquanto for X != Y
verdadeira. No entanto, mesmo que X == Y
a ligação ainda seja possível enquanto for A != B
verdadeira. Por exemplo, socketA
pertence a um programa de servidor FTP e está vinculado 192.168.0.1:21
e socketB
pertence a outro programa de servidor FTP e está vinculado a 10.0.0.1:21
, ambas as ligações terão êxito. Lembre-se, porém, que um soquete pode estar vinculado localmente a "qualquer endereço". Se um soquete estiver vinculado a0.0.0.0:21
, ele é vinculado a todos os endereços locais existentes ao mesmo tempo e, nesse caso, nenhum outro soquete pode ser vinculado à porta 21
, independentemente de qual endereço IP específico ele tenta se conectar, pois 0.0.0.0
conflita com todos os endereços IP locais existentes.
Tudo o que foi dito até agora é praticamente igual para todos os principais sistemas operacionais. As coisas começam a ficar específicas do SO quando a reutilização de endereço entra em ação. Começamos com o BSD, pois como eu disse acima, é a mãe de todas as implementações de soquete.
BSD
SO_REUSEADDR
Se SO_REUSEADDR
estiver ativado em um soquete antes de vinculá-lo, o soquete poderá ser vinculado com êxito, a menos que haja um conflito com outro soquete vinculado exatamente à mesma combinação de endereço e porta de origem. Agora você pode se perguntar como isso é diferente do que antes? A palavra-chave é "exatamente". SO_REUSEADDR
principalmente altera a maneira como os endereços curinga ("qualquer endereço IP") são tratados ao procurar conflitos.
Sem SO_REUSEADDR
, de ligação socketA
a 0.0.0.0:21
e depois a ligação socketB
para 192.168.0.1:21
falhará (com erro EADDRINUSE
), desde 0.0.0.0 significa "qualquer endereço IP local", portanto, todos os endereços IP locais são considerados em uso por esta tomada e isso inclui 192.168.0.1
também. Com SO_REUSEADDR
isso, será bem-sucedido, uma vez que 0.0.0.0
e não192.168.0.1
é exatamente o mesmo endereço, um é um curinga para todos os endereços locais e o outro é um endereço local muito específico. Observe que a afirmação acima é verdadeira, independentemente da ordem socketA
e do socketB
vínculo; sem SO_REUSEADDR
ele sempre falhará, SO_REUSEADDR
sempre terá sucesso.
Para oferecer uma visão geral melhor, vamos fazer uma tabela aqui e listar todas as combinações possíveis:
Soquete SO_REUSEADDRUm soqueteB Resultado
-------------------------------------------------- -------------------
Erro ON / OFF 192.168.0.1:21 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
Erro OFF 0.0.0.0:21 192.168.1.0:21 (EADDRINUSE)
Erro OFF 192.168.1.0:21 0.0.0.0:21 (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
Erro ON / OFF 0.0.0.0:21 0.0.0.0:21 (EADDRINUSE)
A tabela acima pressupõe que socketA
já foi vinculado com êxito ao endereço fornecido e socketA
, em seguida, socketB
é criado, é SO_REUSEADDR
definido ou não e, finalmente, é vinculado ao endereço fornecido socketB
. Result
é o resultado da operação de ligação para socketB
. Se a primeira coluna indicar ON/OFF
, o valor de SO_REUSEADDR
é irrelevante para o resultado.
Ok, SO_REUSEADDR
tem efeito nos endereços curinga, é bom saber. No entanto, esse não é o único efeito que tem. Há outro efeito conhecido, que também é a razão pela qual a maioria das pessoas usa SO_REUSEADDR
em programas de servidor em primeiro lugar. Para o outro uso importante dessa opção, precisamos examinar mais detalhadamente como o protocolo TCP funciona.
Um soquete possui um buffer de envio e, se uma chamada para a send()
função for bem-sucedida, isso não significa que os dados solicitados foram realmente enviados, apenas significa que os dados foram adicionados ao buffer de envio. Para soquetes UDP, os dados geralmente são enviados muito em breve, se não imediatamente, mas para soquetes TCP, pode haver um atraso relativamente longo entre adicionar dados ao buffer de envio e fazer com que a implementação TCP realmente envie esses dados. Como resultado, quando você fecha um soquete TCP, ainda pode haver dados pendentes no buffer de envio, que ainda não foi enviado, mas seu código considera-o como enviado, pois osend()
chamada realizada. Se a implementação do TCP estivesse fechando o soquete imediatamente em sua solicitação, todos esses dados seriam perdidos e seu código nem saberia disso. Diz-se que o TCP é um protocolo confiável e a perda de dados assim não é muito confiável. É por isso que um soquete que ainda tem dados a enviar entrará em um estado chamado TIME_WAIT
quando você o fechar. Nesse estado, ele aguardará até que todos os dados pendentes tenham sido enviados com sucesso ou até que um tempo limite seja atingido; nesse caso, o soquete será fechado com força.
A quantidade de tempo que o kernel irá esperar antes de ele fecha o socket, independentemente se ele ainda tem dados em voo ou não, é chamado de Tempo Linger . O tempo de espera é globalmente configurável na maioria dos sistemas e, por padrão, bastante longo (dois minutos é um valor comum que você encontrará em muitos sistemas). Também é configurável por soquete usando a opção soquete, SO_LINGER
que pode ser usada para diminuir ou diminuir o tempo limite e até mesmo desativá-lo completamente. Desativá-lo completamente é uma péssima idéia, já que fechar um soquete TCP normalmente é um processo um pouco complexo e envolve enviar e voltar alguns pacotes (além de reenviá-los caso eles se percam) e todo esse processo próximo também é limitado pelo Linger Time. Se você desativar a persistência, seu soquete poderá não apenas perder dados em voo, mas também sempre será fechado com força em vez de normalmente, o que geralmente não é recomendado. Os detalhes sobre como uma conexão TCP é fechada normalmente estão além do escopo desta resposta. Se você quiser saber mais, recomendo que você dê uma olhada nesta página . E mesmo que você desabilite a persistência SO_LINGER
, se seu processo morrer sem fechar explicitamente o soquete, o BSD (e possivelmente outros sistemas) permanecerá, no entanto, ignorando o que você configurou. Isso acontecerá, por exemplo, se seu código apenas chamarexit()
(bastante comum para programas de servidor minúsculos e simples) ou o processo é interrompido por um sinal (que inclui a possibilidade de simplesmente travar devido a um acesso ilegal à memória). Portanto, não há nada que você possa fazer para garantir que um soquete nunca permaneça em todas as circunstâncias.
A questão é: como o sistema trata um soquete no estado TIME_WAIT
? Se SO_REUSEADDR
não estiver definido, considera-se que um soquete no estado TIME_WAIT
ainda esteja vinculado ao endereço e porta de origem e qualquer tentativa de vincular um novo soquete ao mesmo endereço e porta falhará até que o soquete seja realmente fechado, o que pode demorar como o tempo de espera configurado . Portanto, não espere que você possa reconectar o endereço de origem de um soquete imediatamente após fechá-lo. Na maioria dos casos, isso irá falhar. No entanto, se SO_REUSEADDR
estiver definido para o soquete que você está tentando ligar, outro soquete vinculado ao mesmo endereço e porta no estadoTIME_WAIT
é simplesmente ignorado, afinal já está "meio morto", e seu soquete pode se conectar exatamente ao mesmo endereço sem nenhum problema. Nesse caso, não desempenha nenhum papel que o outro soquete possa ter exatamente o mesmo endereço e porta. Observe que a ligação de um soquete exatamente ao mesmo endereço e porta que um soquete que está morrendo no TIME_WAIT
estado pode ter efeitos colaterais inesperados e geralmente indesejados, caso o outro soquete ainda esteja "funcionando", mas isso está além do escopo desta resposta e felizmente esses efeitos colaterais são bastante raros na prática.
Há uma coisa final que você deve saber SO_REUSEADDR
. Tudo o que foi escrito acima funcionará desde que o soquete ao qual você deseja se vincular tenha a reutilização de endereço ativada. Não é necessário que o outro soquete, aquele que já esteja vinculado ou esteja em um TIME_WAIT
estado, também tenha esse sinalizador definido quando foi vinculado. O código que decide se a ligação será bem-sucedida ou falhará apenas inspeciona o SO_REUSEADDR
sinalizador do soquete alimentado na bind()
chamada; para todos os outros soquetes inspecionados, esse sinalizador nem é analisado.
SO_REUSEPORT
SO_REUSEPORT
é o que a maioria das pessoas espera SO_REUSEADDR
ser. Basicamente, SO_REUSEPORT
permite vincular um número arbitrário de soquetes exatamente ao mesmo endereço e porta de origem, desde que todos os soquetes vinculados anteriores também tenham sido SO_REUSEPORT
definidos antes de serem vinculados. Se o primeiro soquete vinculado a um endereço e porta não tiver sido SO_REUSEPORT
definido, nenhum outro soquete poderá ser vinculado exatamente ao mesmo endereço e porta, independentemente se esse outro soquete tiver sido SO_REUSEPORT
definido ou não, até que o primeiro soquete libere sua ligação novamente. Ao contrário do caso do SO_REUESADDR
tratamento de código SO_REUSEPORT
, não apenas verificará se o soquete atualmente vinculado foi SO_REUSEPORT
definido, mas também verificará se o soquete com um endereço e porta conflitantes foi SO_REUSEPORT
definido quando foi vinculado.
SO_REUSEPORT
não implica SO_REUSEADDR
. Isso significa que, se um soquete não tiver sido SO_REUSEPORT
definido quando foi vinculado e outro somado SO_REUSEPORT
quando for vinculado exatamente ao mesmo endereço e porta, a ligação falhará, o que é esperado, mas também falhará se o outro soquete já estiver morrendo e está no TIME_WAIT
estado. Para poder vincular um soquete aos mesmos endereços e porta que outro soquete no TIME_WAIT
estado, é necessário SO_REUSEADDR
definir esse soquete ou SO_REUSEPORT
deve ter sido definido nos dois soquetes antes de vinculá-los. É claro que é permitido definir ambos SO_REUSEPORT
e SO_REUSEADDR
em um soquete.
Não há muito o que dizer SO_REUSEPORT
além de ter sido adicionado posteriormente SO_REUSEADDR
; é por isso que você não o encontrará em muitas implementações de soquete de outros sistemas, que "bifurcaram" o código BSD antes que essa opção fosse adicionada e que não havia maneira de vincular dois soquetes exatamente ao mesmo endereço de soquete no BSD antes desta opção.
Connect () retornando EADDRINUSE?
A maioria das pessoas sabe que bind()
pode falhar com o erro EADDRINUSE
; no entanto, quando você começa a brincar com a reutilização de endereços, pode se deparar com uma situação estranha que também connect()
falha com esse erro. Como isso pode ser? Como um endereço remoto, depois de tudo o que o connect adiciona a um soquete, já está em uso? Conectar vários soquetes exatamente ao mesmo endereço remoto nunca foi um problema antes, então o que está acontecendo de errado aqui?
Como eu disse no topo da minha resposta, uma conexão é definida por uma tupla de cinco valores, lembra? E eu também disse que esses cinco valores devem ser únicos, caso contrário o sistema não poderá mais distinguir duas conexões, certo? Bem, com a reutilização de endereço, você pode vincular dois soquetes do mesmo protocolo ao mesmo endereço e porta de origem. Isso significa que três desses cinco valores já são os mesmos para esses dois soquetes. Se você agora tentar conectar esses dois soquetes também ao mesmo endereço e porta de destino, criaria dois soquetes conectados, cujas tuplas são absolutamente idênticas. Isso não pode funcionar, pelo menos não para conexões TCP (conexões UDP não são conexões reais de qualquer maneira). Se os dados chegassem para uma das duas conexões, o sistema não saberia a qual conexão os dados pertencem.
Portanto, se você vincular dois soquetes do mesmo protocolo ao mesmo endereço e porta de origem e tentar conectá-los ao mesmo endereço e porta de destino, connect()
ocorrerá uma falha no erro EADDRINUSE
do segundo soquete que você tentar conectar, o que significa que um O soquete com uma tupla idêntica de cinco valores já está conectado.
Endereços multicast
A maioria das pessoas ignora o fato de existirem endereços multicast, mas eles existem. Enquanto endereços unicast são usados para comunicação um para um, endereços multicast são usados para comunicação um para muitos. A maioria das pessoas ficou sabendo dos endereços multicast ao saber sobre o IPv6, mas os endereços multicast também existiam no IPv4, embora esse recurso nunca tenha sido amplamente utilizado na Internet pública.
O significado das SO_REUSEADDR
alterações nos endereços multicast, pois permite que vários soquetes sejam vinculados exatamente à mesma combinação de endereço e porta multicast de origem. Em outras palavras, os endereços multicast se SO_REUSEADDR
comportam exatamente como os SO_REUSEPORT
endereços unicast. Na verdade, o código trata SO_REUSEADDR
e SO_REUSEPORT
identicamente os endereços multicast, o que significa que você pode dizer que isso SO_REUSEADDR
implica SO_REUSEPORT
em todos os endereços multicast e vice-versa.
FreeBSD / OpenBSD / NetBSD
Todos esses são garfos bastante atrasados do código BSD original, é por isso que os três oferecem as mesmas opções que o BSD e também se comportam da mesma forma que no BSD.
macOS (MacOS X)
Em sua essência, o macOS é simplesmente um UNIX estilo BSD chamado " Darwin ", baseado em um fork tardio do código BSD (BSD 4.3), que foi posteriormente sincronizado com o FreeBSD (na época atual) Código 5 para a versão do Mac OS 10.3, para que a Apple possa obter total conformidade com o POSIX (o macOS é certificado pelo POSIX). Apesar de ter um microkernel em seu núcleo (" Mach "), o resto do kernel (" XNU ") é basicamente apenas um kernel BSD, e é por isso que o macOS oferece as mesmas opções que o BSD e também se comporta da mesma maneira que no BSD .
iOS / watchOS / tvOS
O iOS é apenas uma bifurcação do macOS com um kernel ligeiramente modificado e aparado, um pouco despojado do conjunto de ferramentas do espaço do usuário e um conjunto de estruturas padrão ligeiramente diferente. watchOS e tvOS são garfos para iOS, que são ainda mais simples (especialmente watchOS). Que eu saiba, todos eles se comportam exatamente como o macOS.
Linux
Linux <3.9
Antes do Linux 3.9, apenas a opção SO_REUSEADDR
existia. Essa opção geralmente se comporta da mesma forma que no BSD, com duas exceções importantes:
Desde que um soquete TCP de escuta (servidor) esteja vinculado a uma porta específica, a SO_REUSEADDR
opção será totalmente ignorada para todos os soquetes direcionados a essa porta. A ligação de um segundo soquete à mesma porta só é possível se isso também foi possível no BSD sem ter SO_REUSEADDR
definido. Por exemplo, você não pode vincular a um endereço curinga e, em seguida, a um endereço mais específico ou o contrário, ambos são possíveis no BSD se você definir SO_REUSEADDR
. O que você pode fazer é ligar a mesma porta e dois endereços diferentes de caracteres não curinga, pois isso sempre é permitido. Nesse aspecto, o Linux é mais restritivo que o BSD.
A segunda exceção é que, para soquetes de cliente, essa opção se comporta exatamente como SO_REUSEPORT
no BSD, desde que ambos tenham esse sinalizador definido antes de serem vinculados. A razão para permitir isso era simplesmente o fato de ser importante ligar vários soquetes exatamente ao mesmo endereço de soquete UDP para vários protocolos e, como não havia SO_REUSEPORT
antes do 3.9, o comportamento de SO_REUSEADDR
foi alterado para preencher essa lacuna. . Nesse aspecto, o Linux é menos restritivo que o BSD.
Linux> = 3.9
O Linux 3.9 adicionou a opção SO_REUSEPORT
ao Linux também. Essa opção se comporta exatamente como a opção no BSD e permite a ligação exatamente ao mesmo endereço e número de porta, desde que todos os soquetes tenham essa opção definida antes da ligação.
No entanto, ainda existem duas diferenças SO_REUSEPORT
em outros sistemas:
Para evitar o "seqüestro de portas", há uma limitação especial: Todos os soquetes que desejam compartilhar o mesmo endereço e combinação de portas devem pertencer a processos que compartilham o mesmo ID de usuário eficaz! Portanto, um usuário não pode "roubar" portas de outro usuário. Esta é uma mágica especial para compensar um pouco os desaparecidos SO_EXCLBIND
/ SO_EXCLUSIVEADDRUSE
bandeiras.
Além disso, o kernel executa alguma "mágica especial" para SO_REUSEPORT
soquetes que não é encontrada em outros sistemas operacionais: para soquetes UDP, ele tenta distribuir datagramas uniformemente; para soquetes de escuta TCP, tenta distribuir solicitações de conexão de entrada (aquelas aceitas pela chamada accept()
) uniformemente em todos os soquetes que compartilham a mesma combinação de endereço e porta. Assim, um aplicativo pode abrir facilmente a mesma porta em vários processos filhos e usá SO_REUSEPORT
-lo para obter um balanceamento de carga muito barato.
Android
Embora todo o sistema Android seja um pouco diferente da maioria das distribuições Linux, em seu núcleo funciona um kernel Linux ligeiramente modificado, portanto, tudo o que se aplica ao Linux também deve se aplicar ao Android.
janelas
O Windows conhece apenas a SO_REUSEADDR
opção, não há SO_REUSEPORT
. A configuração SO_REUSEADDR
em um soquete no Windows se comporta como a configuração SO_REUSEPORT
e SO_REUSEADDR
em um soquete no BSD, com uma exceção: um soquete com SO_REUSEADDR
sempre pode vincular exatamente ao mesmo endereço e porta de origem que um soquete já vinculado, mesmo que o outro soquete não possua essa opção. definido quando foi encadernado . Esse comportamento é um pouco perigoso, pois permite que um aplicativo "roube" a porta conectada de outro aplicativo. Escusado será dizer que isso pode ter grandes implicações de segurança. A Microsoft percebeu que isso poderia ser um problema e, portanto, adicionou outra opção de soquete SO_EXCLUSIVEADDRUSE
. ConfiguraçãoSO_EXCLUSIVEADDRUSE
em um soquete garante que, se a ligação for bem-sucedida, a combinação de endereço e porta de origem pertencer exclusivamente a esse soquete e nenhum outro soquete poderá se ligar a eles, nem mesmo se tiver sido SO_REUSEADDR
definido.
Para obter ainda mais detalhes sobre como os sinalizadores SO_REUSEADDR
e SO_EXCLUSIVEADDRUSE
funcionam no Windows, como eles influenciam a ligação / re-ligação, a Microsoft gentilmente forneceu uma tabela semelhante à minha tabela perto da parte superior dessa resposta. Basta visitar esta página e rolar um pouco para baixo. Na verdade, existem três tabelas, a primeira mostra o comportamento antigo (anterior ao Windows 2003), a segunda o comportamento (Windows 2003 e posterior) e a terceira mostra como o comportamento muda no Windows 2003 e posterior, se as bind()
chamadas são feitas por usuários diferentes.
Solaris
Solaris é o sucessor do SunOS. O SunOS foi originalmente baseado em um fork do BSD, o SunOS 5 e, mais tarde, em um fork do SVR4; no entanto, o SVR4 é uma mesclagem do BSD, System V e Xenix; portanto, até certo ponto, o Solaris também é um fork do BSD e um bastante cedo. Como resultado, Solaris sabe apenas SO_REUSEADDR
, não há SO_REUSEPORT
. O SO_REUSEADDR
comportamento se comporta da mesma forma que no BSD. Tanto quanto sei, não há como obter o mesmo comportamento SO_REUSEPORT
do Solaris, isso significa que não é possível vincular dois soquetes exatamente ao mesmo endereço e porta.
Semelhante ao Windows, o Solaris tem uma opção para atribuir a um soquete uma ligação exclusiva. Esta opção é nomeada SO_EXCLBIND
. Se essa opção for definida em um soquete antes de vinculá-lo, a configuração SO_REUSEADDR
em outro soquete não terá efeito se os dois soquetes forem testados quanto a um conflito de endereço. Por exemplo, se socketA
estiver vinculado a um endereço curinga e socketB
tiver SO_REUSEADDR
habilitado e estiver vinculado a um endereço não curinga e a mesma porta que socketA
, essa ligação normalmente será bem-sucedida, a menos que socketA
tenha sido SO_EXCLBIND
ativada, caso em que falhará independentemente do SO_REUSEADDR
sinalizador de socketB
.
Outros sistemas
Caso seu sistema não esteja listado acima, escrevi um pequeno programa de teste que você pode usar para descobrir como o sistema lida com essas duas opções. Além disso, se você acha que meus resultados estão errados , execute o programa antes de postar comentários e possivelmente fazer falsas alegações.
Tudo o que o código requer para construir é um pouco da API POSIX (para as partes da rede) e um compilador C99 (na verdade, a maioria dos compiladores não-C99 funcionará tão bem quanto eles oferecem inttypes.h
e stdbool.h
, por exemplo, com gcc
suporte muito antes de oferecer suporte completo ao C99) .
Tudo o que o programa precisa executar é que pelo menos uma interface em seu sistema (que não seja a interface local) tenha um endereço IP atribuído e que seja definida uma rota padrão que use essa interface. O programa reunirá esse endereço IP e o usará como o segundo "endereço específico".
Ele testa todas as combinações possíveis que você pode imaginar:
- Protocolo TCP e UDP
- Soquetes normais, soquetes de escuta (servidor), soquetes multicast
SO_REUSEADDR
definido no soquete1, soquete2 ou nos dois soquetes
SO_REUSEPORT
definido no soquete1, soquete2 ou nos dois soquetes
- Todas as combinações de endereços que você pode criar
0.0.0.0
(curinga), 127.0.0.1
(endereço específico) e o segundo endereço específico encontrado na interface principal (para multicast, é apenas 224.1.2.3
em todos os testes)
e imprime os resultados em uma boa tabela. Também funcionará em sistemas que não sabem SO_REUSEPORT
; nesse caso, essa opção simplesmente não é testada.
O que o programa não pode testar facilmente é como SO_REUSEADDR
age nos soquetes no TIME_WAIT
estado, pois é muito difícil forçar e manter um soquete nesse estado. Felizmente, a maioria dos sistemas operacionais parece simplesmente se comportar como o BSD aqui e na maioria das vezes os programadores podem simplesmente ignorar a existência desse estado.
Aqui está o código (não posso incluí-lo aqui, as respostas têm um limite de tamanho e o código levaria essa resposta além do limite).
INADDR_ANY
não vincula endereços locais existentes, mas todos os futuros também.listen
certamente cria soquetes com o mesmo protocolo exato, endereço local e porta local, mesmo que você tenha dito que isso não é possível.INADDR_ANY
, nunca disse que não seria vinculado a endereços futuros. Elisten
não cria nenhum soquete, o que torna sua frase inteira um pouco estranha.A resposta de Mecki é absolutamente perfeita, mas vale a pena acrescentar que o FreeBSD também suporta
SO_REUSEPORT_LB
, o que imita oSO_REUSEPORT
comportamento do Linux - ele equilibra a carga; veja setsockopt (2)fonte