javasocketsp2pobjectinputstreamobjectoutputstream

Simple P2P server, object gets corrupted after being relayed from the server


Using

public class Server {
    private static ServerSocket server;
    private static int port = 9876;
    private static Socket p1 = null;
    private static Socket p2 = null;

    public static void main(String args[]) {
        System.out.println("[Server] Attempting to bind port " + port);
        try {
            server = new ServerSocket(port);
        } catch (IOException err) {
            err.printStackTrace();
            System.exit(1);
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(p1 == null) {
                    try {
                        System.out.println("[Server] Waiting for P1 to connect");
                        p1 = server.accept();
                        System.out.println("[Server] P1 has connected");
                        ObjectInputStream ois = new ObjectInputStream(p1.getInputStream());
                        ObjectOutputStream oos = new ObjectOutputStream(p1.getOutputStream());
                        while (p1.isConnected()) {
                            String message = (String) ois.readObject();
                            System.out.println("[Server] P1 sent '" + message + "'");
                            if (p2.isConnected()) {
                                ObjectOutputStream p2out = new ObjectOutputStream(p2.getOutputStream());
                                p2out.writeObject("[P1] " + message);
                            } else {
                                oos.writeObject("[Server] P2 is not connected");
                            }
                        }
                        System.out.println("[Server] P1 has disconnected");
                        p1 = null;
                    } catch (Exception err) {
                        if(err instanceof SocketException) {
                            System.out.println("[Server] P1 has disconnected");
                            p1 = null;
                        } else {
                            err.printStackTrace();
                        }
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(p2 == null) {
                    if(p1 != null) {
                        try {
                            System.out.println("[Server] Waiting for P2 to connect");
                            p2 = server.accept();
                            System.out.println("[Server] P2 has connected");
                            ObjectInputStream ois = new ObjectInputStream(p2.getInputStream());
                            ObjectOutputStream oos = new ObjectOutputStream(p2.getOutputStream());
                            while (p2.isConnected()) {
                                String message = (String) ois.readObject();
                                System.out.println("[Server] P2 sent '" + message + "'");
                                if (p1.isConnected()) {
                                    ObjectOutputStream p1out = new ObjectOutputStream(p1.getOutputStream());
                                    p1out.writeObject("[P2] " + message);
                                } else {
                                    oos.writeObject("[Server] P1 is not connected");
                                }
                            }
                            System.out.println("[Server] P2 has disconnected");
                            p2 = null;
                        } catch (Exception err) {
                            if(err instanceof SocketException) {
                                System.out.println("[Server] P2 has disconnected");
                                p2 = null;
                            } else {
                                err.printStackTrace();
                            }
                        }
                    }
                }
            }
        }).start();
    }

}

and

public class Client {
    static ObjectOutputStream oos = null;
    static ObjectInputStream ois = null;
    static int port = 9876;
    public static void main(String[] args) throws UnknownHostException, IOException, ClassNotFoundException, InterruptedException{
        InetAddress host = InetAddress.getLocalHost();
        Socket socket = new Socket(host.getHostName(), port);
        System.out.println("[Client] Connection established with port " + port);
        oos = new ObjectOutputStream(socket.getOutputStream());
        ois = new ObjectInputStream(socket.getInputStream());
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Object o = ois.readObject();
                    if(o instanceof String) {
                        String msg = (String) o;
                        System.out.println(msg);
                    }
                } catch (ClassNotFoundException | IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        Scanner s = new Scanner(System.in);
        while(true) {
            String msg = s.nextLine();
            oos.writeObject(msg);
        }
    }
}

This P2P Server/Client is supposed to have 1 client send a message to the server, then relay that message to the other client. When a message is sent from one client, the server outputs [Server] P2 sent 'hello', but after relaying the message to the other client, the other client gets this exception:

java.io.StreamCorruptedException: invalid type code: AC
    at java.io.ObjectInputStream.readObject0(Unknown Source)
    at java.io.ObjectInputStream.readObject(Unknown Source)
    at me.aj4real.tutorial.p2pChatServer.Client$1.run(Client.java:25)
    at java.lang.Thread.run(Unknown Source)

Line 25 is Object o = ois.readObject();

How does the object get corrupted after being relayed from the server? What can I do to prevent the object from being corrupted?


Solution

  • Basically the problem is, you shouldn't create TWO ObjectOutputStream-s using the same underlying OutputStream. You should only create one object-output-stream for each socket, and reuse it.

    So the error is caused by rows ObjectOutputStream p1out = .. and ObjectOutputStream p2out = ...

    The AC code comes from a "serialzation header". If we look at the constructor public ObjectOutputStream(OutputStream out) :

    public ObjectOutputStream(OutputStream out) throws IOException {
        ...
        writeStreamHeader();
        ...
    }
    

    and writeStreamHeader is

    /**
     * Magic number that is written to the stream header.
     */
    final static short STREAM_MAGIC = (short)0xaced;
    ...
    protected void writeStreamHeader() throws IOException {
        bout.writeShort(STREAM_MAGIC);
        bout.writeShort(STREAM_VERSION);
    }
    

    So if two object-output-streams are created, the header will be written twice, causing the de-serialization error on the client side.


    A slightly modified version of the Server (it handles disconnection differently, for illustration purpose).

    package clientserver;
    
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class Server {
      private static ServerSocket server;
      private static int port = 9876;
      private static Socket p1 = null;
      private static Socket p2 = null;
    
      static ObjectInputStream ois1, ois2;
      static ObjectOutputStream oos1, oos2;
    
      public static void main(String args[]) throws Exception {
        System.out.println("server listening on port=" + port);
        server = new ServerSocket(port);
    
        p1 = server.accept();
        System.out.println("[Server] P1 has connected");
        p2 = server.accept();
        System.out.println("[Server] P2 has connected");
    
        ois1 = new ObjectInputStream(p1.getInputStream());
        oos1 = new ObjectOutputStream(p1.getOutputStream());
        ois2 = new ObjectInputStream(p2.getInputStream());
        oos2 = new ObjectOutputStream(p2.getOutputStream());
    
        new Thread(new Runnable() {
          @Override
          public void run() {
            try {
              while (p1.isConnected()) {
                String message = (String) ois1.readObject();
                System.out.printf("[Server] P1 sent msg=[%s]%n", message);
                if (p2.isConnected()) {
                  ObjectOutputStream p2out = oos2;
                  p2out.writeObject("[P1] " + message);
                } else {
                  oos1.writeObject("[Server] P2 is not connected");
                }
              }
              System.out.println("[Server] P1 has disconnected");
            } catch (Exception e) {
              e.printStackTrace(System.out);
            }
          }
        }).start();
        new Thread(new Runnable() {
          @Override
          public void run() {
            try {
              while (p2.isConnected()) {
                String message = (String) ois2.readObject();
                System.out.printf("[Server] P2 sent msg=[%s]%n", message);
                if (p1.isConnected()) {
                  ObjectOutputStream p1out = oos1;
                  p1out.writeObject("[P2] " + message);
                } else {
                  oos2.writeObject("[Server] P1 is not connected");
                }
              }
              System.out.println("[Server] P2 has disconnected");
            } catch (Exception e) {
              e.printStackTrace(System.out);
            }
          }
        }).start();
      }
    }