Soquetes Java e Conexões Derrubadas
Qual é a maneira mais apropriada para detectar se um soquete foi descartado ou não? Ou se um pacote foi realmente enviado?
Eu tenho uma biblioteca para enviar o Apple Push Notifications para iPhones através dos Apple Gatways (disponível no GitHub). Os clientes precisam abrir um soquete e enviar uma representação binária de cada mensagem; mas infelizmente a Apple não retorna nenhum reconhecimento. A conexão pode ser reutilizada para enviar várias mensagens também. Estou usando as conexões simples do Java Socket. O código relevante é:
Socket socket = socket(); // returns an reused open socket, or a new one
socket.getOutputStream().write(m.marshall());
socket.getOutputStream().flush();
logger.debug("Message \"{}\" sent", m);
Em alguns casos, se uma conexão for interrompida enquanto uma mensagem é enviada ou antes;Socket.getOutputStream().write()
termina com sucesso embora. Espero que seja devido a janela TCP não está esgotada ainda.
Existe uma maneira que eu possa dizer com certeza se um pacote realmente entrou na rede ou não? Eu experimentei com as duas soluções a seguir:
Insira um adicionalsocket.getInputStream().read()
operação com um tempo limite de 250 ms. Isso força uma operação de leitura que falha quando a conexão é interrompida, mas trava de outra forma por 250ms.
definir o tamanho do buffer de envio de TCP (por ex.Socket.setSendBufferSize()
) para o tamanho binário da mensagem.
Ambos os métodos funcionam, mas degradam significativamente a qualidade do serviço; O throughput vai de 100 mensagens / segundo a cerca de 10 mensagens / segundo no máximo.
Alguma sugestão?
ATUALIZAR:
Desafiado por múltiplas respostas questionando a possibilidade do descrito. Eu construí testes de "unidade" do comportamento que estou descrevendo. Confira os casos da unidade emEssência 273786.
Ambos os testes de unidade possuem dois encadeamentos, um servidor e um cliente. O servidor fecha enquanto o cliente está enviando dados sem uma IOException lançada de qualquer maneira. Aqui está o método principal:
public static void main(String[] args) throws Throwable {
final int PORT = 8005;
final int FIRST_BUF_SIZE = 5;
final Throwable[] errors = new Throwable[1];
final Semaphore serverClosing = new Semaphore(0);
final Semaphore messageFlushed = new Semaphore(0);
class ServerThread extends Thread {
public void run() {
try {
ServerSocket ssocket = new ServerSocket(PORT);
Socket socket = ssocket.accept();
InputStream s = socket.getInputStream();
s.read(new byte[FIRST_BUF_SIZE]);
messageFlushed.acquire();
socket.close();
ssocket.close();
System.out.println("Closed socket");
serverClosing.release();
} catch (Throwable e) {
errors[0] = e;
}
}
}
class ClientThread extends Thread {
public void run() {
try {
Socket socket = new Socket("localhost", PORT);
OutputStream st = socket.getOutputStream();
st.write(new byte[FIRST_BUF_SIZE]);
st.flush();
messageFlushed.release();
serverClosing.acquire(1);
System.out.println("writing new packets");
// sending more packets while server already
// closed connection
st.write(32);
st.flush();
st.close();
System.out.println("Sent");
} catch (Throwable e) {
errors[0] = e;
}
}
}
Thread thread1 = new ServerThread();
Thread thread2 = new ClientThread();
thread1.start();
thread2.start();
thread1.join();
thread2.join();
if (errors[0] != null)
throw errors[0];
System.out.println("Run without any errors");
}
[A propósito, eu também tenho uma biblioteca de testes de concorrência, que torna a configuração um pouco melhor e mais clara. Confira também a amostra na essência].
Quando executado, recebo a seguinte saída:
Closed socket
writing new packets
Finished writing
Run without any errors