Dúvida Simples

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

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!

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

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

[code] if(cmd.equalsIgnoreCase(“s”) || cmd.equalsIgnoreCase(“shutdown”)){

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

}
[/code]

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

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.

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

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

Bem explicado ViniGodoy. :smiley:

Parabéns.

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

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.

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

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.

[quote]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; }[/quote]

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; }

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.

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?

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).

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();

É 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

Muito bom!

Obrigado! :smiley:

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 …
=)