Tutorial: escrevendo seu próprio servidor de transferência arquivos (parte2)

Bom, na segunda parte do tutorial vou mostrar como aceitar conexões de um cliente através de um socket e enviar as respostas.
Para criar um servidor precisamos, antes de mais nada, abrir um ServerSocket. Dessa maneira vamos abrir uma porta TCP para aceitar conexões de outros hosts. Durante os testes, é importante certificar-se de que o firewall vai deixar as conexões entrarem.
O código abaixo é a estrutura básica do servidor. Ele contém um ServerSocket dentro de um laço para aceitar as conexões. Ao aceitar uma conexão, o servidor delega a conexão para uma nova thread. Dessa maneira, o ServerSocket pode aceitar novas conexões sem precisar esperar o processamento da conexão anterior:

public class Servidor {
	private ServerSocket	server;
	private boolean started = false;

	public void start() throws Exception{
		started = true;
		
		int count = 1;
                server = new ServerSocket(3000);
		while(started){
			final Socket socket = server.accept();
			
			Thread thread = new Thread(new Runnable(){
				@Override
				public void run() {
					service(socket);
				}
			}, "service-" + count++);
		}
	}
	

	public void stop() throws IOException{
		started = false;
		server.close();
	}
}

Agora, resta escrever a lógica do tratamento das requisições do cliente. Pode até não parecer, mas esta parte é a mais “chata” de se escrever. Isso porque a transmissão de dados através de um stream tem algumas peculiaridades que quando não são observadas, podem custar algumas boas horas de debug e xingamento. Primeiro, vou começar escrevendo como NÃO ler dados de um stream, sabendo que é a entrada de um socket:

private void service(Socket socket) throws IOException{
		BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
		
		byte [] buf = new byte[256];
		int count = 0;
		while((count = bis.read(buf)) > -1){ //só retorna -1 quando o socket do outro lado é fechado
			//...
		}
	}

No código acima, o método lê bytes do stream de entrada do socket até que o método read do stream retorne -1. Pois bem, o valor -1 somente é retornado quando o stream encontra a marcação EOF nos dados, e isso somente acontece quando a conexão é fechada. Assim, se você estiver escrevendo uma lógica do tipo request-response, com essa abordagem é possível que você leia toda a request e mesmo assim ficar com a thread bloqueada, pois você ainda não recebeu aquele tão esperado -1.
Um outro erro seria usar o método available():

private void service(Socket socket) throws IOException{
		BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
		
		byte [] buf = new byte[256];
		int count = 0;
		while(bis.available() > 0){
			//...
		}

A pegadinha nesse caso é a seguinte: o método available() retorna o número de bytes disponíveis para no buffer. Isso significa que o método pode retornar 0 antes de terminar a leitura de toda a entrada, fazendo com que a thread continue com dados incompletos.
Mas então, qual a solução para este dilema ? É simples, basta trocar mensagens com tamanho fixo ou com algum tipo de delimitador.
O jeito mais fácil que eu encontrei é trocar objetos serializáveis, pois assim a própria JVM se encarrega de inserir delimitadores e fixar os tamanhos dos campos:

class Message implements Serializable {
	private static final long	serialVersionUID	= 1L;

	public int command;
	public String[] args;
	public String[] result;
	public boolean disconnect;
	public boolean fileToSend;
	public String fileName;
	public byte[] fileData;
}

A classe acima é um exemplo de como pode ser um objeto para troca de dados entre o cliente e o servidor. Praticamente, todos os campos que podem ser utilizados na comunicação são colocados em uma única estrutura. Vejamos agora um exemplo de como ficaria a nossa lógica, no servidor:

private void service(Socket socket) throws IOException, ClassNotFoundException {
		ObjectInputStream entrada = new ObjectInputStream(socket.getInputStream());
		ObjectOutputStream saida = new ObjectOutputStream(socket.getOutputStream());
		
		while(true){
			Message request = (Message) entrada.readObject();
			
			if(request.disconnect){
				socket.close();
				break;
			}
			
			Message response = new Message();
			Command cmd = new Command();
			
			response.result = cmd.execute(request.command, request.args);
			
			saida.writeObject(response);
		}
	}

Bom, espero que nesses 2 posts eu tenha conseguido colocar os principais pontos que devem ser levados em consideração na hora de escrever esse tipo de aplicação.

Anúncios

2 Responses to “Tutorial: escrevendo seu próprio servidor de transferência arquivos (parte2)”


  1. 1 Murilo A. Ferreira 03/25/2013 às 09:47

    Posso estar enganado, mas isso ai funcionar precisa alterar o seguinte código
    ————————————————————————————————–
    public void start() throws Exception{
    started = true;

    int count = 1;

    while(started){

    server = new ServerSocket(3000);

    final Socket socket = server.accept();

    Thread thread = new Thread(new Runnable(){
    @Override
    public void run() {
    service(socket);
    }
    }, “service-” + count++);
    }
    }
    —————————————————————————————————

    para isso, tirando a inicialização do server de dentro do while, ou a server será iniciada a cada ciclo, onde a porta já estaria em uso pelo fato da ServerSocket já incializada e ocupando a porta desde o ciclo anterior.

    —————————————————————————————————
    public void start() throws Exception{
    started = true;

    int count = 1;
    server = new ServerSocket(3000);// agora fora do while

    while(started){

    final Socket socket = server.accept();

    Thread thread = new Thread(new Runnable(){
    @Override
    public void run() {
    service(socket);
    }
    }, “service-” + count++);
    }
    }
    —————————————————————————————————

  2. 2 rodolfo4j 03/25/2013 às 10:11

    Verdade …. rsrsrs, vou corrigir o post.


Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s




Categorias

Atualizações Twitter

Erro: o Twitter não respondeu. Por favor, aguarde alguns minutos e atualize esta página.


%d blogueiros gostam disto: