Send objects via socket

5

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

My question is:

When I use only String , to receive the data, I do it 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 within threads and in LOOP. Until then, no secret. However, when do I do to read an object:

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

How do I check if the client has sent data to the server or not? Since that way, I do not have, for example, a " hasNext() ". Because if I leave a while(true) , with the instance of ObjectInputStream off, it does not work, and if left inside the loop will be creating multiple instances. Is there any way to do that? I think I screwed up a bit, but that's it. I have already looked for material on the net and all possible examples show without using a loop with UM connected client, even in the sockets documentation it shows only how to exchange messages ( String ) and only one user .. < p>

EDIT:

I made a very basic example. Class read server objects:

    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 is working. The server can read the sent object and displays the properties in the console. But now comes the key point: What if I want to pass this data on 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 figure out how to do what I want. I'm sorry for ignorance.

    
asked by anonymous 16.07.2014 / 04:21

2 answers

5

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

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

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

  • Before sending your objects, send a number telling them how many objects will be sent.

  • Use a special object to indicate "end of transmission"; continue reading until you find this object. You would, of course, have to check the type of this object before doing cast for type 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 to read, and if you encounter an exception (usually EOF - indicating end of stream) stop reading. Code adapted from this post on 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.");
    

    In this way, it is enough for the writer to send more and more objects via writeObject (using the same ObjectOutputStream , of course) and - when finished - simply close the stream. The disadvantage 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 would go with option 3, but this is subjective. Any of the four approaches should work well, choose the one with which you feel most comfortable.

Update: full (simplified) example addressing multiple client issues, multiple objects per client, and bidirectional communication:

Server.java

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

Client.java

    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 a simplified example: the server only reads objects and responds to its type, and the client is the one who sends things to him in a linear fashion. In practice, it would be more interesting to establish a protocol - for example by sending a string ( writeUTF ) with a command to be executed remotely, and then one or more objects like parameters . Or better yet: use the design pattern State . And for truly two-way communication - where both the client and the server can initiate 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.

    
16.07.2014 / 08:24
3

Your problem lies in controlling your communication stream.

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

Thattranslatesasfollows:

  • AccumulateallbytesthatarearrivingviaSocket.
  • Findingbytes0x0D0x0A(CR+LF):
    • RemoveallbytesinthebufferuntiltheCR+LFmarker;
    • Returnthebytesremovedfromthebufferasastring.

Thesuggestionfor@mgibsonbrisexcellent:SerializingcontentinXMLorJSONwouldallowyoutocontinueusingCRL+LFasamarkerforsettingpayload(aslongasyou,obviously,preventyourpackagesfromowningCR+LFintheircontents.

Anothermethodofcontrol,moreappropriateforbinarycontent,istheearlymarker.Insteadofwaitingforaneventualcontrolsequence,thismethodinformstheclientofthenumberofbytestosendandwhichcharacterizeapayload.So:

In this example, the protocol would be as follows:

  • Get two bytes. The conversion of these to Integer will indicate the size of the payload that will be sent.

  • Accumulate all bytes that are arriving via Socket.

  • Wait until the content in the buffer is equal to the value indicated by the early marker. There,
    • remove the number of bytes indicated in the early marker of the buffer;
    • Return the removed bytes as an array.
  • Wait two bytes, and repeat the process.

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

    
18.07.2014 / 07:51