Byte Separation

3

I am building a system to do communication via Socket:

The client system sends the following information:

1 - int - message size

2 - bytes - the message

The message consists of:

1 - int - method code that I want to run on the server

2 - int - list size I want to send

3 - int - id of the person

4 - int - person name string size

5 - String - string of the person's name

Topics 3, 4, and 5 run for each list item

My client system looks like this:

private void btComunicarActionPerformed(java.awt.event.ActionEvent evt) {                                            
        List<PessoaMOD> pessoas = new ArrayList<PessoaMOD>();
        pessoas.add(new PessoaMOD(1, "Pessoa 1"));
        pessoas.add(new PessoaMOD(2, "Pessoa 2"));
        pessoas.add(new PessoaMOD(3, "Pessoa 3"));
        pessoas.add(new PessoaMOD(4, "Pessoa 4"));
        pessoas.add(new PessoaMOD(5, "Pessoa 5"));
        pessoas.add(new PessoaMOD(6, "Pessoa 6"));
        pessoas.add(new PessoaMOD(7, "Pessoa 7"));
        pessoas.add(new PessoaMOD(8, "Pessoa 8"));
        try {
            Socket cliente = new Socket("127.0.0.1", 12345);
            enviarMensagem(codificarListarPessoas(pessoas), cliente);
        } catch (Exception e) {
            System.out.println("Erro: " + e.getMessage());
        } finally {
        }
    }                                           

    public ByteArrayOutputStream codificarListarPessoas(List<PessoaMOD> pessoas) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);
        dos.writeByte(1); //Método 1, gravar pessoas 
        dos.writeInt(pessoas.size()); // tamanho da lista
        for (PessoaMOD p : pessoas) {
            dos.writeInt(p.getId()); // id da pessoa
            dos.writeInt(p.getNome().length()); //Nr de caracteres do nome da pessoa
            dos.writeChars(p.getNome()); //Nome da pessoa
        }
        return bos;
    }

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

My question is to read this on the server: I'm doing this on the server:

private void btIniciarActionPerformed(java.awt.event.ActionEvent evt) {                                          
        new Thread() {
            @Override
            public void run() {
                try {
                    ServerSocket servidor = new ServerSocket(12345);
                    System.out.println("Servidor ouvindo a porta 12345");
                    while (true) {
                        Socket cliente = servidor.accept();
                        System.out.println("Cliente conectado: " + cliente.getInetAddress().getHostAddress());
                        DataInputStream entrada = new DataInputStream(cliente.getInputStream());

                        int tamanhoMsg = entrada.readInt(); // ler tamanho da mensagem

                        // leio os bytes de acordo com o 
                        //tamanho da mensagem lida anteriormente
                        byte[] bytes = new byte[tamanhoMsg];
                        int op = entrada.read(bytes, 0 , bytes.length);                        

                        // Como posso fazer a leitura separada dos dados enviados?

                        entrada.close();
                        cliente.close();
                    }
                } catch (Exception e) {
                    System.out.println("Erro: " + e.getMessage());
                } finally {
                }
            }
        }.start();
    } 

I read the size of the message and received all the bytes according to the size sent .....

But now how can I separate these bytes according to the data I sent, ie, separate the size of the list, id, string size, name string.

    
asked by anonymous 23.02.2016 / 17:07

3 answers

2

Want some advice? Use JSON. To create binary protocols is to ask to be disturbed. Tomorrow or later you have to interface with a counterpart that was not written in Java ...

    
24.02.2016 / 00:25
1

Just as you did the writing ... now you need to do the reading. Just follow the same order.

For example, you put the size with writeInt and read with readInt. Do the same pro remainder (if you wrote with writeChars, you should read with readChars and so on).

If you want to use the byte vector, you can create temporary byte vectors with the size of the object you want to convert, and then:

String str = new String(bytes, StandardCharsets.UTF_8);
int i= bytes[0];
    
23.02.2016 / 17:16
1

Setting a Goal

The first thing is to understand what you are doing. Basically you are creating your own protocol. This protocol defines the format in which you are serializing a message and the deserialization routine must meticulously follow the same format.

Working at the byte level is something that may require some more advanced knowledge of how each complex type, for example a String, can be represented in bytes.

Current implementation issues

Character encoding

The first problem is that in the generation of the message you are writing the characters of the name in the wrong way. The writeChars method does not do what you think it does. String will lose information.

Whenever you work with Strings transformation you should take into account that strings are represented using some coding as UTF-8 , ASCII or ISO--8859-1 . In addition, the number of bytes is not always equal or proportional to the number of characters.

In this case, the DataOutputStream class has the writeUTF method, which is done to correctly encode Strings in UTF-8 , including the number of bytes.

Organization

I know this is probably an exercise that will not be used on a system of truth. However, always organize your code so that it is easy to test isolated parts.

The clearest example of the problem is that it is not possible to test serialization and deserialization in bytes without relying on running the entire program and connecting the sockets.

You've probably run this a few dozen times, maybe hundreds, and you waste lots of time just to do a simple test. To be more efficient, simply extract the important passages into methods and create a class that runs only those isolated passages.

Only after the serialization is working should you worry about making the sockets and other aspects of the program work.

Solution

I made a simplified implementation of the process and I will put the code snippets below.

Serialization

I've de-serialized the list of people like this:

public static byte[] serializarPessoas(List<PessoaMOD> pessoas) throws IOException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(bos);
    dos.writeInt(pessoas.size()); // tamanho da lista
    for (PessoaMOD p : pessoas) {
        dos.writeInt(p.getId()); // id da pessoa
        dos.writeUTF(p.getNome()); //Nome da pessoa
    }
    return bos.toByteArray();
}

Deserialization

To reconstruct objects from an array of bytes or even a InputStream , simply use the DataInputStream class and read the bytes in the exact order you write them:

public static List<PessoaMOD> desserializarPessoas(byte[] bytes) throws IOException {
    DataInputStream entrada = new DataInputStream(new ByteArrayInputStream(bytes));
    List<PessoaMOD> pessoas = new ArrayList<>();
    int quantidadePessoas = entrada.readInt();
    for (int i = 0; i < quantidadePessoas; i++) {
        int id = entrada.readInt();
        String nome = entrada.readUTF();
        pessoas.add(new PessoaMOD(id, nome));
    }
    return pessoas;
}

Test

Finally, I've created a method to ensure that the above implementations work properly:

public static void main(String[] args) throws IOException {
    List<PessoaMOD> pessoas = new ArrayList<>();
    pessoas.add(new PessoaMOD(1, "Pessoa 1"));
    pessoas.add(new PessoaMOD(2, "Pessoa 2"));
    pessoas.add(new PessoaMOD(3, "Pessoa 3"));
    pessoas.add(new PessoaMOD(4, "Pessoa 4"));
    pessoas.add(new PessoaMOD(5, "Pessoa 5"));
    pessoas.add(new PessoaMOD(6, "Pessoa 6"));
    pessoas.add(new PessoaMOD(7, "Pessoa 7"));
    pessoas.add(new PessoaMOD(8, "Pessoa 8"));

    byte[] bytes = serializarPessoas(pessoas);
    List<PessoaMOD> novasPessoas = desserializarPessoas(bytes);

    if (!pessoas.equals(novasPessoas)) {
        throw new IOException("O programa não conseguiu reconstruir os dados!");
    }
}

I added the equals method to the PessoaMOD class so that the comparison of the original list with the restored version works correctly:

public class PessoaMOD {
    private String nome;
    private int id;
    public PessoaMOD(int id, String nome) {
        this.id = id;
        this.nome = nome;
    }
    public String getNome() {
        return nome;
    }
    public int getId() {
        return id;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PessoaMOD pessoaMOD = (PessoaMOD) o;
        return id == pessoaMOD.id &&
                Objects.equals(nome, pessoaMOD.nome);
    }
    @Override
    public int hashCode() {
        return Objects.hash(nome, id);
    }
}

Alternative Using Serialization

Another alternative rather than creating your own protocol is to encapsulate the message as in the Command >.

Implementing a generic Mensagem fault

For this, we need a generic class to represent the message, for example:

public abstract class Mensagem implements Serializable {
    private Metodo metodo;
    private Object conteudo;
    public Mensagem(Metodo metodo, Object conteudo) {
        this.metodo = metodo;
        this.conteudo = conteudo;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Mensagem that = (Mensagem) o;
        return metodo == that.metodo &&
                this.conteudo.equals(that.conteudo);
    }
    @Override
    public int hashCode() {
        return Objects.hash(metodo, conteudo);
    }
}

Note that instead of using an integer, I created an Enum to represent the method to be executed:

public enum Metodo {
    GRAVAR
}

Creating the specific message for this action

So we can create the implementation for the message of recording people:

public static class MensagemGravarPessoa extends Mensagem {
    public MensagemGravarPessoa(List<PessoaMOD> pessoas) {
        super(Metodo.GRAVAR, pessoas);
    }
}

The class PessoaMOD remains the same.

Serializing and deserializing

Serialization and deserialization methods get much simpler when we use ObjectOutputStream and ObjectInputStream to do the heavy lifting:

public static Mensagem desserializarPessoas(byte[] bytes) throws IOException, ClassNotFoundException {
    ObjectInputStream entrada = new ObjectInputStream(new ByteArrayInputStream(bytes));
    return (Mensagem) entrada.readObject();
}

public static byte[] serializarPessoas(Mensagem p) throws IOException {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream os = new ObjectOutputStream(bos);
    os.writeObject(p);
    return bos.toByteArray();
}

Test

Finally, the test routine:

public static void main(String[] args) throws IOException, ClassNotFoundException {
    List<PessoaMOD> pessoas = new ArrayList<>();
    pessoas.add(new PessoaMOD(1, "Pessoa 1"));
    pessoas.add(new PessoaMOD(2, "Pessoa 2"));
    pessoas.add(new PessoaMOD(3, "Pessoa 3"));
    pessoas.add(new PessoaMOD(4, "Pessoa 4"));
    pessoas.add(new PessoaMOD(5, "Pessoa 5"));
    pessoas.add(new PessoaMOD(6, "Pessoa 6"));
    pessoas.add(new PessoaMOD(7, "Pessoa 7"));
    pessoas.add(new PessoaMOD(8, "Pessoa 8"));

    Mensagem mensagem = new MensagemGravarPessoa(pessoas);

    byte[] bytes = serializarPessoas(mensagem);
    Mensagem mensagemNova = desserializarPessoas(bytes);

    if (!mensagem.equals(mensagemNova)) {
        throw new IOException("O programa não conseguiu reconstruir os dados!");
    }
}

In fact, depending on the granularity of the Command pattern classes, you would not even need Enum to tell the method.

Finally, when you receive a message, simply use the instanceof operator to check what type the message is. Example:

if (mensagem instanceof MensagemGravarPessoa) {
    //...
}
    
24.02.2016 / 04:58