자바

[JAVA] 객체 직렬화를 통한 네트워크 전송

planting grass 2025. 4. 23. 18:19
728x90

들어가기 앞서

객체 직렬화와 네트워크 전송에 대해 이해하려면 소켓 통신에 대한 이해가 필요하다.

만약 소켓 통신에 대해 모른다면 아래 글을 참고하면 된다.

https://lold2424.tistory.com/245

 

[JAVA] 소켓 통신의 기본 개념과 구조

Java에서는 네트워크 통신이 가능하도록 java.net 패키지를 통해 TCP/IP 기반 소켓 통신 기능을 제공한다.이번 글에서 TCP 기반의 소켓과 서버소켓을 사용한 1:1 구조를 알아보도록 하겠다.소켓 통신이

lold2424.tistory.com

직렬화

객체(Object)의 상태를 바이트 형태로 변환하여 전송하거나 저장할 수 있게 하는 기술을 직렬화라 한다.

반대의 과정을 역직렬화라 하며 바이트 데이터를 다시 객체로 복원한다.

직렬화된 바이트 데이터는 파일 저장, DB 저장, 메모리 저장 등 다양한 방식에 사용할 수 있다.

왜 필요할까?

일반적으로 프로그램 간 데이터를 주고받을 경우 문자열, 숫자와 같은 기본 데이터 타입 또는 JSON 문자열을 사용한다.

네트워크는 바이트 단위로 데이터를 전송한다.

때문에, 기본 데이터 타입이나 JSON 문자열의 경우에는 별 문제가 없으나 개발자가 직접 만든 사용자 정의 객체(클래스)를 통째로 주고받을 수 없기 때문에 직렬화가 이때 사용된다.

직렬화의 종류

1. Serializable 인터페이스

public class User implements Serializable {
    private String name;
    private int age;
}

Serializable마커 인터페이스(Marker Interface)다.

마커 인터페이스는 메서드가 없고, "이 객체는 직렬화 가능하다"는 의사 표시 역할을 한다고 생각하면 된다.

JVM은 이 인터페이스를 통해 직렬화 가능 여부를 판단한다.

구현하지 않으면 NotSerializableException 예외가 발생하기 때문에 반드시 구현해야 한다.

2. ObjectOutputStream / ObjectInputStream

직렬화와 역직렬화를 위한 입출력 클래스다.

직렬화 (쓰기)

ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
out.writeObject(user);  // 객체 → 바이트

역직렬화 (읽기)

ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
User user = (User) in.readObject();  // 바이트 → 객체

내부적으로 클래스 이름, 필드 값, 버전 정보 등을 포함한 바이트로 변환된다.

역직렬화 시 해당 클래스가 JVM에 존재하고 구조가 같아야 정상적으로 복원된다.

3. serialVersionUID 필드

private static final long serialVersionUID = 1L;

클래스 구조가 변경되었는지 판별하는 버전 식별자다.

클래스 구조(필드, 메서드 등)를 바꾸면 JVM이 자동으로 새 UID를 생성하지만, 자동 생성된 값이 다르면 역직렬화가 되지 않는다.

따라서, 직접 명시하는 것이 좋다.

버전 호환을 유지해주기 때문에 서버-클라이언트 간의 클래스가 일치하는것을 보장해준다.

4. transient 키워드

private transient String password;

transient는 직렬화 대상에서 제외하겠다는 의미를 가진다.

보안 데이터, 임시 캐시 등 민감하거나 재생성 가능한 정보에 사용한다.

실제 네트워크 전송 흐름

직렬화 과정 (클라이언트)

Message msg = new Message("Alice", "Hello Server!");
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
out.writeObject(msg);

객체는 JVM의 ObjectOutputStream에 의해 바이트로 변환된다.

내부적으로 클래스 이름, 필드 타입, 값 등의 메타 정보 포함된다.

역직렬화 과정 (서버)

ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Message received = (Message) in.readObject();

전송된 바이트를 JVM이 해석하여 객체로 재구성한다.

동일한 클래스가 메모리에 로딩되어 있어야 한다.

serialVersionUID가 일치해야 한다.

JVM 내부 직렬화 흐름

직렬화 시 JVM이 아래 순서대로 동작한다.

  1. 객체가 Serializable을 구현했는지 확인
  2. 내부 클래스 정보(ObjectStreamClass) 생성
  3. 직렬화 가능한 필드를 수집 (transient 제외)
  4. ObjectOutputStream에 필드 값을 순차적으로 기록
  5. 바이트 스트림 생성 후 전송 또는 저장

코드 예시

사용자 정의 객체

import java.io.Serializable;

public class Person implements Serializable {
    private static final long serialVersionUID = 100L;
    private String name;
    private int age;

    public Person(String name, int age) { this.name = name; this.age = age; }

    public String toString() { return name + " (" + age + ")"; }
}

서버 코드

import java.io.*;
import java.net.*;

public class ObjectServer {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ServerSocket server = new ServerSocket(8888);
        System.out.println("[서버] 연결 대기 중...");

        Socket socket = server.accept();
        ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
        Person p = (Person) in.readObject();
        System.out.println("[서버] 수신 객체: " + p);

        socket.close();
        server.close();
    }
}

클라이언트 코드

import java.io.*;
import java.net.*;

public class ObjectClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 8888);
        ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());

        Person p = new Person("홍길동", 30);
        out.writeObject(p);

        socket.close();
    }
}

직렬화의 문제점과 단점

직렬화는 간단하고 빠르면서 객체 전송에 유용하지만 여러 한계와 위험성을 갖고 있기에 반드시 인지하고 있어야 한다.

1. 보안 취약점

  • 자바 직렬화는 클래스 이름, 구조, 필드 정보까지 포함한 바이너리 형식이다.
  • 역직렬화 시 의도치 않은 클래스가 실행될 수 있어 보안 위협이 발생할 수 있다.
  • 예) CVE-2015-4852

2. 버전 호환성 문제

  • 클래스 구조가 조금만 바뀌어도 이전 버전과 호환되지 않을 수 있다.
  • 특히 serialVersionUID를 잘못 관리하면 예외가 발생하게 된다.

3. 자바 환경에 종속적

  • 직렬화 자체가 자바를 위해 존재하다 보니 JVM에서만 사용이 가능하다.
  • 다른 언어나 플랫폼에서는 사용이 불가능한 자바 전용이다.

4. 데이터 포맷 비표준

  • 직렬화 데이터는 바이너리로 저장되어 사람이 읽을 수 없다.
    • JSON은 텍스트 형식으로 저장되어 사람이 읽을 수 있다.
  • 분석, 디버깅, API 연동 등에서 불리하다

5. 불필요한 정보까지 포함

  • 클래스 이름, 패키지, 메타데이터까지 저장된다.
  • 네트워크나 저장소의 자원을 그만큼 잡아먹기 때문에 부하를 줄 수 있다.
728x90