Java ObjectOutput/ObjectInput流是如何工作的?

Java ObjectOutput/ObjectInput流是如何工作的?,java,serialization,objectinputstream,objectoutputstream,Java,Serialization,Objectinputstream,Objectoutputstream,我正在尝试使用java制作一个客户机/服务器聊天应用程序。我对使用套接字在应用程序之间进行通信非常陌生。我决定使用ObjectInput/ObjectOutput流在客户端和服务器之间发送对象 当客户端连接到套接字时,我尝试将用户数据发送到服务器。这是代码 服务器: private void startServer(){ 试一试{ this.server=新服务器套接字(端口); this.socket=server.accept(); log(“已接受新连接!”); this.output=n

我正在尝试使用java制作一个客户机/服务器聊天应用程序。我对使用套接字在应用程序之间进行通信非常陌生。我决定使用ObjectInput/ObjectOutput流在客户端和服务器之间发送对象

当客户端连接到套接字时,我尝试将用户数据发送到服务器。这是代码

服务器:

private void startServer(){
试一试{
this.server=新服务器套接字(端口);
this.socket=server.accept();
log(“已接受新连接!”);
this.output=newObjectOutputStream(socket.getOutputStream());
this.input=newObjectInputStream(socket.getInputStream());
试一试{
User=(User)input.readObject();
ChatUtils.log(user.getDisplayName()+“(“+user.getUsername()+”)已连接!”);
}catch(classnotfounde异常){
}
}捕获(IOE异常){
e、 printStackTrace();
}
}
客户:

public void connectToServer(int端口){
试一试{
服务器=新套接字(“127.0.0.1”,端口);
this.port=端口;
this.objectOutput=新的ObjectOutputStream(server.getOutputStream());
System.out.println(“连接到端口“+端口+”!”上的服务器”;
objectOutput.writeObject(用户);
}捕获(未知后异常e){
e、 printStackTrace();
}捕获(IOE异常){
e、 printStackTrace();
}
}
一切都很好,但我想澄清一下ObjectOutputStream#writeObject()和ObjectInputStream#readObject()方法的工作原理

  • 当我写行
    User=(User)input.readObject()时,它将对象作为用户对象读取。这是否只会尝试转换从客户端的ObjectOutputStream发送的“用户”对象

  • 由于此方法只调用一次,如果我从输出流将这些对象发送到服务器,是否可以将输入流转换为其他对象?例如:
    String message=(String)input.readObject()

  • 如果我同时从输出流向服务器发送多个对象,会发生什么

  • 4) 在示例一中,我尝试读取“user”对象。如果有两个或多个对象等待读取,会发生什么情况?如何确定哪个对象是哪个对象?例:

    
    //客户
    public void connectToServer(){
    String message=“你好,服务器!”
    用户=新用户(“John Doe”、“jdoe123”);
    output.writeObject(用户);
    output.writeObject(消息);
    }
    如果有人能回答这些问题,那就太好了。非常感谢!
    
    每次调用.writeObject时,java都会获取您指定的对象并将其序列化

    这个过程是一个不成熟的,不推荐的策略

    Java将首先尝试将传递的对象分解为其组成部分。希望它能在类定义(对象所在的类,即
    theObjectWrite.getClass()返回的类)的帮助下实现这一点
    。任何
    实现Serializable的class def都声称是为此而设计的,并获得了一些额外的帮助,但如果不这样做,该机制将尝试使用反射黑客

    然后,组成部分沿着线路发送(也就是说,以对象为例,任何原语字段都可以发送;例如,ObjectOutputStream知道如何从本质上发送int。任何其他类型都通过请求该对象的类来发送)。对于每个对象,java还发送所谓的“串行版本uid”,这是一个计算出的数字,在类中任何地方任何所谓的签名发生变化时都会发生变化。它是类的包、名称、扩展的类、实现的接口以及每个字段的每个名称和类型的组合(可能还有每个方法引发的每个名称、返回类型、参数类型和异常类型)

    现在我们有了一个包,包括:

    • 类的名称(例如,
      com.foo.elliott.User
    • 类的SerialVersionId
    • 用户中的实际数据。如果用户包含任何非基本字段,则递归应用此过程
    然后这些都通过电线发送

    然后,在接收时,接收代码将获取所有这些内容并将其打包回用户对象。这将失败,除非接收端在类路径上实际具有
    com.foo.elliott.User
    ,并且def具有相同的串行版本UID

    换句话说,如果您更新了这个类,除非“另一端”也更新了,否则传输将失败

    您可以通过显式声明serialVersionUID来手动处理这些内容,但是请注意,例如,任何创建的字段最终都是空的,即使构造函数通常会确保它们永远不会是空的

    您还可以通过覆盖某些特定的“巫毒”方法(一种具有特定名称的方法。Java通常不是结构类型化的,但这些过去25年的遗迹,例如
    psv main
    和这些方法,是所有Java中唯一的结构类型化的东西)来完全手动管理所有这些

    此外,该数据的二进制格式或多或少是“封闭”的,不明显,不容易解码,并且几乎没有库存在

    因此,结果是:

    • 这是一个挑剔、充满错误的过程
    • 更新您序列化的任何内容都是一件麻烦事
    • 除了java之外,你不可能用任何编程语言阅读这个有线协议
    • 这种格式既不容易阅读,也不容易使用,也不特别紧凑
    这导致了不可避免的结论:不要使用
    ObjectOutputStream

    相反,使用ot
    try (OutputStream out = socket.getOutputStream()) { .. do stuff here .. }
    
    catch (ThingICantHandleException e) {
        throw new RuntimeException("unhandled", e);
    }