linux - Opções de soquete SO_REUSEADDR e SO_REUSEPORT, como elas diferem? Eles significam o mesmo em todos os principais sistemas operacionais?



windows sockets (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:

  1. 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 conjunto SO_REUSEADDR . Por exemplo, você não pode ligar a um endereço curinga e, em seguida, a um mais específico ou SO_REUSEADDR , ambos são possíveis no BSD se você definir SO_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.

  2. 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ão SO_REUSEPORT nenhum SO_REUSEPORT anterior a 3.9, o comportamento de SO_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:

  1. 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 .

  2. 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 chamando accept() ) 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, usar SO_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 é apenas 224.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).

https://ffff65535.com

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?