Dúvida Simples

29 respostas
fajohann

Boa noite,
Estou implementando um multiserver com sockets e threads e gostaria de saber se é possivel o servidor fazer alguma ação no cliente quando este digitar uma tecla. Por exemplo, se o cliente digitar ‘dir’ mostrar os diretorios dele ou outra aplicação qualquer.
Desculpe a pergunta mas sou mto iniciante em sockets e já andei pesquisando sobre isso e só acho exemplos mais simples como mostrar a data e hora do servidor e tal mas até agora não vi nada sobre chamar processos internos do cliente dependendo da ação que ele tomar.

Espero que alguém possa me ajudar
Obrigado

29 Respostas

douglas_vidotto

Eu sei o básico também de conexões de socket em java. Vou dar uma sugestão, mas pode ser que haja soluções melhores e mais fáceis.

Bom, com o socket você consegue enviar dados do cliente para o servidor e vice-versa, certo? Bom, então, você pode enviar a String que você cria com o comando digitado pelo usuário. No servidor você a recebe e verifica o comando recebido desse cliente. Se for um dir, você pode criar objetos contendo a árvore de diretórios do servidor e enviar para o cliente de volta em um objeto que usa uma Lista ou uma TreeSet, enfim, qualquer coisa.

Não sei se a sugestão é boa, mas pelo pouco conhecimento que tenho, acho que eu faria isso.

Abraços!

B

Alguém pode explicar como a máquina virtual funciona?

fajohann
Olá, Então Douglas, é exatamente isso que eu estou tentando fazer..mas não estou conseguindo enviar o objeto de volta para o cliente... Por exemplo..para testar se o cliente digitar a string 'shutdown' ou 's' o computador dele deveria desligar...mas não funciona
if(cmd.equalsIgnoreCase("s") || cmd.equalsIgnoreCase("shutdown")){

                   Process process = Runtime.getRuntime().exec("cmd.exe /c shutdown -s \m//ipdocliente -t 10 ");
                    
}

Não sei como retornar objetos vindos do servidor ou comandos..só strings...
Se vc souber que comando eu uso para devolver objetos ou comandos que não sejam string..agradeço

Valeu

ViniGodoy

Já ouviu falar sobre “criar um protocolo”? Se tanto o seu cliente, quanto seu servidor são Java, uma boa também pode ser usar RMI.

fajohann

Como assim criar um protocolo ?? Sou meio novato nisso…
Mas RMI não posso usar tem que ser usando somente o conceito de sockets

Veja se pode ser implementado assim:

String s - para o cliente digitar determinada palavra o letra;

FileInputStream (ou BufferedInputStream) para ler um arquivo do servidor ou um comando tipo shutdown  quando o cliente digitar determinada String;

ObjectOutputStream para enviar os Objetos do servidor para o cliente;

ObjectInputStream para ler os Objetos vindos do servidor no cliente;

Seria +/- esse o raciocínio ou não é por ai?

Obrigado

ViniGodoy

O primeiro passo numa aplicação sockets, antes mesmo da programação, é definir como vai ser o protocolo de comunicação entre seu cliente e seu servidor.

O protocolo descreve como as mensagens serão codificadas, que mensagens existirão, etc. Por exemplo, você pode dizer que toda mensagem tem o seguinte formato:

1 int - Tamanho da mensagem
1 byte - Código da mensagem (de 0 até 127)
outros bytes - DADOS DA MENSAGEM

O campo “tamanho da mensagem” é muito importante. É ele que diz ao servidor quantos bytes devem ser lidos, para que aquela mensagem tenha sido completamente recebida. Esteja atento ao detalhe que, em sockets, o número de writes no lado do de quem escreve pode não ter nada a ver com o número de reads necessários no lado de quem lê a mensagem. Por exemplo, é possível que o cliente escreva um grande arquivo com um só write, mas que o servidor precise de vários pequenos reads para ler a mensagem completa, pois os bytes trafegam pela rede devagar e chegam aos poucos. O contrário também pode ocorrer, o cliente pode fazer vários writes pequenos, e os dados chegarem juntos ao servidor, e serem lidos num só read.

Sem a informação do tamanho, é impossível saber quantos bytes esperar, ou até onde ler. E como saber por onde começar a leitura? Nosso protocolo SEMPRE começa com o int de tamanho, ou seja, nosso servidor precisa começar lendo um int, que indica o tamanho da primeira mensagem.

Vamos adiante. A mensagem de código 01, poderia ser a de “listar diretório”. Ela, poderia exigir que os outros bytes estivesse preenchidos com uma String, contendo que diretório no servidor seria listado. Vejamos a estrutura da mensagem 01:

String - Caminho a ser listado.

Ela poderia ser respondida pela mensagem 01 do servidor. Essa mensagem, poderia ter o seguinte formato:

Para cada arquivo
1 int - Tamanho da string com o nome do diretório
String - Com o nome do diretório.

Note que como a resposta do servidor serão várias string, precisamos de um byte antes de cada delas para saber, na massa de dados, onde termina uma string e onde outra começa… Outra alternativa seria incluir um caracter separador, como faz o C++. Poderíamos, opcionalmente, fazer assim:

Para cada arquivo (forma opcional)
String - Com o nome do diretório.
um byte contendo o valor 0.

Eu prefiro a primeira forma, pois para os sockets é conveniente saber quantos bytes ler.

Ok, agora que você já sabe como será a conversa do servidor com o cliente, é possível codificar a mensagem que vai para o servidor:

public ByteArrayOutputStream codificarListarDiretorio(String diretorio) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(1); //Mensagem 1, listar diretório dos.writeChars(diretorio); //Diretório a ser listado return bos; }

Agora vamos fazer o método de enviar mensagem. Note que no método anterior, eu não incluí o campo do tamanho da mensagem. Isso porque, como esse campo é igual para todas as mensagens, preferi que o método de envio cuidasse desse detalhe. Isso é uma divisão em camadas do protocolo. O método de envio cuida da parte “bruta” que é dizer o tamanho total da massa de dados, e os outros métodos (como o de cima), se preocupam em codificar as mensagens em si.

Alguns protocolos incluem checksums, criptografia, e outras coisas, que poderiam ser colocadas aqui no método de envio. Coisas como essa validam a mensagem, ou a codificam, sem alterar sua estrutura. Como podem ser calculadas externamente, a lógica fica melhor dividida dessa forma.

Como o nosso protocolo é muito simples, e não tem nada disso, vamos ver como o método de envio fica:

public void enviarMensagem(ByteArrayOutputStream mensagem) { byte[] msg = mensagem.getByteArray(); DataOutputStream out = new DataOutputStream(socket.getOutputStream()); out.writeInt(msg.length); //Agora sim, o tamanho da mensagem out.write(msg.toByteArray()); //Os dados out.flush(); }

Note que para comandar uma listagem de diretório, agora basta fazer:

enviarMensagem(codificarListarDiretorio("C:\"));

E como seria a decodificação do servidor? Bom, agora você sabe o que esperar. O primeiro passo é ler quatro bytes (um int), que sabemos (pelo nosso protocolo) que será o tamanho da mensagem.

Depois, lemos a quantidade de bytes indicada nesse parâmetro. O primeiro byte lido, será o código da mensagem. Ao recebermos o código 01, saberemos (pelo protocolo) que, necessariamente, refere-se a uma mensagem de listar diretório.

Os bytes seguintes podem, então, ser seguramente copiados para uma string. E essa string terá o nome do diretório.

Espero que você tenha entendido o conceito. Como o procotolo é seu, sinta-se à vontade de fazer qualquer modificação, inclusive, usar somente strings. As informações estariam contidas nessa string, que deverá ser parseada entre o cliente e o servidor. O seu protocolo definiria então como o parser trabalharia nessa string. Muitos protocolos hoje são feitos dessa forma. É o caso dos protocolos SIP e dos web-services (os últimos usam xml).

Você também pode ler alguma RFC de um protocolo simples, como o TFTP, para ver como os projetistas organizaram o protocolo.

Eu não recomendo que você use somente strings, no entanto. É um bom exercício trabalhar com bytes, e ver como você pode otimizar o protocolo para consumir a menor banda possível. Na verdade, muitos protocolos são orientados a bytes, e seria uma boa experiência você aprender a trabalhar desse jeito também.

Dê uma olhada nesse programa de chat, que já deixei aqui no fórum. Ele aplica esses conceitos:
http://www.guj.com.br/posts/list/15/74218.java#506163

jidlafe

Bem explicado ViniGodoy. :smiley:

Parabéns.

fajohann

Baa, muito obrigado pela ajuda, vou ver se consigo implementar assim então…
Mas caso o cliente se conectar via Telnet eu também deveria proceder assim ?
Valeu

ViniGodoy

Telnet é o nome do protocolo especificado na RFC854.
http://www.faqs.org/rfcs/rfc854.html

Se você só estiver fazendo um Client, uma boa é usar uma classe já pronta, como essa aqui:
http://commons.apache.org/net/api/org/apache/commons/net/telnet/TelnetClient.html

Cuidado também pq muitas aplicações Telnet podem te enviar codificação Ansii, para texto colorido.
Aí é bom implementar uma espécie de filtro.

A

Consentimos que é bem melhor que ele crie um protocolo, mas ele não poderia retornar o objeto ao cliente atraves do ObjectOutputStream ?

ViniGodoy
Eu geralmente acho mais complexo (ou tão complexo quanto). Você tem que garantir diversas coisas:

a) Que todas as classes são serializáveis;

b) Que a serialização é depth e não shallow;

c) Que você trata serializações cíclicas;

Além disso, é pouco prudente não sentar antes e organizar sua comunicação. É muito importante saber que mensagens mandar, quais não mandar e como o servidor garantirá que a conexão ficará viva. Dependendo da carga, o ObjectInputStream também é pouquíssimo eficiente, por serializar dados demais.

Claro, para um sistema simples de faculdade, ele talvez quebre um galho. Mas é justamente na faculdade que você deveria entender os conceitos relacionados a rede, não simplesmente usar frameworks prontos.

E

Ok, agora que você já sabe como será a conversa do servidor com o cliente, é possível codificar a mensagem que vai para o servidor:

public ByteArrayOutputStream codificarListarDiretorio(String diretorio) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(1); //Mensagem 1, listar diretório dos.writeChars(diretorio); //Diretório a ser listado return bos; }

Se eu quisesse adicionar mais uma string na mensagem por exemplo, eu adicionaria um writeByte() depois do dos.writeChars(diretorio);? o codigo ficaria assim?

public ByteArrayOutputStream codificarListarDiretorio(String diretorio, String str) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(1); //Mensagem 1, listar diretório dos.writeChars(diretorio); //Diretório a ser listado dos.writeByte(1);// dos.writeChars(str); //essa eh a string que adicionei return bos; }

ViniGodoy

Não. Aquele 1 era o código da mensagem (listar diretório, no nosso exemplo) e é convencionado pelo programador. Como nessa mensagem havia 1, e apenas 1 String, não era necessário saber o tamanho de cada String.

No caso de adicionar outra String, seria necessário saber quantos bytes ler de cada String. Nesse caso, é melhor adicionar essa informação ao protocolo:

public ByteArrayOutputStream codificarListarDiretorio(String diretorio, String str) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeByte(1); //Mensagem 1, listar diretório dos.writeInt(diretorio.length()); //Nr de caracteres do diretório dos.writeChars(diretorio); //Diretório a ser listado dos.writeInt(str.length());// dos.writeChars(str); //string adicional return bos; }

Assim na hora de ler você saberia exatamente quantos bytes esperar para cada String.

E

Mas nesse caso como o receptor saberia onde começa e termina cada string? o exemplo que eu dei foi com esse intuito eu ecreveria mais um byte pra diferenciar o fim de uma string e inicio de outra (o 1 foi só por conviniencia).

Dessa forma como o protocolo esta sendo montado, eu poderia enviar qualquer objeto por mensagem?

Qual a diferença entre o protocolo e a mensagem?

ViniGodoy

Você saberia pois a primeira String começa logo após o cabeçalho, que tem uma quantidade de bytes conhecida.

E, de cara, você leria o tamanho dela. Assim você saberá exatamente quantos bytes ler, e poderia fazer isso sem trabalho.
A segunda String começaria logo após a primeira.

O que você fez são bytes de terminação. É uma estratégia ok também, usada por padrão nas Strings em C (que terminam com 0). Ela tem a vantagem de consumir apenas 1 byte, mesmo que sua mensagem seja longa (com o tamanho, vc sempre consome 4 bytes).

Entretanto, ela é um pouco mais difícil de trabalhar com comunicação em rede. Isso pq vc nunca sabe quantos bytes virão numa leitura. Você terá que fazer a leitura e ver se no meio dos bytes lidos está lá o terminador. Aí vc terá que separar os bytes do buffer referentes a primeira String, dos bytes que já podem estar representando o início da segunda. Não é exatamente difícil, mas é bem mais trabalhoso.

No caso de ter o tamanho, os métodos do socket geralmente permitem informar quantos bytes vc quer que sejam lidos no máximo.
Assim, fica fácil garantir que vc só receberá a primeira String, mesmo que sejam feitas várias leituras.

Além disso, recebendo o tamanho você já pode reservar um array de char para guardar sua String do tamanho exato.
Sem o tamanho, você seria obrigado a recorrer a uma estrutura dinâmica, como um StringBuilder.

No java isso não é um problemas, mas é especialmente interessante se você estiver comunicando com máquinas rodando em C (o Czão não tem StringBuilder).

Sua idéia funciona também, eu não havia reconhecido que era esse seu intuito com aquele 1 ali.
Achei que você tinha escrito por ter confundido o 1 que representa o identificador da mensagem no protocolo.
Não pareceu um terminador pq vc só escreveu o 1 no final da primeira String.

Sim.

O protocolo é a descrição de todas as mensagens possíveis e seu formato. Ou seja, a mensagem é apenas 1 item do protocolo
(assim como a palavra é um item do português, mas é a gramática que define nosso protocolo de comunicação).

E

Entendi agora. O problema que estou enfrentando é o seguinte, a aplicação que estou desenvolvendo é um jogo em rede e lida com vários tipos de mensagem. Eu criei uma superclasse mensagem e as outras herdam dessa classe, por exemplo, TextMessage, CreateGame e etc… Pelo que eu entendi em cada um dessas classes eu teria um metodo pra codifica-las e envia-las de acordo com o meu protocolo, não é isso?

Como está meu codigo:

public ByteArrayOutputStream encoderMessage(String message) throws IOException{
        
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        DataOutputStream dataOut = new DataOutputStream(byteOut);
        dataOut.writeByte(type);
        dataOut.writeChars(message);
        
        
        return byteOut;
    }
    
    public void sendMessage(ByteArrayOutputStream message) throws IOException{
        
        byte msg [] = message.getByteArray();
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());  
        out.writeInt(msg.length);  
        out.write(msg); 
        out.flush();
    }

Estou com problemas com o metodo getByteArray, quando faço a operação abaixo o netBeans acusa erro e diz que eu não posso realizar a operação.

byte[] msg = mensagem.getByteArray();
ViniGodoy

É pq o nome certo do método é toByteArray().

Quando tiver dúvidas como essa, consulte o Javadoc:
http://docs.oracle.com/javase/6/docs/api/java/io/ByteArrayOutputStream.html

danilo.figueiredo

Muito bom!

Obrigado! :smiley:

F

Pessoal, estou tendo dificuldades em ler meu stream no servidor, usando este protocolo criado como explicação pelo Vini (diga-se de passagem, quanta nobreza em compartilhar o conhecimento) …

O problema é que quando efetuo a leitura e converto pra string, tenho espaços vazios na string, ou seja, tenho q realizar alguma filtragem a esta conversão.
Abaixo meu codigo de leitura:

int tamanho = input.readInt();
    System.out.println(tamanho);
    byte[] buffer = new byte[tamanho];
    input.read(buffer);
    String s = new String(buffer);
    System.out.println("primeira palavra:");
    System.out.println(s);

se eu mandar a palavra “teste”, vai sair: " t e s t e" …
obs.: no envio estou dando um writeChars(string str); acho q tá aí o probelma, na quantidade de butes usados …
agradeço por antecipação se alguém puder me ajudar …
=)

ViniGodoy

Se você escreve com writeChar, então precisa fazer a leitura com readChar.

F

Ok,obrigado …
=)
fiz um for e li char por char …
como estarei passando um array-list de um lado pro outro, ou seja, bastante string´s, sabe me dizer se tem uma forma mais otimizada pra ler (ou até mesmo transmitir) essas strings´?
obrigado como sempre por antecipação …

ViniGodoy

No fundo a informação vai ser transmitida de uma vez só. O socket fará isso por baixo, carregando em buffers. Você no fundo está lendo de um buffer também, então, não há tanto problema assim.

Se tiver, você pode usar buffers de software, associando ao seu socket um BufferedInputSTream e um BufferedOutputStream.

F

muito obrigado pela atenção !!
muito mesmo …

G

Pelo que vi, se for apenas uma String que você qeur enviar, pode usar o writeUTF(minhaString) ao inves do writeChars(minhaString). Quando for receber, usa o readUTF() que ele deve ler a mensagem que você digitou.

Eu estou precisando fazer algo assim, só que eu quero, ao invés de enviar uma mensagem (uma String), enviar um arquivo. Alguém tem ideia de como fazer um protocolo nesse modelo?

Enviar o tamanho do arquivo e o arquivo
Do outro lado, receber o tamanho do arquivo, alocar o que for necessário e receber o arquivo, guardando em algum diretório.

Pode parecer fácil, mas estou enrolado para fazer isso… Alguém pode dar uma ajuda aí?

ViniGodoy

Não é muito diferente. Use o DataInputStream para enviar um int com quantos bytes serão transmitidos. Então, use o método write para escrever byte-a-byte.

Do outro lado, dê um readInt para saber quantos bytes você vai ler, e leia byte-a-byte.

R

Opa, e aí galera, beleza?

Será que alguém poderia me dar uma ajuda com uma situação estranha que estou tendo com SOCKET no Android, por favor.

Eu tenho o seguinte código dentro de uma Thread, quando eu uso o IP 192.168.0.104 e envio “.”, o servidor retorna uma String normalmente, porem se eu usar outro IP o servidor não retorna a String, a execução do programa para na linha dentro do while, simplesmente para, não entra na exeption nem nada, para de executar mesmo.

Porem, se eu executar qualquer IP no putt com telnet ou no aplicativo que foi feito para IOS funciona normalmente também. Apenas no Android está com esse problema.

Alguém saberia o que pode ser?

//Instancia as view's e recupera o IP e PORTA do EditText
				textIn = (TextView) findViewById(R.id.txtAlerta);
				editTextIp = (EditText) findViewById(R.id.editTextIp);
				editTextPort = (EditText) findViewById(R.id.editTextPort);
				
				String ip = editTextIp.getText().toString();
				int port = Integer.parseInt( editTextPort.getText().toString() );
				
				//Instancia socket, entrada e saída
				Socket socket = null;
				DataOutputStream dataOutputStream = null;
				DataInputStream dataInputStream = null;
				
				//Verifica se existe um novo dispositivo com o IP e PORTA informados e retorna sucesso ou exception
				try {
					
					socket = new Socket(ip, port);
					
					dataOutputStream = new DataOutputStream(socket.getOutputStream());
					dataInputStream = new DataInputStream(socket.getInputStream());
					
					dataOutputStream.writeUTF(".");
					dataOutputStream.flush();
					
					//Verifica se readLine possui algum texto e exibe quando encontrar
					BufferedReader buffer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
					
					do {
						
						readLine = buffer.readLine();
						
					}while(readLine == null || !readLine.substring(0, 1).equals("%"));
					
					
					final String readAux = readLine;


					//Fecha janela de progress
					progressDialog.dismiss();
					
					final TextView txxt = (TextView) ActivitySettings.this.findViewById(R.id.aaaaaaaaa);
					txxt.post(new Runnable() {
						@Override
						public void run() {
							txxt.setText(String.valueOf( readAux ));
						}
					});

					
				} catch (Exception e) {
					
					String message = "Dispositivo não encontrado ou o WIFI está desligado.";
					showToast(message);
					Log.e("Socket", e.getMessage());
					e.printStackTrace();
				}
				finally{
					
					//Fecha entrada se estiver aberta
					if(dataInputStream != null){
						try {
							dataInputStream.close();
						} catch (Exception e2) {
							
							String message = "Não foi possível fechar a conexão de entrada.";
							showToast(message);
						}
					}
					
					//Fecha saída se estiver aberta
					if(dataOutputStream != null){
						try {
							dataOutputStream.close();
						} catch (Exception e2) {
							
							String message = "Não foi possível fechar a conexão de saída.";
							showToast(message);
						}
					}
					
					//Fecha socket se estiver aberto
					if(socket != null){
						try {
							socket.close();
						} catch (Exception e2) {
							
							String message = "Não foi possível fechar a conexão com socket.";
							showToast(message);
						}
					}
					
					//Fecha janela de progress
					progressDialog.dismiss();
				}

Valeu pessoal

R

Alguém saberia o que pode ser?

L

Acho que minha dúvida se encaixa aqui.
Tenho um projeto de servidor/cliente, o servidor trabalhará independente, o cliente será somente um tipo de janela para os comando executados no servidor, o cliente não terá acesso ao código, somente as informações vindas do servidor. (Um tipo de comparação seria seu navegador acessando um site PHP, você tem controle do navegador, mas não tem controle do conteúdo do site).

Pensei em usar RMI, mas tem um detalhe, certas decisões do servidor dependerão das decisões tomadas pelos clientes conectados, com o servidor RMI isso dificulta muito, e pelo que entendi, o ServerSocket seria a melhor opção.

Agora uma dúvida quanto a comunicação cliente/servidor:
Quais são as opções confiáveis?

Pensei em criar uma tabela com possíveis comandos, não seria nada muito complicado, somente uma lista de caracteres e o significado de cada caractere para o servidor, dessa forma, quando o cliente mandar um “00” o servidor saberá que isso significa null, quando enviar 0F o servidor pegará a data atual, e assim por diante.
(um exemplo bem básico seria o SimpleChat do ViniGodoy, quando recebe “EXIT” ele desconecta o cliente)

Existe algum modo mais eficiente de ter um controle do servidor através de um cliente sem ter acesso ao código do mesmo por um Socket?

Li sobre o que o ViniGodoy escreveu, e fiquei na dúvida, o que seria exatamente um “Protocolo”?
Um padrão de mensagens entre servidor/cliente?

Obrigado!

mamangava

Boa tarde ViniGodoy, desculpa ressuscitar o tópico, mas estou precisando muito de uma ajuda.

Gostaria de saber como ficaria a parte do servidor nesse seu exemplo com mais de uma string.
Na verdade eu preciso enviar do cliente para o servidor vários arquivos, sendo enviados um de cada vez,
a cada iteração, junto com o arquivo estou enviando um md5 do arquivo para testar se o arquivo foi enviado corretamente.
Portanto, eu tenho q enviar o arquivo e o md5, validar se o arquivo foi enviado ok, e retornar um true ou false pro cliente.
se true ele pode excluir o arquivo senão o arquivo não é excluido.

Acho que consegui explicar o q eu preciso. Se puderem me dar uma força eu ficarei mto grato.

Atenciosamente,

Rafael.

Criado 29 de agosto de 2009
Ultima resposta 28 de jan. de 2014
Respostas 29
Participantes 13