2010-01-22

Sockets cliente/servidor

O que é um socket?

De uma forma muito simples e rápida (visto isto ser matéria que diz respeito a Comunicações por Computador), um socket é uma das pontas de um canal de comunicação entre duas máquinas.

Exercício 1: Servidor de eco

Para implementar o servidor de eco vamos usar a classe java.net.ServerSocket, que serve para receber conexões externas com o método accept. Este método devolve um objecto do tipo java.net.Socket que comunica com o cliente cuja conexão foi aceite com accept.

public class EchoServer {

public void start() throws IOException {
ServerSocket server = new ServerSocket(5000); // Iniciar o servidor
// à escuta na porta 5000
Socket client = server.accept(); // Aceitar uma ligação
while (true) {
serve(client); // Servir este cliente
client = server.accept(); // Aceitar uma ligação nova
}
}

private void serve(Socket client) throws IOException {
// ...
}
}

Para efectuar qualquer tipo de comunicação temos à nossa disposição duas streams: uma para enviar dados (output) e outra para receber dados (input). Como queremos enviar e receber linhas de texto, não vamos utilizar estas streams directamente, porque o seu interface é bastante primitivo (só permite o uso directo de arrays de bytes). Vamos embrulhar estas streams em objectos que disponibilizam métodos que trabalham com strings: BufferedReader e PrintWriter:

    private BufferedReader getSocketReader(Socket client) throws IOException {
return new BufferedReader(new InputStreamReader(client.getInputStream()));
}

private PrintWriter getSocketWriter(Socket client) throws IOException {
return new PrintWriter(new OutputStreamWriter(client.getOutputStream()));
}

Com isto, para servir o cliente basta passar linhas da stream de input para a de output:

    private void pump(BufferedReader reader, PrintWriter writer) throws IOException {
String line = reader.readLine();
while (line != null) { // Repetir enquanto existirem linhas para ler
writer.println(line);
writer.flush(); // Necessário para forçar envio
line = reader.readLine();
}
}

A chamada a flush() é necessária porque PrintWriter usa um buffer interno para minimizar o número de operações de escrita. Se pedirmos para escrever uma linha pequena o buffer não enche e essa linha só será enviada quando o buffer acumular linhas suficientes para encher. Ao chamar flush(), estamos a forçar a escrita e a linha é enviada imediatamente.

Só falta agora escrever o método serve. Com todas as suas partes preparadas isto é bastante simples:

    private void serve(Socket client) throws IOException {
try {
BufferedReader reader = getSocketReader(client);
PrintWriter writer = getSocketWriter(client);
pump(reader, writer);
} finally {
client.close();
}
}

Reparem no uso de try-finally para garantir que a ligação é fechada tanto em caso de sucesso ou de falha.

Testar

Para testar isto podemos usar o seguinte método main:

    public static void main(String[] args) {
try {
new EchoServer().start();
} catch (IOException ex) {
logger.log(Level.SEVERE, "Ooops", ex);
}
}

E, conforme sugerido no enunciado, usamos o comando telnet como cliente:

$ telnet localhost 5000
Olá!
Olá!
Yupeee! Tá a funcionar!
Yupeee! Tá a funcionar!

1 commentários:

JoseCid disse...

Obrigado pela massagem inicial!

A ver se consigo chegar ao chat até ao dia do teste.

Enviar um comentário