Send objects via socket

Asked

Viewed 7,594 times

5

I have a chat that exchanges messages (String) to each other. Everything is working correctly. However, now I want to start sending objects via Socket, like for example, a class that has some attributes set by me (e.g., name, IP, host name, time, message, etc).

My question is this::

When I only use String, to receive the data, I do as follows:

  Scanner s = new Scanner(servidor.getInputStream());
        while (s.hasNext()) {
            cliente.writeHistorico(s.nextLine());
        }

And to send:

  PrintStream ps = new PrintStream(socket.getOutputStream());
  ps.println("mensagem aqui);

PS: Remembering that they are always inside threads and in LOOP. Until then, no secret. However, when I will do to read an object:

        readObject = new ObjectInputStream(input);
        User s = (User) readObject.readObject();

How do I check whether or not the client sent data to the server? Since that way, I don’t have for example, a "hasNext()". 'Cause if I leave one while(true), with the instance of ObjectInputStream outside, it doesn’t work, and if you leave it inside the loop it will be creating several instances. Is there any way to do this? I think I rolled around a little bit, but that’s it. I’ve looked for material on the net and all possible examples show without using loop with ONE connected client, even in sockets documentation shows only how to exchange messages (String) and only one user..

EDITED:

I made a very basic example. Server object reading class:

    public class ThreadRecebedor extends Thread implements Runnable {

    InputStream inputCliente;
    ObjectInputStream input;
    User user;

    public ThreadRecebedor(InputStream inputCliente) {
        this.inputCliente = inputCliente;
    }

    @Override
    public void run() {
        try {
            //while (true) {
                input = new ObjectInputStream(inputCliente);
                Object aux = input.readObject();
                if (aux instanceof User) {
                    user = (User) aux;
                    System.out.println(user.getNome());
                    System.out.println(user.getMsg());
                    System.out.println(user.getHora());
                }
              //  input.close();
           // }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException ex) {
            Logger.getLogger(ThreadRecebedor.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

Class to send data:

       public class cliente {

  public static void main(String[] args) {
        try {
            Socket cliente = new Socket("192.168.1.7", 1412);
            User user = new User();
            user.setNome("Teste");
            user.setMsg("oi cristiano");
            user.setHora(new SimpleDateFormat("HH:mm:ss").format(new Date()));

            ObjectOutputStream output = new ObjectOutputStream(cliente.getOutputStream());
            output.writeObject(user);
            output.flush();
            output.reset();
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }
}

The way the two classes are, it is working. The server can read the sent object and shows the properties in the console. But now comes the key point: What if I want to pass this data to the user himself? That way, his socket will be closed. What if I wanted to listen to this client for other objects? How would I do it? I swear I read your comment about 3x, but I could not understand how to do it I want. I’m sorry for the ignorance.

2 answers

5


In theory, it is perfectly possible to write and read several serialized objects in the same stream. In practice, I remember having run into a number of problems in the past, and I do not remember them (nor their solutions). Personally, I would serialize these objects in another format (say, XML or JSON) and pass them in the same text format, simplifying their life.

That said, here are some suggestions (alternatives) if you still want to do it this way:

  • Place your objects in a Object[], and send that (simple, but only if the side writer already knows its amount before sending).

  • Before sending your objects, send a number stating how many objects will be sent (same).

  • Use a special object to indicate "end of transmission"; continue reading until you find that object. You would, of course, have to check the type of this object before doing cast for the guy User.

    readObject = new ObjectInputStream(input);
    boolean acabou = false;
    do {
        Object lido = readObject.readObject();
        if ( lido instanceof User ) {
            User s = (User)lido;
            // Faz alguma coisa com o User s (ex.: adicione-o a uma lista)
        }
        else if ( lido instanceof FimTransmissao )
            acabou = true;
    } while(!acabou);
    // Opcional: input.close();
    
  • Try reading, and if you find an exception (usually EOF - indicating end of stream) stop reading. Adapted code of this post on the oracle.com:

    readObject = new ObjectInputStream(input);
    int quantosObjetos = 0;
    boolean chegouAoFim = false;
    while (!chegouAoFim) {
        User s = null;
        try { 
            s = (User) readObject.readObject();
        }
        catch (EOFException eofe){
            chegouAoFim = true;
        }
        catch (OptionalDataException ode) {
            chegouAoFim = ode.eof; 
        }
    
        if (s != null) {
            quantosObjetos++;
            // Faz alguma coisa com seu User s (ex.: adicione-o a uma lista)
        }
    }
    readObject.close();
    
    System.out.println("Terminou. Eu li " + quantosObjetos + " objetos.");
    

    That way, just the writer send more and more objects via writeObject (using the same ObjectOutputStream, of course) and - when finished - simply close the stream. The downside of this option, as you can see, is the need to close the stream at the end; if you want to send N objects but keep the stream open for other things, this option does not apply.

Personally, I’d go with option three, but that’s subjective. Any of the four approaches should work well, choose the one you feel most comfortable with.


Updating: complete (simplified) example, addressing the issues of multiple clients, multiple objects per client, and bi-directional communication:

Java server.

ServerSocket serverSocket = new ServerSocket(1412);
while ( true )
    new ThreadRecebedor(serverSocket.accept()).start(); // Múltiplos clients por server

Threadreceiver.java.

ObjectInputStream input;   // comunicação
ObjectOutputStream output; // bidirecional
User user;

public ThreadRecebedor(Socket socketCliente) {
    input = new ObjectInputStream(socketCliente.getInputStream());
    output = new ObjectOutputStream(socketCliente.getOutputStream());
}

public void run() {
    while ( true ) {
        Object lido = input.readObject();
        if ( lido instanceof FimTransmissao )
            break;
        if ( lido instanceof User ) {
            user = (User)lido;
            output.writeUTF("Você me mandou:");
            output.flush(); // Nota: não sei se isso é mesmo necessário
            output.reset(); //       mas mal não deve fazer...
            output.writeObject(user);
            output.flush();
            output.reset();
        }
        ...
    }
}

Java client.

    Socket cliente = new Socket("192.168.1.7", 1412);
    User user = new User();
    user.setNome("Teste");
    user.setMsg("oi cristiano");
    user.setHora(new SimpleDateFormat("HH:mm:ss").format(new Date()));

    ObjectOutputStream input = new ObjectInputStream(cliente.getInputStream());
    ObjectOutputStream output = new ObjectOutputStream(cliente.getOutputStream());

    // Envia o usuário
    output.writeObject(user);
    output.flush();
    output.reset();

    // Lê a resposta
    String resposta = input.readUTF();
    User u = (User).input.readObject();
    if ( !user.equals(u) )
        System.out.println("Servidor recebeu dados errados");

    // Envia mais objetos/Recebe mais respostas
    ...

    // Fim
    output.writeObject(new FimTransmissao());
    output.flush();
    output.reset();
    output.close();

This is an example quite a lot Simplified: the server only reads objects and responds to their type, and the client sends things to them in a linear way. In practice, it would be more interesting to establish a protocol - for example sending a string (writeUTF) with a commando to be executed remotely, and then one or more objects like parameters. Or better yet: use the design pattern State. And for really bi-directional communication - where both the client and the server can start a message - create two threads instead of one, each with an infinite loop, one of which is responsible for reading and the other for writing. Managing this is complicated, and there are performance considerations involved, being too broad to address in a single question. But I leave the suggestion as a starting point.

  • The problem is that I want to be "listening to the object arrive" and then bump into what I told you, where to instantiate? Where to proceed? I did a very basic test, I’ll post along with the code up there.

  • 1

    @Cristianobombazar I updated my answer according to what I could understand of your doubts. I hope I’ve helped, but it’s worth pointing out that writing a client/server protocol is quite complicated - subject for a whole book chapter - so I gave a super-simple example. You will probably run into more difficulties in the future, so I suggest opening up new questions every time you grab something. Also consider investigating existing client/server frameworks so as not to reinvent the wheel (but if your goal is just to learn, then it’s okay).

  • Yeah, I want to do more for learning anyway. Now it’s late, tomorrow I’ll check better what you wrote. Thanks for the help.

3

Your problem lies in controlling your stream communication.

Your initial method of reading string and reading from stream via nextLine is actually a very simple and widely used flow control implementation, which makes use of a set of specific bytes as load delimiters (or payload). Thus:

inserir a descrição da imagem aqui

Which translates as follows::

  • Accumulate all incoming bytes via Socket.
  • When finding the bytes 0x0D 0x0A (CR+LF):
    • Remove all bytes present in the buffer up to the CR+LF marker;
    • Return removed bytes from buffer as a string.

The suggestion of @mgibsonbr is excellent: A serialization of content on XML or JSON would allow you to continue using CRL+LF as late Marker for definition of payload (as long as you obviously prevent your packages from owning CR+LF in their contents.

Another control method, more suitable for binary content, is the Early Marker. Instead of waiting for an eventual control sequence, this method informs the client the amount of bytes to be sent and that characterize a payload. So:

inserir a descrição da imagem aqui

In this example, the protocol would be the following:

  • Get two bytes. Convert them to Integer will indicate the size of payload that will be sent.

  • Accumulate all incoming bytes via Socket.

  • Wait until the size of the contents in the buffer is equal to the value specified by Early Marker. There,
    • remove the number of bytes indicated in Early Marker from the buffer;
    • Return removed bytes as an array.
  • Wait two bytes, and repeat the process.

This is the case, for example, of the HTTP protocol - using the header content-size to indicate how many bytes make up the payload, something especially useful in content multi-part.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.