Opção TCP SO_LINGER (zero) – quando é necessário

Eu acho que entendo o significado formal da opção. Em algum código legado que estou manipulando agora, a opção é usada. O cliente se queixa de RST como resposta a FIN do seu lado na conexão perto de seu lado.

Não tenho certeza se posso removê-lo com segurança, pois não entendo quando deve ser usado.

Você pode, por favor, dar um exemplo de quando a opção seria necessária?

O motivo típico para definir um tempo limite de SO_LINGER zero é evitar um grande número de conexões sentadas no estado TIME_WAIT , amarrando todos os resources disponíveis em um servidor.

Quando uma conexão TCP é fechada corretamente, o final que iniciou o fechamento (“fechamento ativo”) termina com a conexão em TIME_WAIT por vários minutos. Portanto, se o seu protocolo é aquele em que o servidor inicia o fechamento da conexão e envolve um número muito grande de conexões de vida curta, ele pode estar suscetível a esse problema.

Entretanto, isso não é uma boa ideia – há TIME_WAIT por um motivo (para garantir que os pacotes dispersos de conexões antigas não interfiram em novas conexões). É uma idéia melhor redesenhar seu protocolo para um onde o cliente inicia a conexão, se possível.

Para minha sugestão, por favor leia a última seção: “Quando usar SO_LINGER com tempo limite 0” .

Antes de chegarmos a uma pequena palestra sobre:

  • Terminação TCP normal
  • TIME_WAIT
  • FIN , ACK e RST

Terminação TCP normal

A seqüência normal de terminação do TCP se parece com isso (simplificada):

Nós temos dois pares: A e B

  1. Uma chamada close()
    • A envia FIN para B
    • A entra no estado FIN_WAIT_1
  2. B recebe FIN
    • B envia ACK para A
    • B entra no estado CLOSE_WAIT
  3. A recebe ACK
    • A entra no estado FIN_WAIT_2
  4. B chama close()
    • B envia FIN para A
    • B entra no estado LAST_ACK
  5. A recebe FIN
    • A envia ACK para B
    • A entra no estado TIME_WAIT
  6. B recebe ACK
    • B vai para o estado CLOSED – ie é removido das tabelas de socket

TEMPO DE ESPERA

Assim, o peer que inicia a terminação – isto é, chama close() primeiro – terminará no estado TIME_WAIT .

Para entender por que o estado TIME_WAIT é nosso amigo, leia a seção 2.7 na terceira edição da “Programação de Rede UNIX” de Stevens et al. (Página 43).

No entanto, pode haver um problema com muitos sockets no estado TIME_WAIT em um servidor, pois isso pode impedir que novas conexões sejam aceitas.

Para contornar esse problema, tenho visto muitos sugerindo definir a opção de soquete SO_LINGER com tempo limite 0 antes de chamar close() . No entanto, esta é uma solução ruim, pois faz com que a conexão TCP seja encerrada com um erro.

Em vez disso, projete seu protocolo de aplicativo para que a finalização da conexão seja sempre iniciada no lado do cliente. Se o cliente sempre souber quando leu todos os dados restantes, ele poderá iniciar a sequência de término. Como exemplo, um navegador sabe do header HTTP Content-Length quando leu todos os dados e pode iniciar o fechamento. (Eu sei que no HTTP 1.1 ele ficará aberto por um tempo para uma possível reutilização, e então irá fechá-lo.)

Se o servidor precisar fechar a conexão, projete o protocolo do aplicativo para que o servidor peça ao cliente para chamar close() .

Quando usar SO_LINGER com tempo limite 0

Novamente, de acordo com a “Programação de Rede UNIX” da terceira edição da página 202-203, a configuração de SO_LINGER com tempo limite 0 antes de chamar close() fará com que a sequência de finalização normal não seja iniciada.

Em vez disso, o peer configurando essa opção e chamando close() irá enviar um RST (reset de conexão) que indica uma condição de erro e é assim que será percebido no outro extremo. Você normalmente verá erros como “Conexão redefinida pelo par”.

Portanto, na situação normal, é uma péssima idéia definir SO_LINGER com timeout 0 antes de chamar close() – a partir de agora chamado abortive close – em um aplicativo de servidor.

No entanto, certas situações justificam isso de qualquer maneira:

  • Se o cliente de seu aplicativo servidor se comportar mal ( CLOSE_WAIT , retorna dados inválidos, etc.), um fechamento abortivo faz sentido para evitar ficar preso em CLOSE_WAIT ou terminar no estado TIME_WAIT .
  • Se você precisar reiniciar o aplicativo do servidor que atualmente possui milhares de conexões de cliente, considere configurar essa opção de soquete para evitar milhares de sockets do servidor em TIME_WAIT (ao chamar close() da extremidade do servidor), pois isso pode impedir que o servidor obtenha portas disponíveis para novas conexões de cliente depois de ser reiniciado.
  • Na página 202 do livro acima mencionado especificamente diz: “Há certas circunstâncias que garantem o uso desse recurso para enviar um fechamento abortivo. Um exemplo é um servidor de terminal RS-232, que pode ficar pendurado para sempre em CLOSE_WAIT tentando entregar dados a um ponto bloqueado. porta do terminal, mas seria corretamente redefinir a porta presa se ele tem um RST para descartar os dados pendentes “.

Eu recomendaria este longo artigo que, acredito, dá uma resposta muito boa à sua pergunta.

Quando linger está ligado, mas o tempo limite é zero, a pilha TCP não espera que os dados pendentes sejam enviados antes de fechar a conexão. Os dados podem ser perdidos devido a isso, mas definindo linger dessa forma, você está aceitando isso e solicitando que a conexão seja redefinida imediatamente, em vez de fechada de forma elegante. Isso faz com que um RST seja enviado em vez do FIN normal.

Obrigado ao EJP pelo seu comentário, veja aqui para detalhes.

Se você pode remover o linger em seu código com segurança ou não depende do tipo de sua aplicação: é um “cliente” (abrindo conexões TCP e fechando ativamente primeiro) ou é um “servidor” (escutando um TCP aberto e fechando depois do outro lado iniciou o fechamento)?

Se o seu aplicativo tem o sabor de um “cliente” (fechando primeiro) E você inicia e fecha um grande número de conexões com diferentes servidores (por exemplo, quando seu aplicativo é um aplicativo de monitoramento supervisionando a acessibilidade de um grande número de servidores diferentes) tem o problema de que todas as suas conexões de cliente estão presas no estado TIME_WAIT. Em seguida, recomendo encurtar o tempo limite para um valor menor que o padrão para ainda encerrar normalmente, mas liberar os resources de conexões do cliente anteriormente. Eu não iria definir o tempo limite para 0, como 0 não encerra graciosamente com FIN mas abortivo com RST.

Se o seu aplicativo tiver o sabor de um “cliente” e tiver que buscar uma quantidade enorme de arquivos pequenos do mesmo servidor, você não deve iniciar uma nova conexão TCP por arquivo e acabar em uma enorme quantidade de conexões de cliente em TIME_WAIT, mas mantenha a conexão aberta e obtenha todos os dados na mesma conexão. A opção Linger pode e deve ser removida.

Se a sua aplicação é um ‘servidor’ (próximo segundo como reação ao fechamento do par), no fechamento () sua conexão é encerrada normalmente e os resources são liberados quando você não entra no estado TIME_WAIT. Linger não deve ser usado. Mas se o seu aplicativo sever tiver um processo supervisório que detecta conexões inativas abertas inativas por um longo tempo (“longo” deve ser definido), você pode desligar essa conexão inativa do seu lado – ver isso como um tipo de tratamento de erros – com um encerramento abortado. Isso é feito definindo o tempo limite de espera para 0. close () irá enviar um RST para o cliente, informando que você está com raiva 🙂