Sockets - Estranho erro de IO

11 respostas
E

Olá...

Tenho uma aplicação cliente-servidor. Nessa aplicação, o cliente faz upload de vídeos no servidor e pode fazer download de vídeos do repositório do servidor. Faço essa tranferência de dados via conexão TCP/IP...Sempre que o cliente faz upload de algum vídeo, além de enviar os dados do vídeo, envia um Objeto Propriedades, que encapsula propriedades autorais do vídeo...Sempre que ele faz download do vídeo, em seguida, ele baixa as propriedades do vídeo...

Para tanto, obtenho dosi tipos de fluxos de dados do meu socket um de Data e um do Object, assim:

private Socket socket;
private ObjectOutputStream saidaObjeto;
private ObjectInputStream entradaObjeto;
private DataInputStream entradaDado;
private DataOutputStream saidaDado;
public void conecta()throws ExcecaoDeIdentidadeNaoConfirmada, ExcecaoDeRede
{
        this.socket = new Socket(controlador.getIPServidor(),controlador.getPortaServidor());
        this.saidaObjeto = new ObjectOutputStream(socket.getOutputStream());
        this.entradaObjeto = new ObjectInputStream(socket.getInputStream());
        this.saidaDado = new DataOutputStream(socket.getOutputStream());
        this.entradaDado = new DataInputStream(socket.getInputStream());
}

Ou seja, abro em paralelo essas duas espécies de fluxo de dados...

Abaixo tenho o método que faz a requisição e recebe arrays de bytes do servidor e já os escreve em um arquivo que crio.

public File recebeArquivoDeVideo(String nomeDoArquivo)throws ExcecaoDeRede,ExcecaoDeClienteNaoConectado
{
        if(this.estahConectado)
        {
            try
            {
                Mensagem<String> requisicao = new Mensagem(TipoDeMensagem.REQUISICAO_ENVIO_DE_ARQUIVO_DE_VIDEO,nomeDoArquivo);
                saidaObjeto.writeObject( requisicao );
                saidaObjeto.flush();

                File arquivo = controlador.getArquivoDeVideoTemporario(nomeDoArquivo);

                Mensagem<Integer> mensagem = (Mensagem<Integer>)entradaObjeto.readObject();

                if(mensagem.getTipoDeMensagem()==TipoDeMensagem.TRANSPORTE_DE_INFORMACAO_DE_TAMANHO_DE_ARQUIVO)
                {
                    DataOutputStream saidaArquivo = new DataOutputStream(new FileOutputStream(arquivo));
                    //O servidor envia o tamanho do arquivo que está para enviar, empacotado em um Integer
                    int bytesParaReceber = ((Integer)mensagem.getDados()).intValue();
                    int tamanhoDoFragmento = 1024*128;
                    byte[] dados = new byte[tamanhoDoFragmento];
                    int bytesLidos;
                    
                    long tempoInicial = System.currentTimeMillis();
                    
                    while(bytesParaReceber>0)
                    {
                        //bytesLidos = (bytesParaReceber >= tamanhoDoFragmento ? tamanhoDoFragmento : bytesParaReceber);
                        if(bytesParaReceber >= tamanhoDoFragmento)
                        {
                            bytesLidos = tamanhoDoFragmento;
                        }
                        else
                        {
                            bytesLidos = bytesParaReceber;
                            dados = new byte[bytesLidos];
                            
                        }
                        
                        entradaDado.read(dados,0,bytesLidos);
                        saidaArquivo.write(dados,0,bytesLidos);

                        bytesParaReceber = bytesParaReceber - bytesLidos;
                        try
                        {
                                Thread.sleep(0,1);	
                        }
                        catch(InterruptedException IE)
                        {
                                continue;
                        }
                    }
                    saidaArquivo.close();
                    return arquivo;
                }
                else
                {
                        throw new ExcecaoDeRede();
                }
            }
            catch(IOException IOE)
            {
                throw new ExcecaoDeRede();
            }
            catch(ClassNotFoundException CNFE)
            {
                throw new ExcecaoDeRede();
            }
            catch(Exception ex)
            {
                throw new ExcecaoDeRede();
            }
        }
        else
        {
            throw new ExcecaoDeClienteNaoConectado();
        }
}

Aqui tenho o método que recebe o objeto Propriedades do servidor.

public Propriedades recebePropriedadesDeVideo(String nomeDoArquivo)throws ExcecaoDeRede,ExcecaoDeClienteNaoConectado
{
        if(this.estahConectado)
        {
            try
            {
                Mensagem<String> requisicao = new Mensagem(TipoDeMensagem.REQUISICAO_ENVIO_DE_PROPRIEDADES_DE_VIDEO,nomeDoArquivo);
                saidaObjeto.writeObject(requisicao);

                Mensagem<Propriedades> propriedades = (Mensagem<Propriedades>)entradaObjeto.readObject();//Nesta linha acusa o erro

                return propriedades.getDados();
            }
            catch(IOException IOE)
            {
                IOE.printStackTrace();
                throw new ExcecaoDeRede();
            }
            catch(ClassNotFoundException CNFE)
            {
                throw new ExcecaoDeRede();
            }
        }
        else
        {
            //Excecao e cliente não conectado ao servidor
            throw new ExcecaoDeClienteNaoConectado();
        }
}

Pois é...Esses doi método são sempre chamados um em seqüência do outro...Primeiro eu recebo os dados do vídeo e em seguida as Propriedades dele.

Reparem que no método em que recebo os dados do vídeo, fico recebendo fragmentos dele. O número de bytes de cada fragmento é dado pela variável tamanhoDoFragmento...Se eu estipulo esse valor em 1024 (ou seja, 1KByte), tudo ocorre maravilhosamente bem...Se altero esse valor (é claro, tanto no servidor, quanto no cliente), ocorre uma Exceção muito estranha:

[color=red]java.io.StreamCorruptedException: invalid type code: B6[/color]

Queria entender porque isso ocorre...Estou fazendo algo errado?

Obrigado...

11 Respostas

E

Esta exceção ocorre na linha 10 do método recebePropriedadesDeVideo…

E

Acabo de descobrir aqui, através do método da aplicação de testes exaustivos, que o valor limite para o tamanho do fragmento é 3760 bytes…Ou seja, posso variar de 1 a 3760 essa taxa que a transmissão funciona…Se eu usar 3761 já lança aquela exceção que comentei acima…

ViniGodoy

Que sleep de um nano segundo bizarro é aquele?

E

hahaha…Esqueci de tirar cara…Esse um nano foi pra testar algumas coisas de leitura e escrita…

E

Fiquei pensando se não há problema em trabalhar com esses dois tipos de fluxos obtidos a partir do mesmo Stream do socket…

ViniGodoy

O ideal, quando se usa sockets, é vc montar um protocolo e trafegar os dados no braço. É por isso que a sun criou classes como a ByteBuffer.
Ou usar um framework pronto, específico para isso, como o RMI.

Serializar objetos e joga-los na rede é o caminho certo para a frustração.

E

Por que?
Imagino (embora não saiba exatamente) que serializar um objeto, no fim das contas, por baixo, seja algo como buscar os campos da instância com reflection e fazer uma série de transmissões de tipos simples…Por ai…

Não consigo ver onde estariam os problemas com a serialização de objetos…

O que você me recomendaria?

ViniGodoy

O problema não está em serializar os objetos, mas em joga-los diretamente na rede.

O TCP é orientado a fluxo de dados, portanto, o número de writes que vc faz numa ponta, pode não ter qualquer relação com o número de reads dado na outra. Se você não tiver mecanismos de controles (ahem, um protocolo), fica muito difícil fazer as transmissões com qualidade e segurança.

O seu mecanismo pode ser um protocolo simples como:

  1. Dois bytes de sincronismo: 0xBA, 0xA1 (valores escolhidos ao acaso);
  2. O tamanho do objeto serializado, em bytes;
  3. O objeto serializado.

O importante é que, com o campo 2, você sabe exatamente quantos bytes esperar. E com isso, pode criar mecanismos de leitura e timeout.

Também seria importante prestar atenção no formato serializado, dependendo do tipo de aplicação. Se você estiver rodando carga, ou programando um jogo RTS, terá que usar tipos de dado com racionalidade, para não detonar a banda da sua rede, que está longe de ser um recurso tão abundante quanto a memória.

Finalmente, é bom lembrar que o TCP não tem mecanismos eficientes para descobrir que o outro lado da conexão caiu. Por isso, seria bom você também adicionar no seu protocolo mensagens de Keep Alive, para garantir que ambos os pontos da conexão estão vivos.

E

Pois é…Eu tentei implementar essas seguranças em um protocolo próprio…O problema é que fiz meu protocolo baseado na troca de Objetos Mensagem…Ou seja, com base na serialização…

Pelo que entendi do que disseste, esse protocolo teria como objetivo garantir um sincronismo entre as pontas, utilizando a Serialização… Ou entendi tudo errado?

ViniGodoy

É isso mesmo. Mas o objeto serializado teria que fazer parte do payload do seu protocolo.

Como eu falei, o problema não está em usar serialização, mas em como transmitir o dado serializado pela rede.

E

Olha só…
Eu parei de usar os DatainoutStream/DataOutputStream para enviar os dados do vídeo…Agora estou encapsulando os blocos de dados dentro de objetos mensagem…Agora posso alterar meu tamanho do fragmento como quiser que tudo funciona perfeitamente…
Fiz um teste agora a pouco enviando fragmentos de 100kBytes e foi tranqüilo…

Agora estou pensando em como determinar esse valor…Qaul seria o meu valor de equilíbrio…
Penso se existe uma forma de determinar esse valor até dinamicamente…

Criado 6 de agosto de 2008
Ultima resposta 8 de ago. de 2008
Respostas 11
Participantes 2