Seguindo uma pergunta recente , eu me pergunto por que é impossível em Java, sem tentar ler / gravar em um soquete TCP, detectar que o soquete foi normalmente fechado pelo par? Este parece ser o caso, independentemente de se usar o pré-NIO Socket
ou o NIO SocketChannel
.
Quando um par fecha normalmente uma conexão TCP, as pilhas TCP em ambos os lados da conexão sabem sobre o fato. O lado do servidor (aquele que inicia o desligamento) termina no estado FIN_WAIT2
, enquanto o lado do cliente (aquele que não responde explicitamente ao desligamento) termina no estado CLOSE_WAIT
. Por que não existe um método em Socket
ou SocketChannel
que possa consultar a pilha TCP para ver se a conexão TCP subjacente foi encerrada? É que a pilha TCP não fornece essas informações de status? Ou é uma decisão de design para evitar uma chamada dispendiosa para o kernel?
Com a ajuda dos usuários que já postaram algumas respostas para essa pergunta, acho que vejo de onde o problema pode estar vindo. O lado que não fecha explicitamente a conexão termina no estado TCP, CLOSE_WAIT
o que significa que a conexão está em processo de encerramento e espera que o lado execute sua própria CLOSE
operação. Suponho que seja justo que isConnected
volte true
e isClosed
volte false
, mas por que não existe algo assim isClosing
?
Abaixo estão as classes de teste que usam soquetes pré-NIO. Mas resultados idênticos são obtidos usando NIO.
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer {
public static void main(String[] args) throws Exception {
final ServerSocket ss = new ServerSocket(12345);
final Socket cs = ss.accept();
System.out.println("Accepted connection");
Thread.sleep(5000);
cs.close();
System.out.println("Closed connection");
ss.close();
Thread.sleep(100000);
}
}
import java.net.Socket;
public class MyClient {
public static void main(String[] args) throws Exception {
final Socket s = new Socket("localhost", 12345);
for (int i = 0; i < 10; i++) {
System.out.println("connected: " + s.isConnected() +
", closed: " + s.isClosed());
Thread.sleep(1000);
}
Thread.sleep(100000);
}
}
Quando o cliente de teste se conecta ao servidor de teste, a saída permanece inalterada, mesmo após o servidor iniciar o desligamento da conexão:
connected: true, closed: false
connected: true, closed: false
...
fonte
Socket.close
não é um encerramento elegante.Respostas:
Tenho usado Sockets com frequência, principalmente com Seletores, e embora não seja um especialista em OSI de Rede, no meu entendimento, chamar
shutdownOutput()
um Socket na verdade envia algo na rede (FIN) que ativa meu Seletor do outro lado (mesmo comportamento em C língua). Aqui você tem detecção : realmente detectando uma operação de leitura que falhará quando você tentar.No código que você fornecer, fechar o socket irá desligar os fluxos de entrada e saída, sem possibilidades de leitura dos dados que possam estar disponíveis, perdendo-os, portanto. O
Socket.close()
método Java executa uma desconexão "elegante" (oposta ao que eu pensava inicialmente) em que os dados deixados no fluxo de saída serão enviados seguidos por um FIN para sinalizar seu fechamento. O FIN será reconhecido pelo outro lado, como qualquer pacote regular faria 1 .Se você precisar esperar que o outro lado feche seu soquete, você precisa esperar por seu FIN. E para conseguir isso, você tem que detectar
Socket.getInputStream().read() < 0
, o que significa que você não deve fechar o seu soquete, pois fecharia o seuInputStream
.Pelo que fiz em C e agora em Java, conseguir esse fechamento sincronizado deve ser feito assim:
read()
detectar o controle remotoclose()
InputStream
até recebermos a resposta FIN da outra extremidade (como ele detectará o FIN, ele passará pelo mesmo processo de diconnection). Isso é importante em alguns sistemas operacionais, pois eles não fecham o soquete, desde que um de seus buffer ainda contenha dados. Eles são chamados de soquete "fantasma" e usam números de descritor no sistema operacional (isso pode não ser mais um problema com o sistema operacional moderno)Socket.close()
ou fechando seuInputStream
ouOutputStream
)Conforme mostrado no seguinte snippet Java:
É claro que ambos os lados devem usar a mesma forma de fechamento, ou a parte de envio pode estar sempre enviando dados suficientes para manter o
while
loop ocupado (por exemplo, se a parte de envio está apenas enviando dados e nunca lendo para detectar o encerramento da conexão. O que é desajeitado, mas você pode não ter controle sobre isso).Como @WarrenDew apontou em seu comentário, o descarte dos dados no programa (camada de aplicativo) induz uma desconexão não normal na camada de aplicativo: embora todos os dados tenham sido recebidos na camada TCP (o
while
loop), eles são descartados.1 : Em " Fundamental Networking in Java ": veja a fig. 3.3 p.45, e todo §3.7, pp 43-48
fonte
socket.close()
é "brutal" por não respeitar esta sinalização Cliente / Servidor. O servidor seria notificado de uma desconexão do cliente somente quando seu próprio buffer de saída de soquete estivesse cheio (porque nenhum dado está sendo reconhecido pelo outro lado, que foi fechado).read()
retorna -1 no final do fluxo e continua a fazê-lo quantas vezes você o chamar. ACread()
orrecv()
retorna zero no final do stream e continua a fazê-lo quantas vezes você chamá-lo.Eu acho que isso é mais uma questão de programação de socket. Java está apenas seguindo a tradição de programação de socket.
Da Wikipedia :
Depois que o handshake é feito, o TCP não faz nenhuma distinção entre dois pontos de extremidade (cliente e servidor). O termo "cliente" e "servidor" é principalmente para conveniência. Assim, o "servidor" pode estar enviando dados e o "cliente" pode estar enviando outros dados simultaneamente um para o outro.
O termo "Fechar" também é enganoso. Existe apenas a declaração FIN, que significa "Não vou enviar mais nada a você". Mas isso não significa que não haja pacotes em vôo ou que o outro não tenha mais nada a dizer. Se você implementar o correio tradicional como a camada de enlace de dados, ou se o seu pacote viajou por rotas diferentes, é possível que o destinatário receba os pacotes na ordem errada. O TCP sabe como consertar isso para você.
Além disso, você, como programa, pode não ter tempo para verificar o que está no buffer. Assim, conforme sua conveniência, você pode verificar o que está no buffer. Em suma, a implementação de soquete atual não é tão ruim. Se realmente houvesse isPeerClosed (), essa chamada extra que você terá que fazer toda vez que quiser chamar read.
fonte
A API de sockets subjacente não tem essa notificação.
De qualquer forma, a pilha TCP de envio não enviará o bit FIN até o último pacote, portanto, pode haver muitos dados armazenados em buffer a partir do momento em que o aplicativo de envio fechou logicamente seu soquete antes mesmo dos dados serem enviados. Da mesma forma, os dados armazenados em buffer porque a rede é mais rápida do que o aplicativo de recebimento (não sei, talvez você esteja retransmitindo-os por uma conexão mais lenta) podem ser significativos para o receptor e você não gostaria que o aplicativo de recebimento os descarte apenas porque o bit FIN foi recebido pela pilha.
fonte
Como nenhuma das respostas até agora respondeu totalmente à pergunta, estou resumindo meu entendimento atual do problema.
Quando uma conexão TCP é estabelecida e um par faz uma chamada
close()
oushutdownOutput()
em seu soquete, o soquete do outro lado da conexão muda para oCLOSE_WAIT
estado. Em princípio, é possível descobrir na pilha TCP se um soquete está noCLOSE_WAIT
estado sem chamarread/recv
(por exemplo,getsockopt()
no Linux: http://www.developerweb.net/forum/showthread.php?t=4395 ), mas isso não portátil.A
Socket
classe Java parece ter sido projetada para fornecer uma abstração comparável a um soquete BSD TCP, provavelmente porque este é o nível de abstração com o qual as pessoas estão acostumadas ao programar aplicativos TCP / IP. Os sockets BSD são uma generalização que suporta sockets diferentes de INET (por exemplo, TCP), então eles não fornecem uma maneira portátil de descobrir o estado TCP de um socket.Não há método
isCloseWait()
igual, porque as pessoas acostumadas a programar aplicativos TCP no nível de abstração oferecido pelos soquetes BSD não esperam que o Java forneça nenhum método extra.fonte
isCloseWait()
porque não é compatível com todas as plataformas.Detectar se o lado remoto de uma conexão de soquete (TCP) foi fechado pode ser feito com o método java.net.Socket.sendUrgentData (int) e capturar a IOException lançada se o lado remoto estiver inativo. Isso foi testado entre Java-Java e Java-C.
Isso evita o problema de projetar o protocolo de comunicação para usar algum tipo de mecanismo de ping. Ao desativar OOBInline em um soquete (setOOBInline (false), quaisquer dados OOB recebidos são silenciosamente descartados, mas os dados OOB ainda podem ser enviados. Se o lado remoto for fechado, uma redefinição de conexão é tentada, falha e faz com que alguma IOException seja lançada .
Se você realmente usa dados OOB em seu protocolo, sua milhagem pode variar.
fonte
a pilha Java IO definitivamente envia FIN quando é destruída em uma desmontagem abrupta. Simplesmente não faz sentido que você não possa detectar isso, porque a maioria dos clientes só envia o FIN se estiverem encerrando a conexão.
... outra razão pela qual estou realmente começando a odiar as classes NIO Java. Parece que tudo é meio burro.
fonte
É um assunto interessante. Eu vasculhei o código java agora para verificar. Pelo que descobri, há dois problemas distintos: o primeiro é o próprio TCP RFC, que permite que o soquete fechado remotamente transmita dados em half-duplex, portanto, um soquete fechado remotamente ainda fica semiaberto. De acordo com o RFC, o RST não fecha a conexão, você precisa enviar um comando ABORT explícito; então Java permite o envio de dados por meio de soquete semi-fechado
(Existem dois métodos para ler o status de fechamento em ambos os terminais.)
O outro problema é que a implementação diz que esse comportamento é opcional. Como o Java se esforça para ser portátil, eles implementaram o melhor recurso comum. Manter um mapa de (SO, implementação de half duplex) teria sido um problema, eu acho.
fonte
Esta é uma falha das classes de soquete OO do Java (e de todos os outros que eu examinei) - nenhum acesso à chamada de sistema select.
Resposta correta em C:
fonte
getsocketop(... SOL_SOCKET, SO_ERROR, ...)
.Aqui está uma solução alternativa. Use SSL;) e o SSL faz um handshake de fechamento na desmontagem para que você seja notificado sobre o fechamento do soquete (a maioria das implementações parece fazer uma desmontagem de handshake de propriedade).
fonte
A razão para esse comportamento (que não é específico do Java) é o fato de que você não obtém nenhuma informação de status da pilha TCP. Afinal, um soquete é apenas outro identificador de arquivo e você não pode descobrir se há dados reais para ler dele sem realmente tentar (
select(2)
não vai ajudar nisso, ele apenas indica que você pode tentar sem bloquear).Para obter mais informações, consulte o FAQ sobre soquetes Unix .
fonte
select()
informa se há dados ou EOS disponíveis para serem lidos sem bloqueio. 'Sinais que você pode tentar sem bloquear' não tem sentido. Se você estiver no modo sem bloqueio, sempre poderá tentar sem bloquear.select()
é conduzido por dados no buffer de recebimento do socket ou um FIN pendente ou espaço no buffer de envio do socket.getsockopt(SO_ERROR)
? Na verdade, o evengetpeername
informará se o soquete ainda está conectado.Apenas as gravações exigem que os pacotes sejam trocados, o que permite que a perda de conexão seja determinada. Uma solução alternativa comum é usar a opção KEEP ALIVE.
fonte
Quando se trata de lidar com soquetes Java entreabertos, pode-se dar uma olhada em isInputShutdown () e isOutputShutdown () .
fonte