Comunicação entre sockets

38 respostas
S

Olá … estou tendo problemas com conexões socket em um programa que estou fazendo aqui. A situação é a seguinte, existe duas classes gerente e trabalhador e ambas criam um serversocket e socket para haver uma comunicação entre elas. O que esta ocorrendo é que nem sempre o gerente ou trabalhador recebem as informações necessárias pelo socket. Por exemplo, se eu instanciar um gerente G ele cria um server socket, e se eu instanciar dois trabalhadores A e B, estes trabalhadores criam um server socket e um socket com o gerente G. A partir daí se um trabalhador ou ambos tentarem enviar mensagem para o gerente nem sempre esse gerente recebe essa mensagem.

Não sei se consegui me expressar corretamente mas segue o codigo do meu communicator:

Essa classe é responsavel por criar um server socket e aceitar conexoes, entao no caso do gerente G new Communicator(), e ambos os trabalhadores A e B criam um new Communicator(“192.168.1.2”,50000). Até aí tudo bem, o problema é quando o trabalhador envia sendNewWorker(…) para o channel criado com o G nem sempre esse recebe a mensagem. Fiz um teste aqui, criei dois trabalhadores e coloquei em um for=5 para ficar enviando mensagem para o G e somente enviou 3 mensagens da primeira vez que executei e na segunda vez que executei enviou 4.

Alguém pode me auxiliar nesse problema? O que há de errado?

FLW.

38 Respostas

alucardeck

perae vamos por partes…

soh deve existir um serversocket…
a partir dai os clients devem se conectar nesse serversocket…

vamos supor que o Gerente é o principal…
ele possui varios trabalhadores…

logo o Gerente é o serverSocket, e os trabalhadores são apenas sockets/client

para existir transmissão entre 2 sockets, basta eles terem sido ligados em comum em UM serverSocket, vc nao precisa ter um server de cada lado…

eu tenho um exemplo antigo e pratico de um JChat por socket na assinatura do meu post…
veja logo abaixo
V
V
V

ViniGodoy

Esse post vai te esclarecer alguns conceitos básicos:
http://www.guj.com.br/posts/list/136538.java#735860

Pode explicar porque você está perdendo mensagens.

S

Ok, vlw vou dar uma olhada. Possuo uma dúvida ainda, você comentou que é somente UM serversocket. Ex:

G(192.168.1.2:70000) -> ServerSocket
Lanço o T, nesse momento T solicita uma conexão com G, se aceita cria uma coxeão do tipo:
G(192.168.1.2:34214) <-----> T(192.168.1.2:34215), Correto? A partir daí o T pode enviar mensagens para G e vice-versa? Correto? Essa é minha dúvida.

ViniGodoy

Sim, correto, mas leia o texto que postei, pq vc vai ver que você tem alguns entendimentos errados sobre como essas mensagens trafegam.

S

Não estou entendendo muito bem o que está de errado. Tudo bem, ao invés de estar utilizando Out/InPutStream estou utilizando bytebuffer para “empacotar” as mensagens. e o formato da mensagem deve ser necessariamente.

buf.putShort(TIPOMSG); ===> Tipo da mensagem
	buf.putInt(size); ===> o tamanho da mensagem
	buf.put(workerId_b); ====> String
	buf.put((byte) 0); ====> esse byte 0 é um limitador para quand vou ler os dados 
	buf.putInt(workload); ===> Int
	buf.putFloat(capacity); ===> floar

Ja fiz um teste aqui e realmente o problema está na leitura dos dados do buffer, ou seja, no metodo run() da classe listener dentro do cmmunicator que enviei antes.

ViniGodoy

Sim, mas você precisa usar a informação do tamanho dos bytes na hora de fazer o read.

O que você precisa entender é que um write do lado em uma ponta não significa um read em outra. Como o dado é enviado aos poucos, pode ser que você precise de vários reads.

É necessário fazer algum tipo de leitura certa no início. No seu caso, leia individualmente um short e um int. Então, faça um método que leia byte-a-byte o que vem da rede, até que o número de bytes lidos seja igual ao campo size. Não há problema em usar o ByteBuffer para isso (no meu sistema eu também uso).

Mas é importante entender que o socket funciona assim.

S

ok. Achei o meu erro. tens razão a leitura estava sendo feita de maneira erra. TANKS

S

aechiara

spiderman, acho que vc não leu ou não entendeu o que o pessoal mandou, vamos lá

para ler de um socket, tudo que vc precisa fazer é algo assim se tiver passando uma String, não bytes (sim, vc pode enviar o objeto de uma classe, serializar ele e deserializar do outro lado, mas vamos manter a string por enquanto):

try 
{
    BufferedReader rd = new BufferedReader(new InputStreamReader(socket.getInputStream()));

    String str;
    while ((str = rd.readLine()) != null) {
        process(str);
    }
    rd.close();
} catch (IOException e) 
{
//tratar exception
}

feito isso, vc faz o parse dentro da String str e pega as mensagens.

Simples assim, como o pessoal disse, apenas o “Gerente” seria o ServerSocket, e os outros somente Socket.

ViniGodoy

aechiara:
spiderman, acho que vc não leu ou não entendeu o que o pessoal mandou, vamos lá

para ler de um socket, tudo que vc precisa fazer é algo assim se tiver passando uma String, não bytes (sim, vc pode enviar o objeto de uma classe, serializar ele e deserializar do outro lado, mas vamos manter a string por enquanto):

Ahem… dá uma lida no artigo sobre protocolos que eu já passei ali em cima, que você vai ver que para manipulação direta do socket como o colega está fazendo o buraco é bem mais embaixo. Eu não recomendo que ninguém use esses métodos de read e write diretamente sem saber exatamente o que está fazendo. O caso que você postou, por exemplo, usa implicitamente o \n como terminador, coisa que é pouco eficiente com sockets. Um DataOutputStream, por exemplo, não será tão tolerante, pois o número de reads não corresponde necessariamente ao de writes, e você pode acabar com lixo nas mãos se pressupor isso.

E, outra coisa, não se recomenda o uso de ObjectInputStream e ObjectOutputStream via socket. Esses métodos geram leaks, e geralmente causam muita dor de cabeça.

spiderman, não vi no seu reader onde você separa o cabeçalho e faz as leituras dos bytes iniciais do protocolo. E nem onde, em seguida, você reserva os bytes baseado nesse protocolo. Você deveria interpretar seu próprio protocolo no momento da leitura.

S

Olá.

Olha só ViniGodoy, eu fiz o que você sugeriu em um post anterior que seria ler o tipo da mensagem e o tam e depois ler byte/byte:

Bom acredito que o meu problema esta quando adiciono a msg em uma lista bloqueante(incoming). Eu tenho uma outra thread que fica aguardando chegar uma mensagem nessa lista bloqueante e ler os dados. O problema agora é que sei que tenho dados nessa lista mas não consigo dar um buf.get nos dados veja, essa é a thread que fica aguardando a lista:

ViniGodoy

É bom você ter método que lêem uma quantia garantida de bytes. Eu implementei um método assim na minha aplicação, deixa eu te mostrar:

/**
     * Reads a given number of bytes. The thread will block until the given
     * number of bytes are read or an I/O error occur.
     * 
     * @param bytes Number of bytes to read
     * @return A flipped bytebuffer containing the bytes read.
     * @throws IOException If some I/O error occurs.
     */
    private ByteBuffer blockRead(int bytes) throws IOException
    {
        ByteBuffer msg = ByteBuffer.allocate(bytes);
        int readBytes = 0;
        while (readBytes < bytes)
        {
            int readCount = channel.read(dst);
            if (readCount == -1) // Server disconnected?
            {
                Log.getInstance().severe(getClass(), "blockRead(int bytes)",
                        "Connection closed by server!");
                disconnect();
                return null;
            }
            readBytes += readCount;
        }
        dst.flip();

        return dst;
    }

Para adaptar para sua, talvez seja necessário mudar o código que está dentro da parte que ele testa se o servidor desconectou. No meu caso, isso não deveria ser um comportamento adequado, por isso o cliente simplesmente loga isso e chama o método disconnect(), que faz com que ele feche o próprio socket. A thread que processa as mensagens também está programada para parar caso o blockRead retorne null.

Aí você pode fazer:

ByteBuffer msg = blockRead(msg, size);

Note que isso já lê a mensagem inteira num buffer, e já deixa esse buffer preparado para ser trabalhado. Você poderia criar uma pequena classe, para enfileirar essa mensagem + seu tipo numa fila. Ou mesmo já chamar um método para decodificar isso num evento mais amigável.

Lembre-se. O número de reads nem sempre é o número de writes. Veja o seu código, você faz:

nBytes = clientChannel.read(buf);

mas considere que nBytes seja 2. Significa que ele leu apenas o short que estaria vindo, mas o int ainda não chegou. E isso pode acontecer, mesmo que vc tenha feito um único “write” no buffer do outro lado, com os dois valores. Portanto, você deveria esperar mais bytes. Quando vc tenta decodificar o int, dá um BufferOverflowException, pois você tentou ler dados que ainda não foram colocados no buffer.

O que o método “blockRead” faz, é justamente esperar um número fixo de bytes, não mais, não menos. Por isso ele leva em consideração esse valor de retorno, e aguarda os bytes até que o valor chegue, certinho.

Para esperar o cabeçalho de 6 bytes (um short + um int) você faz:
ByteBuffer header = blockRead(6);

Esse método garante que 6 bytes foram lidos. E vai bloquear a thread até que os 6 estejam no buffer. Em seguida, vc decodifica esse cabeçalho e faz um outro blockread, mas dessa vez para o conteúdo da mensagem.

Você deve entender que o read lê no máximo o tamanho do buffer, mas não dá garantias quanto ao mínimo. É nessa hora que o protocolo é importante. Só através dele você sabe quantos bytes devem ser lidos. 6 no caso do seu cabeçalho, size no caso dos demais dados.

S

:smiley: :smiley: :shock: :smiley: Te juro que tentei entender. Mas não entendi nada. :smiley:

ViniGodoy

Você tem que entender o seguinte. Quando você cria um ByteBuffer de 6 bytes, e faz um read sobre esse bytebuffer, nada garante que 6 bytes serão lidos.

Mesmo que o outro lado da conexão tenha escrito em um único write os 6 bytes, pode ser que só 4 tenham viajado pela rede no momento do seu read, e o método read vai retornar o valor 4, indicando que só 4 bytes chegaram.

Então, você terá que fazer um segundo read, para pegar os 2 bytes que faltaram. Esse é um exemplo de situação onde existe 2 reads para um único write.

Estou falando em 6 bytes pois esse é o tamanho do cabeçalho do seu protocolo (um int = 6 bytes, 1 short = 2 bytes).

Agora, pare um pouco e olhe o que o método que você postou faz. Você cria um bytebuffer com 6 bytes:

buf = ByteBuffer.allocate(2 + 4); buf.clear(); //Desnecessário, o bytebuffer já está limpo nBytes = clientChannel.read(buf); //Faz um read

E apenas testa se o servidor não fechou a conexão. Mas caso contrário, você já sai lendo o protocolo:

if (nBytes == -1) { channelClient.close(); } else { buf.flip(); short msg_type = buf.getShort(); int size = buf.getInt(); //Ops, e quem garante que nBytes é 6?

O getInt() ali está sendo chamado, mas pode ser que você caia na situação que descrevi acima. Você fez um write do cabeçalho no lado cliente, mas só chegou aqui 2 bytes, referentes ao tipo da mensagem. Esses 2 bytes são copiados para o buffer buf, você faz o flip e o que acontece ao tentar fazer o readInt()? BufferUnderflowException.

Aquele método que postei, chamado blockread faz a leitura completa dos 6 bytes, antes de retornar o ByteBuffer. Ele recebe como parâmetro um número de bytes, e vai somando o readcount até que todos os bytes tenham efetivamente sido lidos do canal.

O seu método repete esse mesmo erro para a leitura do conteúdo. Você carrega um buffer de tamanho size, mas faz apenas um único read para ele, despreocupadamente. Para resolver isso, use o método blockRead que postei.

S
P*** mas eu devo ser burrinho mesmo. Tchê o que estou fazendo de errado?

Exception in thread Thread-2 java.lang.OutOfMemoryError: Java heap space

at java.nio.HeapByteBuffer.(HeapByteBuffer.java:57)

at java.nio.ByteBuffer.allocate(ByteBuffer.java:329)

at testenovoCOM.Communicator$Listener.run(Communicator.java:123)

[/code]

A VM não está conseguindo alocar memória suficiente … :cry: :cry:

ViniGodoy

Na linha 14, você precisa alocar size + 6. Se vai armazenar por ali o cabeçalho.
Você pode apagar a linha 15. Não é preciso fazer o rewind no buf. O blockread já o deixa pronto para leitura.

Ok, ali na linha 19 você fez um incoming.put(msg). Quando você retira a mensagem da lista incoming e processa?

S

Existe uma outra thread que fica aguardando essa lista bloqueante receber um valor, no caso msg(bytebuffer). A thread é essa

ViniGodoy

E essa thread está imprimindo os dados no console? Essa lista bloqueante é uma LinkedBlockingQueue?

S

isso, é uma BlockingQueue incoming = new LinkedBlockingQueue();

e está imprimindo o seguinte na console:

Recebendo mensagem - Manager

Exception in thread "Thread-2" java.lang.OutOfMemoryError: Java heap space
	at java.nio.HeapByteBuffer.<init>(HeapByteBuffer.java:57)
	at java.nio.ByteBuffer.allocate(ByteBuffer.java:329)
	at testenovoCOM.Communicator$Listener.run(Communicator.java:123)
TIPO MSG: 16965
TAMANHO: [telefone removido]
LOST MESSAGE! Type: 16965

Esses print só estão aí para teste mesmo. Na real não precisam(deveriam estar aí)

ViniGodoy

Estranho estar chegando esse int enorme aí no size. Já tentou usar o Wireshark para ver o que está indo pelo canal?

ViniGodoy

Só corrigindo:

public void run() {
   try {
      while (listening) {
         ByteBuffer header = blockRead(6);
         if (header == null) {
            clientChannel.close();
         } else {
            header.getShort(); //Msg_Type
            int size = header.getInt();
            header.rewind();

            ByteBuffer body = blockRead(size);
            ByteBuffer msg = ByteBuffer.allocate(6 + size);
            msg.put(header).put(body).flip();

            try {
               incoming.put(msg);
            } catch (InterruptedException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
            }
         }
      }
   } catch (IOException e) {
         System.out.println("Communicator =>");
         System.out.println("\t Erro ao receber mensagem.");
         System.out.println("\t\t" + e + "\n");
         System.exit(1);
   }
}

Ainda assim, isso não explica o valor maluco que está chegando no size(). É bom descobrir se a mensagem está mesmo sendo enviada corretamente, ou se é um problema de decodificação do cliente.

S

BufferOverflowException em body.put(header).put(msg).flip();

Somente um comentário para ver se seu método está correto:

int readCount = clientChannel.read(msg);

msg.flip();

return msg;

No seu exemplo que você colocou em outro post nessas 3 linhas  você tinha dst, está correto da maneira que está  em cima?
S

ahhh e o metodo para escrever é esse:

ViniGodoy

Tinha um erro na linha 14. Eu estava fazendo body.put(header).put(body), quando o certo era fazer isso com o msg.

ViniGodoy

Esse eu já tinha visto. Usou lá o wireshark pra ver se está enviando certo? Ou se estamos recebendo errado?
Pelo método de recepção, vejo poucas possibilidades de erro.

S

por partes, você comentou que havia um erro na linha 14, mas o BufferOverflowException está ocorrendo com essas opções que você comentou, ou seja, body.put(header).put(msg).flip(); o msg no lugar do body.

ViniGodoy

Veja a linha corrigida lá. É:

msg.put(header).put(body).flip();
S

Verifiquei aqui com o wireshark e a mensagem está sendo escrita corretamente. Mas continua ocorrendo esse erro, na linha ByteBuffer body = blockRead(size);

Exception in thread "Thread-2" java.lang.OutOfMemoryError: Java heap space
	at java.nio.HeapByteBuffer.<init>(HeapByteBuffer.java:57)
	at java.nio.ByteBuffer.allocate(ByteBuffer.java:329)
	at testenovoCOM.Communicator.blockRead(Communicator.java:143)
	at testenovoCOM.Communicator.access$000(Communicator.java:16)
	at testenovoCOM.Communicator$Listener.run(Communicator.java:121)
S

Achei o problema, era só colocar um -6 no segundo blockRead. Porque o total de byte escritos são 23 mas como o cabeçalho é 6 e foi no primeiro blockRead faltava ser lido o restante ou seja, 23 - 6. :wink:

MUITO OBRIGADO ViniGodoy, MUITO OBRIGADO MESMO. VOCÊ É OOOOOO CARAAAA :wink: :wink: :wink:

ViniGodoy

Ah claro.
Desculpe, no meu protocolo nosso size não inclui o tamanho do cabeçalho. Por isso nem me liguei que o seu pudesse ter.

Mas beleza, legal que tá funcionando. :slight_smile:

Abraços,

Vini

S

Olá Vini e ou demais membros do forum, estou com um problema de lógica de OO. Vejam o arquivo em anexo, para executar:
java -cp bin/ test.Init
java -cp bin/ core.Manager ManagerTeste0001 192.168.1.2:51000

claro, se o ip da sua máquina for outro, altere-o. Bom o problema está quando a classe launcher tentar enviar uma mensagem para o manager. Quando eu crio um socket com o manager no metodo construtor do launcher a mensagem não é enviada e quando eu crio um socket dentro do metod run() antes de

managerSend.sendMsgNewWorker(managerSend.getSocket(), "2222","7777777"); a mensagem é enviada. Alguém pode me auxiliar o porque isso está ocorrendo?

Eu preciso necessariamente criar um socket com o manager e enviar e receber mensagem por este somente e não sempre criar um socket quando quero enviar uma mensagem.

ViniGodoy

Putz, pergunta difícil. Seria necessário entender seu código inteirinho.

Só uma coisa eu te garanto. É possível enviar e receber quantas mensagens você quiser numa única conexão socket, desde que você não a feche.

S

O problema está com a thread. Quando eu executo:

public worker(string canaldoservidor) {
   workerChannel = new Communicator(canaldoservidor);
   this.start()
}

public void run() {

   workerChannel.SendMessages(....);
}

A mensagem é escrita no canal, mas o servidor nao consegue ler. agora quando eu faço :

public worker(string canaldoservidor) {
   this.serverChannel = canaldoservidor
   this.start()
}

public void run() {
   workerChannel = new Communicator(serverChannel);
   workerChannel.SendMessages(....);
}

A mensagem é enviada e recebida(write e read). Eu fiz um outro teste aqui, criei um metodo(sendMsg()) para enviar e simulei as mesmas situações coloquei esse metodo no metodo construtor e enviou, coloquei dentro do run() não enviou.

O que estou fazendo de errado? O que pode ser?

S

Ninguém saberia me dizer o que está acontecendo, referente ao post anterior? que desespero … :frowning: :frowning: :frowning: :cry: :cry: :cry:

S

Olá ViniGodoy, pois é eu ainda estou com problema nessa comunicação. Está ocorrendo algo muito estranho. Não sei se você está lembrado dessa situação, mas ainda não estou recebendo as mensagens na outra ponta(servidor) principalmente quando minha classe worker(thread) tenta enviar duas mensagens seguidas pelo mesmo canal, o outro lado não recebe, quer dizer as vezes recebe e as vezes não.

Algo que notei, foi que quando a minha máquina é reiniciada e após reiniciar eu tento executar novamente o meu programa a mensagem é enviada. após tentar executar pela segunda vez a mensagem não é mais enviada.

Teria alguma idéia do que pode estar ocorrendo???

S

só mais uma perguntinha de iniciante ou precipiante :smiley: este problema que está havendo não pode ser algo relacionado à memória cache?

ViniGodoy

Estranho, não parece ter a ver não.

S

estou entrando no estado de desespero já … outra situação … eu tinha dois metodos em separado para enviar os dois tipos de mensagem. A thread do worker chamava esses dois metodos. Bom, excluí estes dois metodos e adicionei as duas opções de envio dentro da propria thread. Quando salvei e executei novamente as mensgens foram enviadas, mas quando fui executar pela segunda vez ja nao enviou mais.

:frowning: :frowning: não sei mais o que fazer.

Criado 10 de agosto de 2010
Ultima resposta 28 de ago. de 2010
Respostas 38
Participantes 4