relação - unix and linux
Opções de soquete SO_REUSEADDR e SO_REUSEPORT, como elas diferem? Eles significam o mesmo em todos os principais sistemas operacionais? (1)
Bem-vindo ao maravilhoso mundo da portabilidade ... ou melhor, da falta dele. Antes de começarmos a analisar essas duas opções em detalhes e observar com mais profundidade como os diferentes sistemas operacionais lidam com elas, 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 socket BSD em algum momento (ou pelo menos suas interfaces) e então começaram a desenvolvê-lo sozinhos. É claro que a implementação do soquete BSD também evoluiu ao mesmo tempo e, assim, os sistemas que copiaram posteriormente obtiveram recursos que faltavam nos sistemas que o copiaram anteriormente. Entender a implementação do socket BSD é a chave para entender todas as outras implementações de socket, então você deve ler sobre isso mesmo se você não quiser escrever código para um sistema BSD.
Há algumas noções básicas que você deve saber antes de analisarmos 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 seria capaz de distinguir essas conexões por mais tempo.
O protocolo de um socket é definido quando um socket é criado com a função socket()
. O endereço de origem e a porta são configurados com a função bind()
. O endereço de destino e a porta são configurados com a função connect()
. 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 design geral de aplicativos. No modo sem conexão, os soquetes UDP que não foram explicitamente ligados quando os dados são enviados pela primeira vez são geralmente vinculados automaticamente pelo sistema, pois um soquete UDP não acoplado não pode receber nenhum dado (de resposta). O mesmo é verdadeiro para um soquete TCP não acoplado, ele é automaticamente ligado antes de ser conectado.
Se você vincular explicitamente um soquete, é possível vinculá-lo à porta 0
, que significa "qualquer porta". Como um soquete não pode realmente estar vinculado a todas as portas existentes, o sistema terá que escolher uma porta específica nesse caso (geralmente a partir de um intervalo específico predefinido de portas de origem do SO). Um curinga semelhante existe 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). Diferente do caso de portas, um socket pode realmente estar 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 mais tarde, o sistema deve escolher um endereço IP de origem específico, já que um soquete não pode ser conectado e, ao mesmo tempo, 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, não é possível vincular dois soquetes à mesma combinação de endereço de origem e porta de origem. Desde que a porta de origem seja diferente, o endereço de origem é irrelevante. A ligação de socketA
a A:X
e socketB
a B:Y
, onde A
e B
são endereços e X
e Y
são portas, é sempre possível desde que X != Y
seja verdadeiro. No entanto, mesmo se X == Y
, a ligação ainda é possível, desde que A != B
seja verdadeira. Por exemplo, socketA
pertence a um programa de servidor FTP e está vinculado a 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 serão bem-sucedidas. Tenha em mente, no entanto, que um soquete pode estar vinculado localmente a "qualquer endereço". Se um soquete estiver vinculado a 0.0.0.0:21
, ele estará vinculado a todos os endereços locais existentes ao mesmo tempo e, nesse caso, nenhum outro soquete poderá ser vinculado à porta 21
, independentemente do endereço IP específico ao qual ele tenta se vincular, como 0.0.0.0
entra em conflito 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 sistema operacional quando a reutilização de endereços entra em ação. Começamos com o BSD, já que como eu disse acima, é a mãe de todas as implementações de socket.
BSD
SO_REUSEADDR
Se SO_REUSEADDR
estiver habilitado em um soquete antes de SO_REUSEADDR
-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 de origem e porta. Agora você pode se perguntar como isso é diferente de antes? A palavra-chave é "exatamente". SO_REUSEADDR
altera principalmente a forma como os endereços curinga ("qualquer endereço IP") são tratados ao procurar conflitos.
Sem SO_REUSEADDR
, ligar socketA
a 0.0.0.0:21
e, em seguida, ligar socketB
a 192.168.0.1:21
irá 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 este soquete e isso inclui 192.168.0.1
, também. Com SO_REUSEADDR
será bem-sucedido, pois 0.0.0.0
e 192.168.0.1
não são 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 instrução acima é verdadeira, independentemente de em qual ordem socketA
e socketB
estão ligados; sem SO_REUSEADDR
sempre falhará, com SO_REUSEADDR
sempre será bem-sucedido.
Para lhe dar uma visão geral melhor, vamos fazer uma tabela aqui e listar todas as combinações possíveis:
SO_REUSEADDR socketA socketB Result --------------------------------------------------------------------- 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) 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)
A tabela acima assume que o socketA
já foi ligado com sucesso ao endereço dado para o socketA
, então o socketB
é criado, ou obtém o SO_REUSEADDR
definido ou não, e finalmente é ligado ao endereço dado para o socketB
. Result
é o resultado da operação de ligação para o socketB
. Se a primeira coluna diz ON/OFF
, o valor de SO_REUSEADDR
é irrelevante para o resultado.
Ok, SO_REUSEADDR
tem um efeito sobre endereços curinga, é bom saber. No entanto, não é apenas o efeito que tem. Há outro efeito bem conhecido que também é a razão pela qual a maioria das pessoas usa SO_REUSEADDR
em programas de servidor. Para o outro uso importante desta opção, temos que dar uma olhada mais profunda em como o protocolo TCP funciona.
Um soquete tem um buffer de envio e se uma chamada para a função send()
for bem-sucedida, isso não significa que os dados solicitados realmente foram realmente enviados, isso significa apenas que os dados foram adicionados ao buffer de envio. Para soquetes UDP, os dados geralmente são enviados rapidamente, 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 como enviado, desde que a chamada send()
bem-sucedida. Se a implementação TCP estivesse fechando o soquete imediatamente a seu pedido, todos esses dados seriam perdidos e seu código nem saberia disso. O TCP é considerado um protocolo confiável e a perda de dados não é muito confiável. É por isso que um soquete que ainda tem dados para enviar entrará em um estado chamado TIME_WAIT
quando você fechá-lo. Nesse estado, ele aguardará até que todos os dados pendentes sejam enviados com sucesso ou até que um tempo limite seja atingido. Nesse caso, o soquete é fechado com força.
A quantidade de tempo que o kernel irá esperar antes de fechar o socket, independente de ainda ter dados de envio pendentes ou não, é chamado de Linger Time . O Linger Time é 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 SO_LINGER
do soquete, que pode ser usada para diminuir ou diminuir o tempo limite, e até para desativá-lo completamente. Desabilitar isso 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 (assim como reenviar esses pacotes caso eles sejam perdidos) e todo esse processo de fechamento. também é limitado pelo Tempo de descanso . Se você desabilitar o lingering, seu soquete poderá não apenas perder os dados pendentes, mas também estará sempre fechado com força em vez de graciosamente, 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 sobre, eu recomendo que você dê uma olhada nesta página . E mesmo se você desativou a SO_LINGER
com SO_LINGER
, se seu processo morrer sem fechar explicitamente o soquete, o BSD (e possivelmente outros sistemas) permanecerá, ignorando o que você configurou. Isso acontecerá, por exemplo, se seu código apenas chamar exit()
(muito comum para programas de servidor simples e pequenos) ou se o processo for eliminado por um sinal (que inclui a possibilidade de que ele simplesmente trave 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 socket no estado TIME_WAIT
? Se SO_REUSEADDR
não estiver configurado, um soquete no estado TIME_WAIT
será considerado como estando vinculado ao endereço e à porta de origem, e qualquer tentativa de ligar um novo soquete ao mesmo endereço e porta falhará até que o soquete tenha sido realmente fechado, o que pode levar contanto que o tempo de permanência configurado. Portanto, não espere que você possa religar o endereço de origem de um soquete imediatamente depois de fechá-lo. Na maioria dos casos, isso falhará. No entanto, se SO_REUSEADDR
estiver configurado para o soquete que você está tentando ligar, outro soquete vinculado ao mesmo endereço e porta no estado TIME_WAIT
é simplesmente ignorado, depois de todos os seus "half dead", e seu soquete pode ser ligado exatamente ao mesmo endereço sem qualquer 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 a exatamente o mesmo endereço e porta de um soquete em estado TIME_WAIT
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 última coisa que você deve saber sobre SO_REUSEADDR
. Tudo 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á está ligado ou esteja em um estado TIME_WAIT
, também tenha esse sinalizador definido quando foi ligado. O código que decide se a ligação terá êxito ou falha apenas inspeciona o sinalizador SO_REUSEADDR
do soquete alimentado na chamada bind()
, para todos os outros soquetes inspecionados, esse sinalizador nem sequer é observado.
SO_REUSEPORT
SO_REUSEPORT
é o que a maioria das pessoas esperaria que SO_REUSEADDR
fosse. Basicamente, SO_REUSEPORT
permite ligar um número arbitrário de sockets exatamente ao mesmo endereço de origem e porta, desde que todos os sockets anteriores tenham SO_REUSEPORT
definidos antes de serem ligados. Se o primeiro soquete que está vinculado a um endereço e porta não tem SO_REUSEPORT
definido, nenhum outro soquete pode ser vinculado a exatamente o mesmo endereço e porta, independentemente se esse outro soquete tiver SO_REUSEPORT
definido ou não, até que o primeiro soquete libera sua ligação novamente. Diferentemente do caso de SO_REUESADDR
o código que manipula SO_REUSEPORT
não apenas verificará se o soquete atualmente ligado possui SO_REUSEPORT
configurado, mas também verificará se o soquete com endereço e porta SO_REUSEPORT
tinha SO_REUSEPORT
configurado quando foi ligado.
SO_REUSEPORT
não implica SO_REUSEADDR
. Isso significa que se um soquete não tiver SO_REUSEPORT
configurado quando foi ligado e outro soquete tiver SO_REUSEPORT
configurado quando estiver ligado 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 estado TIME_WAIT
. Para poder ligar um soquete aos mesmos endereços e porta de outro soquete no estado TIME_WAIT
necessário que SO_REUSEADDR
seja configurado nesse soquete ou SO_REUSEPORT
deve ter sido configurado nos dois soquetes antes de SO_REUSEPORT
los. É claro que é permitido definir ambos, SO_REUSEPORT
e SO_REUSEADDR
, em um soquete.
Não há muito mais a dizer sobre SO_REUSEPORT
além do que foi adicionado depois de SO_REUSEADDR
, é por isso que você não o encontrará em muitas implementações de sockets de outros sistemas, que "bifurcaram" o código BSD antes desta opção ser adicionada, e que Não havia como ligar 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, você pode se deparar com a estranha situação em que connect()
falha com esse erro também. Como isso pode ser? Como pode um endereço remoto, afinal, é isso que a conexão adiciona a um soquete, já estar em uso? Conectar vários soquetes exatamente ao mesmo endereço remoto nunca foi um problema antes, então o que está 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 pode distinguir duas conexões por mais tempo, certo? Bem, com a reutilização de endereços, você pode vincular dois soquetes do mesmo protocolo ao mesmo endereço de origem e porta. Isso significa que três desses cinco valores já são os mesmos para esses dois soquetes. Se você tentar conectar esses dois soquetes ao mesmo endereço de destino e porta, você criaria dois soquetes conectados, cujas tuplas são absolutamente idênticas. Isso não funciona, 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 poderia informar a qual conexão os dados pertencem. Pelo menos o endereço de destino ou a porta de destino deve ser diferente para qualquer conexão, para que o sistema não tenha nenhum problema para identificar a qual conexão os dados de entrada pertencem.
Portanto, se você vincular dois soquetes do mesmo protocolo ao mesmo endereço de origem e porta e tentar conectá-los ao mesmo endereço de destino e porta, o connect()
falhará com o erro EADDRINUSE
para o segundo soquete que você tentar conectar. o que significa que um soquete com uma tupla idêntica de cinco valores já está conectado.
Endereços Multicast
A maioria das pessoas ignora o fato de que existem endereços multicast, mas eles existem. Embora os endereços unicast sejam usados para comunicação um-para-um, os endereços multicast são usados para comunicação um-para-muitos. A maioria das pessoas tomou conhecimento dos endereços multicast quando aprendeu sobre o IPv6, mas os endereços multicast também existiam no IPv4, embora esse recurso nunca tenha sido amplamente usado na Internet pública.
O significado de SO_REUSEADDR
alterado para 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, para endereços multicast, SO_REUSEADDR
se comporta exatamente como SO_REUSEPORT
para endereços unicast. Na verdade, o código trata SO_REUSEADDR
e SO_REUSEPORT
idêntica para endereços multicast, o que significa que você poderia dizer que SO_REUSEADDR
implica SO_REUSEPORT
para todos os endereços multicast e SO_REUSEADDR
SO_REUSEPORT
.
FreeBSD / OpenBSD / NetBSD
Todos estes são bastante tardios do código BSD original, é por isso que todos os três oferecem as mesmas opções que o BSD e também se comportam da mesma maneira que no BSD.
macOS (MacOS X)
Em sua essência, o macOS é simplesmente um UNIX estilo BSD chamado " Darwin ", baseado em uma bifurcação bastante tardia do código BSD (BSD 4.3), que mais tarde foi ressincronizado com o (nesse momento atual) FreeBSD 5 base de código para o lançamento do Mac OS 10.3, para que a Apple pudesse obter total conformidade com POSIX (o MacOS é certificado para POSIX). Apesar de ter um microkernel no 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 do BSD e também se comportam da mesma maneira que no BSD .
iOS / watchOS / tvOS
O iOS é apenas um fork do macOS com um kernel ligeiramente modificado e aparado, um conjunto de ferramentas de espaço do usuário um pouco reduzido e um conjunto de framework padrão ligeiramente diferente. watchOS e tvOS são garfos iOS, que são ainda mais despojados (especialmente watchOS). Para o meu melhor conhecimento, todos se comportam exatamente como o macOS faz.
Linux
Linux <3.9
Antes do Linux 3.9, apenas a opção SO_REUSEADDR
existia. Esta opção geralmente se comporta da mesma maneira que no BSD com duas importantes exceções:
Contanto que um soquete TCP de escuta (servidor) esteja vinculado a uma porta específica, a opção
SO_REUSEADDR
é totalmente ignorada para todos os soquetes que visam essa porta. Ligar um segundo socket à mesma porta só é possível se também fosse possível no BSD sem ter o conjuntoSO_REUSEADDR
. Por exemplo, você não pode ligar a um endereço curinga e, em seguida, a um mais específico ouSO_REUSEADDR
, ambos são possíveis no BSD se você definirSO_REUSEADDR
. O que você pode fazer é ligar-se à mesma porta e a dois endereços não-curinga diferentes, como sempre é permitido. Neste aspecto, o Linux é mais restritivo que o BSD.A segunda exceção é que, para soquetes do cliente, essa opção se comporta exatamente como
SO_REUSEPORT
no BSD, contanto que ambos tenham esse sinalizador definido antes de serem ligados. A razão para permitir isso foi simplesmente que é importante poder ligar vários soquetes exatamente ao mesmo endereço de soquete UDP para vários protocolos e como nãoSO_REUSEPORT
nenhumSO_REUSEPORT
anterior a 3.9, o comportamento deSO_REUSEADDR
foi alterado de acordo com o preenchimento essa lacuna. Nesse aspecto, o Linux é menos restritivo que o BSD.
Linux> = 3,9
O Linux 3.9 também adicionou a opção SO_REUSEPORT
ao Linux. Esta opção se comporta exatamente como a opção no BSD e permite ligar exatamente o mesmo endereço e número de porta, desde que todos os soquetes tenham esta opção definida antes de ligá-los.
No entanto, ainda existem duas diferenças para SO_REUSEPORT
em outros sistemas:
Para evitar o "seqüestro de porta", há uma limitação especial: Todos os soquetes que desejam compartilhar o mesmo endereço e a mesma combinação de portas devem pertencer a processos que compartilham o mesmo ID de usuário efetivo! Portanto, um usuário não pode "roubar" portas de outro usuário. Isso é alguma mágica especial para compensar um pouco os
SO_EXCLBIND
/SO_EXCLUSIVEADDRUSE
.Além disso, o kernel executa alguns "special magic" para soquetes
SO_REUSEPORT
que não são encontrados em outros sistemas operacionais: Para soquetes UDP, ele tenta distribuir datagramas uniformemente, para soquetes de escuta TCP, ele tenta distribuir solicitações de conexão de entrada (aqueles aceitos chamandoaccept()
) uniformemente em todos os sockets que compartilham o mesmo endereço e combinação de portas. Assim, um aplicativo pode facilmente abrir a mesma porta em vários processos filhos e, em seguida, usarSO_REUSEPORT
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 modificado, portanto, tudo o que se aplica ao Linux também deve ser aplicado ao Android.
janelas
O Windows só conhece a opção SO_REUSEADDR
, não há SO_REUSEPORT
. Definir SO_REUSEADDR
em um soquete no Windows se comporta como definir SO_REUSEPORT
e SO_REUSEADDR
em um soquete no BSD, com uma exceção: Um soquete com SO_REUSEADDR
sempre pode ser ligado exatamente ao mesmo endereço de origem e porta como um soquete já ligado, mesmo se o outro soquete não tem essa opção definida quando foi ligada . Esse comportamento é um pouco perigoso porque 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 pode ser um problema e, portanto, adicionou outra opção de soquete SO_EXCLUSIVEADDRUSE
. Definir SO_EXCLUSIVEADDRUSE
em um soquete garante que, se a ligação for bem-sucedida, a combinação de endereço de origem e porta seja de propriedade exclusiva desse soquete e nenhum outro soquete possa ser vinculado a eles, nem mesmo se tiver SO_REUSEADDR
configurado.
Para obter ainda mais detalhes sobre como os sinalizadores SO_REUSEADDR
e SO_EXCLUSIVEADDRUSE
funcionam no Windows, como eles influenciam a vinculação / recolocação, a Microsoft gentilmente forneceu uma tabela semelhante à minha tabela, próxima ao início dessa resposta. Basta visitar esta página e descer um pouco. Na verdade, existem três tabelas, a primeira mostra o comportamento antigo (anterior ao Windows 2003), a segunda o comportamento (Windows 2003 e superior) e a terceira mostra como o comportamento é alterado no Windows 2003 e posterior se as chamadas bind()
são feitos por diferentes usuários.
Solaris
O Solaris é o sucessor do SunOS. SunOS foi originalmente baseado em uma bifurcação de BSD, SunOS 5 e mais tarde foi baseado em uma bifurcação de SVR4, no entanto SVR4 é uma mesclagem de BSD, System V e Xenix, então até certo ponto o Solaris também é um bifurcado BSD. bastante cedo. Como resultado, o Solaris apenas conhece SO_REUSEADDR
, não há SO_REUSEPORT
. O SO_REUSEADDR
se comporta praticamente da mesma forma que no BSD. Até onde sei, não há como obter o mesmo comportamento que SO_REUSEPORT
no Solaris, isso significa que não é possível ligar dois soquetes exatamente ao mesmo endereço e porta.
Semelhante ao Windows, o Solaris tem a opção de fornecer ao soquete uma ligação exclusiva. Esta opção é denominada SO_EXCLBIND
. Se esta opção estiver configurada em um soquete antes de SO_REUSEADDR
lo, a configuração de SO_REUSEADDR
em outro soquete não SO_REUSEADDR
efeito se os dois soquetes forem testados para um conflito de endereços. Por exemplo, se socketA
estiver ligado a um endereço curinga e socketB
tiver SO_REUSEADDR
ativado e estiver ligado a um endereço não curinga e à mesma porta que o socketA
, essa ligação normalmente será bem-sucedida, a menos que o socketA
tenha SO_EXCLBIND
ativado, caso em que falhará independentemente do SO_REUSEADDR
sinalizador de socketB
.
Outros sistemas
Caso seu sistema não esteja listado acima, eu escrevi um pequeno programa de teste que você pode usar para descobrir como seu sistema lida com essas duas opções. Além disso, se você acha que meus resultados estão errados , por favor, primeiro execute esse programa antes de postar quaisquer comentários e, possivelmente, fazer declarações falsas.
Tudo o que o código requer para compilar é um pouco de API POSIX (para as partes de 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
; muito antes de oferecer suporte C99 completo).
Tudo o que o programa precisa executar é que pelo menos uma interface em seu sistema (diferente da interface local) tem um endereço IP atribuído e que uma rota padrão é definida e usa 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, escuta soquetes (servidor), soquetes multicast
-
SO_REUSEADDR
definido no soquete1, soquete2 ou em ambos os soquetes -
SO_REUSEPORT
definido no soquete1, soquete2 ou em ambos os soquetes - Todas as combinações de endereço que você pode fazer de
0.0.0.0
(caractere curinga),127.0.0.1
(endereço específico) e o segundo endereço específico encontrado em sua interface primária (para multicast é apenas224.1.2.3
em todos os testes)
e imprime os resultados em uma boa mesa. Ele também funcionará em sistemas que não conhecem SO_REUSEPORT
, nesse caso, essa opção simplesmente não será testada.
O que o programa não pode testar com facilidade é como o SO_REUSEADDR
age em sockets no estado TIME_WAIT
, pois é muito complicado 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 empurraria essa resposta para o limite).
As man pages
e as documentações do programador para as opções de soquete SO_REUSEADDR
e SO_REUSEPORT
são diferentes para sistemas operacionais diferentes e geralmente são altamente confusas. Alguns sistemas operacionais nem possuem a opção SO_REUSEPORT
. A WEB está repleta de informações contraditórias sobre esse assunto e, muitas vezes, você pode encontrar informações que só são verdadeiras para uma implementação de um soquete de um sistema operacional específico, que pode nem ser explicitamente mencionada no texto.
Então, como exatamente SO_REUSEADDR
é diferente de SO_REUSEPORT
?
Os sistemas sem SO_REUSEPORT
mais limitados?
E qual é exatamente o comportamento esperado se eu usar um em sistemas operacionais diferentes?