第一个序列化程序 socket例子

本文介绍了一个简单的Socket客户端和服务端实现,用于验证序列化传送数据的功能。通过定义一个公用对象类`User`,实现了数据的序列化与反序列化过程。服务器端接收并处理客户端发送的数据,然后将处理后的信息回传给客户端。本文详细展示了如何在Java中使用`ServerSocket`和`Socket`进行通信,并通过`ObjectInputStream`和`ObjectOutputStream`进行数据的读写操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个最简单的socket的客户端和服务端,只是用来验证序列化传送数据。
第一次用序列化,记一下。
公用对象:
class User implements Serializable  {
    private static final long serialVersionUID = 1L;
        private String name;
        private String saying;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getSaying() {
            return saying;
        }

        public void setSaying(String saying) {
            this.saying = saying;
        }

}
服务器端:
public class Service {
    public static void main(String[] args) {
        try {

            ServerSocket serverSocket;
            serverSocket = new ServerSocket(8088);
            while (true) {
                Socket socket = serverSocket.accept();
                ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
                User u = (User) in.readObject();
               
                DataOutputStream outStream = new DataOutputStream(socket.getOutputStream());
                outStream.writeUTF("你好!你是?"+u.getName()+"你想说:"+u.getSaying());
                socket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
客户端
public class client {

    Socket socket = null;
    DataInputStream inStream = null;
    DataOutputStream outStream = null;

    private void waitData() {
        System.out.println("waitData");

        String acceptStr;
        try {
            acceptStr = inStream.readUTF();
            System.out.println( acceptStr);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void send(User l) {
        System.out.println("send");
        try {
            ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
            os.writeObject(l);
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
       
    }

    private void init() throws UnknownHostException, IOException {
        System.out.println("init");
        socket = new Socket("127.0.0.1", 8088);
        inStream = new DataInputStream(socket.getInputStream());
        outStream = new DataOutputStream(socket.getOutputStream());
    }

    public static void main(String[] args) {
        client client = new client();
        try {
            while (true) {
                System.out.print("姓名:内容");
                BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
                String s = in.readLine();
                String[] lis = s.split(":");
                User u = new User();
                u.setName(lis[0]);
                u.setSaying(lis[1]);
                client.init();
                client.send(u);
                client.waitData();
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

}
客户端输入 wty:faaaa 输出 你好!你是?wty你想说faaaa
关于transient 就是对象中不想序列化的内容

这篇文章不错:http://onlylove.javaeye.com/blog/161034

<think>我们正在讨论序列化与反序列化的实现原理。根据引用内容,序列化是将对象转换为字节流的过程,而反序列化则是将字节流恢复为对象的过程。下面我将详细解释其工作机制。 序列化(Serialization)的底层实现原理: 1. 当对一个对象进行序列化时,Java虚拟机会检查该对象是否实现了java.io.Serializable接口。如果没有实现,则抛出NotSerializableException。 2. 序列化过程通过ObjectOutputStream实现。它通过反射机制获取对象的类元数据,包括类名、字段、方法等。 3. 对于每个对象,ObjectOutputStream会先写入一个标识符(TC_OBJECT)和类描述符(包括类名、序列化版本ID等)。 4. 然后递归地写入该对象的非静态和非瞬态字段的值。对于引用类型字段,会递归地序列化整个对象图(即引用的对象也会被序列化)。 5. 在序列化过程中,如果对象定义了writeObject方法,则调用该方法进行自定义序列化(引用[5]中提到)。 反序列化(Deserialization)的底层实现原理: 1. 反序列化通过ObjectInputStream实现。它从字节流中读取类描述符,并根据描述符创建对应的类(如果本地存在该类)。 2. 然后,ObjectInputStream会创建一个新的对象实例(不调用构造函数),并通过反射将字节流中的字段值填充到新对象的对应字段中。 3. 对于引用类型的字段,反序列化过程会递归地反序列化整个对象图,恢复原始对象之间的引用关系。 4. 如果对象定义了readObject方法,则调用该方法进行自定义反序列化(引用[5]中提到)。 注意:序列化机制使用一个序列化版本ID(serialVersionUID)来验证版本一致性。如果反序列化时发现本地类的版本ID与字节流中的不一致,会抛出InvalidClassException。 下面我们通过一个简单的代码示例来展示序列化和反序列化的过程: 首先定义一个可序列化的类: ```java import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; private String name; private transient int age; // transient关键字标记的字段不会被序列化 public Person(String name, int age) { this.name = name; this.age = age; } // 自定义序列化方法 private void writeObject(java.io.ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // 默认序列化非transient字段 // 这里可以添加自定义序列化逻辑 } // 自定义反序列化方法 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 默认反序列化非transient字段 // 这里可以添加自定义反序列化逻辑 } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } } ``` 序列化测试: ```java public class SerializableTest { public static void main(String[] args) throws Exception { Person person = new Person("Alice", 30); // 序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser")); oos.writeObject(person); oos.close(); // 反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser")); Person deserializedPerson = (Person) ois.readObject(); ois.close(); System.out.println(deserializedPerson); // 输出:Person{name='Alice', age=0}(因为age是transient,反序列化后为0) } } ``` 在这个例子中,我们注意到: - 使用transient关键字修饰的age字段不会被序列化,因此在反序列化后,age字段的值是0(int的默认值)。 - 通过自定义writeObject和readObject方法,我们可以控制序列化和反序列化的过程(例如加密敏感数据)。 序列化机制的工作流程总结: 1. 序列化: - 检查对象是否实现Serializable接口。 - 创建ObjectOutputStream。 - 写入对象类型和类描述符。 - 递归写入对象数据(非transient和非静态字段)。 - 如果对象有自定义的writeObject方法,则调用该方法。 2. 反序列化: - 读取字节流,解析类描述符。 - 验证本地类与字节流中的类版本是否一致(通过serialVersionUID)。 - 创建对象实例(不调用构造函数)。 - 递归读取字段数据并填充到对象中。 - 如果对象有自定义的readObject方法,则调用该方法。 此外,引用[4]提到了在RPC(远程过程调用)中,序列化和反序列化需要约定相同的算法,否则会出现解析失败。例如,如果一方使用JSON序列化,另一方使用XML反序列化,则无法正确解析。 引用[5]提到了几种自定义序列化的方式: - 实现Externalizable接口(更高效,但需要手动控制所有字段)。 - 在类中定义writeObject和readObject方法(更灵活,可以控制部分字段)。 - 使用第三方库如Jackson(支持JSON等格式,且可以通过白名单机制提高安全性)。 性能与安全提示: - 序列化过程可能较慢,因为涉及反射和递归处理对象图。 - 反序列化过程可能成为安全漏洞的来源(如反序列化攻击),因此建议: - 不要反序列化不受信任的数据。 - 使用最新版本的序列化机制,并注意安全更新。 - 使用第三方库(如Jackson)并配置白名单,只允许反序列化预期的类。 最后,我们通过一个流程图来展示序列化和反序列化的过程: 序列化过程: ``` [开始] -> [检查Serializable接口] -> [创建ObjectOutputStream] -> [写入类描述符] -> [递归写入对象数据] -> [自定义writeObject?] -> [结束] ``` 反序列化过程: ``` [开始] -> [读取字节流] -> [解析类描述符] -> [检查serialVersionUID] -> [创建空对象] -> [递归读取字段数据] -> [自定义readObject?] -> [结束] ``` 希望以上内容能帮助你理解序列化和反序列化的实现原理。 §§相关问题§§ 1. 为什么Java序列化中要使用serialVersionUID?如何生成? 2. 如何避免Java反序列化漏洞? 3. 除了Java自带的序列化机制,还有哪些流行的序列化框架?它们有什么优缺点?</think>### 序列化与反序列化的实现原理及工作机制 #### 一、核心概念 1. **序列化**:将对象转换为字节流的过程(内存 → 字节流) 2. **反序列化**:将字节流恢复为对象的过程(字节流 → 内存) 3. **核心接口**:`java.io.Serializable`(标记接口,无方法) #### 二、底层实现原理 1. **字节流结构**(序列化后的二进制格式): ```plaintext | 魔数(0xACED) | 版本号 | 类描述符 | 字段数据 | 结束标记 | ``` - **类描述符**:包含类名、serialVersionUID、字段类型信息 - **字段数据**:按字段类型编码的二进制值(如int→4字节) 2. **反射机制**: - 序列化时:通过反射获取对象的所有字段值 - 反序列化时:通过反射创建新对象并填充字段值 3. **递归处理**: - 对象图遍历:序列化时会递归处理所有关联对象 - 引用处理:相同对象的多次引用只存储一次(避免循环引用) #### 三、Java原生序列化工作流程 ```mermaid graph TD A[对象实例] -->|序列化| B[ObjectOutputStream] B --> C[写入类元数据] C --> D[递归写入字段值] D --> E[生成字节流] E -->|网络/存储| F[字节流] F -->|反序列化| G[ObjectInputStream] G --> H[读取类元数据] H --> I[创建空对象] I --> J[递归填充字段值] J --> K[重建对象] ``` #### 四、关键实现细节 1. **serialVersionUID的作用**: ```java private static final long serialVersionUID = 1L; // 显式声明版本号 ``` - 版本一致性校验:反序列化时比对字节流与本地类的版本号 - 未显式声明时:JVM根据类结构自动生成(易因字段修改导致不兼容) 2. **字段处理规则**: - 序列化:所有非`transient`、非`static`字段 - 跳过字段:`transient`修饰符或`static`字段 ```java public class User implements Serializable { private String name; // 序列化 private transient int age; // 不序列化 private static String type; // 不序列化 } ``` 3. **自定义序列化**(通过特殊方法覆盖默认行为): ```java private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); // 默认序列化 out.writeUTF(encrypt(data)); // 自定义加密 } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); // 默认反序列化 this.data = decrypt(in.readUTF()); // 自定义解密 } ``` #### 五、跨语言序列化方案(如Protobuf) 1. **IDL定义**(接口描述语言): ```protobuf syntax = "proto3"; message User { string name = 1; int32 id = 2; } ``` 2. **编码特点**: - **Tag-Length-Value结构**:字段ID + 长度 + 值 - **变长整数编码**:小整数用1字节存储(Varint) - **跨平台**:生成Java/C++/Python等语言的类 3. **性能对比**: | 指标 | Java原生序列化 | Protobuf | |---------------|---------------|----------| | 字节大小 | 大(含元数据) | 小(约1/3) | | 速度 | 慢 | 快(3-5倍) | | 跨语言支持 | 仅Java | 多语言 | #### 六、安全机制 1. **反序列化漏洞防护**: ```java // 启用反序列化过滤器(JDK9+) ObjectInputStream ois = new ObjectInputStream(input); ois.setObjectInputFilter(filter); ``` - 白名单机制:只允许特定类进行反序列化 - 深度验证:限制对象图的最大深度 2. **最佳实践**: - 避免反序列化不可信数据源 - 对敏感字段使用`transient`或自定义加密 - 定期更新serialVersionUID #### 七、应用场景 1. **持久化存储**: ```java // 序列化到文件 try (ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream("user.dat"))) { oos.writeObject(user); } ``` 2. **网络传输**(RPC框架): ```java // 序列化后通过Socket发送 Socket socket = new Socket("host", port); ObjectOutputStream oos = new ObjectOutputStream( socket.getOutputStream()); oos.writeObject(request); ``` 3. **分布式缓存**:Redis等缓存系统存储序列化对象 > **性能优化建议**:高频场景使用Protobuf/Thrift等高效序列化框架[^5]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值