简单的 P2P 服务器,对象从服务器中继后损坏

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

正在使用

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

}

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

此 P2P Server/Client 应该让 1 个客户端向服务器发送消息,然后将该消息转发给另一个客户端。当一条消息从一个客户端发送时,服务器输出[Server] P2 sent 'hello',但是在将消息中继到另一个客户端后,另一个客户端得到这个异常:

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.run(Client.java:25)
    at java.lang.Thread.run(Unknown Source)

第 25 行是 Object o = ois.readObject();

对象从服务器转发后如何损坏? 我能做些什么来防止对象被损坏?

基本上问题是,您不应该使用相同的底层 OutputStream 创建两个 ObjectOutputStream-s。您应该只为每个套接字创建一个对象输出流,然后重复使用它。

所以错误是由行 ObjectOutputStream p1out = ..ObjectOutputStream p2out = ...

引起的

AC 代码来自 "serialzation header"。如果我们看一下构造函数 public ObjectOutputStream(OutputStream out) :

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

并且writeStreamHeader

/**
 * 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);
}

所以如果创建了两个object-output-streams,header会被写入两次,导致客户端反序列化错误


服务器的略微修改版本(出于说明目的,它以不同方式处理断开连接)。

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