//Java Serializatoin 이란?
자바 객체를 저장 또는 전송하기 위하여 자바 객체의 코드를 다시 복원가능한 형태의 Stream으로 직렬화 시켜주는 것을 말함!!
기존예제 테스트 및 압축만!!
-Swrite.java-
//Java Serializatoin 이란?
//자바 객체를 저장 또는 전송하기 위하여 자바 객체의 코드를
//다시 복원가능한 형태의 Stream으로 직렬화 시켜주는 것을 말함!!
package serializable;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.Date;
public class swrite {
public static void main(String args[])
{
try {
//파일이나 데이터를 출력하기위한 출력스트림을 생성한다.
//tmp라는 파일을 갖는 f라는 객체 생성
FileOutputStream f = new FileOutputStream("tmp");
//기본데이터 타입의 데이터를 쓰는 객체 s 생성
ObjectOutput s = new ObjectOutputStream(f);
//Object타입의 객체를 쓴다. 인자는 문자열 타입의 "Today"
s.writeObject("Today");
//데이터 타입을 객체로 쓴다.
s.writeObject(new Date());
//스트림을 보내다. 메모리해제!! 및 tmp 파일로 생성됨
s.flush();
}
catch (IOException e) {
}
//투데이라는 문자열 출력
System.out.println("Today");
//현재의 날짜를 출력
System.out.println(new Date());
}
}//class
위의 프로그램은 File Stream을 열어서 tmp라는 파일에 2개의 객체("Today"라는 String 객체와 Date 객체)를 저장하고 있다.
여기서 객체저장을 위해 writeObject라는 메쏘드가 사용되고 있다는 것을 알 수 있다.
위의 프로그램을 실행하면 2 객체가 시리얼라이제이션이 일어나서 다시 복원가능한 형태로되어 직렬화되어
tmp라는 파일로 저장된다.
객체를 파일로 저장하다니? 얼마나 놀라운가?
(제대로 다시 복원만 된다면...)
자! 그럼 tmp 파일에서 객체를 살려보자!!!
-Sread.java-
package serializable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.util.Date;
public class sread {
public static void main(String args[]) {
try {
// 파일 입력을 받기위한 파일 입력 스트림 in 객체 생성
// 인자는 파일 또는 "tmp"문자열 사용 가능
FileInputStream in = new FileInputStream("tmp");
// 입력받은 in객체를 입력을 받기위한 객체 s로 만듬
ObjectInput s = new ObjectInputStream(in);
// 읽어들인 객체를 문자열 today에 저장
String today = (String) s.readObject();
// 읽어들인 객체를 Date 타입의 date객체로 저장
Date date = (Date) s.readObject();
System.out.println(today);
System.out.println(date);
} catch (IOException e) {
} catch (ClassNotFoundException e) {
}
}
}// class
-Test.java-
package serializable;
public class Test {
//전역 문자열 변수 str
public String str;
//transient
//나중에 다시 사용하기 위한 저장을 하지 않는,
//짧고 제한된 수명을 갖는 소프트웨어 객체이다.
//전력선을 통해 오직 몇 분의 1초 동안 지속되는
//갑작스러운 여분의 전압 펄스가 들어오는 것으로서,
//컴퓨터나 파일 등에 손상을 입힐 수 있다.
public transient int ivalue;
public Test(String s, int i) {
this.str = s;
this.ivalue = i;
}
}//class
당연히 java.io.Serializable 인터페이스를 implements하고 있어야 한다.
위에서 Test 클래스에 멤버변수로 String 타입의 str과 int 타입의 ivalue가 있음을 볼 수 있다.
그런데 이기서 transient 키워드에 주목하자!
transient 키워드는 앞에 지정하면 지정된 항목 내용은
자바 시리얼라이제이션에서 제외된다.
즉, 직렬화가 이루어질때 사용자가 문제의 소지가 있는 변수나 메쏘드를 제외시킬 수 있도록 해주는 것이다.
그럼 Test 클래스를 직렬화하여 파일로 저장해보자!
-Write.java-
package serializable;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class write {
public static void main(String args[]) {
try {
//file.out이란 이름으로 파일 fos 객체 스트림을 생성
FileOutputStream fos = new FileOutputStream("file.out");
//출력스트림 객체 생성
ObjectOutputStream oos = new ObjectOutputStream(fos);
//태스트클래스를 이용하여 문자열, 숫자 타입의 객체를 인자로 넘겨 객체 생성
oos.writeObject(new Test("testing", 37));
//메모리 해제 및 파일 생성
oos.flush();
//스트림을 사용하였으므로ㅌ 스트림을 닫는다.
fos.close();
}
catch (Throwable e){
//출력 결과들 출력
System.err.println(e);
}
}
}//class
위의 소스는 Test 객체를 file.out이라는 이름의 파일로 저장할 것이다.
역시 writeObject를 사용했고, 초기 값으로 "testing" 이라는 문자열과 37의 값을 지정했다.
자! 그럼 file.out 파일에서 Test 객체로 복원시켜 보자!
-Read.java-
package serializable;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class read {
public static void main(String args[]) {
//테스트 클래스 타입의 객체 생성
Test testobj = null;
try {
//file.out이란 파일을 읽어 입력스트림으로 변환
FileInputStream fis = new FileInputStream("file.out");
//파입입력스트림을 객체입력스트림 객체로 생성
ObjectInputStream ois = new ObjectInputStream(fis);
//읽어들인 객체를 테스트타입크랠스 객체로 저장
testobj = (Test) ois.readObject();
//파일 입력이 완료되었으므로 닫는다.
fis.close();
}
catch (Throwable e){
e.printStackTrace();
//System.err.println(e);
}
//변환된 스트림을 출력하기 위해 만들어진 객체 타입의 필드값을 출력
System.out.println(testobj.str);
System.out.println(testobj.ivalue);
}//method
}//class
-MyObject.java-
package serializable;
import java.io.Serializable;
public class MyObject implements Serializable {
//객체 생성
String name;
int count;
//기본 생성자
MyObject() {
setName();
}
//setter
public void setName() {
count++;
name = "MyObject " + count;
}
//이름을 반환하는 toString메소드 재정의
public String toString() {
return name;
}
}
역시... Serializable 인터페이스를 implements하고 있으며... 변수로 name과 count를 가지고 있다.
컨스트럭터에서 setName() 메쏘드를 호출하여 count를 증가시키고 name의 String에도 숫자가 추가되고 있다.
그러면 위의 객체를 스트림을 통하여 소켓으로 전송하기위하여 먼저 서버를 만들어보자!
-JabberServer.java-
package serializable;
import java.io.DataInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class JabberServer {
//포트 설정
static final int port = 8080;
public static void main(String[] args) {
try {
//빈 객체 생성
MyObject o = new MyObject();
//서버소켓 생성
ServerSocket s = new ServerSocket(port);
//생성된 서버 출력
System.out.println("Server Started: " + s);
//서버소켓에 접속이 이루어졌을 때 소켓을 생성
Socket socket = s.accept();
//접속한 클라이언트의 상태를 출력
System.out.println("Connection accepted, socket: " + socket);
// 접속한 클라이언트의 출력스트림을 갖는 출력 스트림 객체를 생성
ObjectOutputStream ToClient =
new ObjectOutputStream(
socket.getOutputStream());
//소켓의 입력스트림을 받는 데이터 입력스트림 객체를 생성
DataInputStream FromClient =
new DataInputStream(
socket.getInputStream());
//오브젝트객체의 카운트가 10이될 때까지 반복
while (o.count<11) {
//오브젝트 객체를 사용한 횟수 출력
System.out.println("writing " + o);
//이름 설정
o.setName();
/**
Object reference를 reset한다.
(이 부분이 포인트)
이것을 안하면 첫번째 전송한 객체의
reference로 계속 전송된다.
그래서 갱신된 data가 반영되지 못하는
현상이 생긴다.
*/
//서버를 리셋
ToClient.reset();
//빈객체를 클라이언트객체에 쓴다.
ToClient.writeObject(o);
System.out.print("trying to received acknowledgement ... ");
//클라이언트에서 읽은 인트 값을 출력한다.
System.out.println("acknowledgement: " +
FromClient.readInt());
System.out.println("succeeded");
}
System.out.println("closing...");
//클라이언트의 연결을닫는다.
ToClient.close();
//서버 소켓을 닫는다.
socket.close();
}
catch(Exception e){
e.printStackTrace();
}
}
}
위 서버 프로그램에서는 서버 소켓을 8080 포트로 열어놓고 클라언트를 기다리다가 클라이언트가 억셉트되면 계속 동작하게 되어있다.
계속 설명하기 전에... 클라이언트 프로그램도 같이 보도록 하자!
-JabberClient.java-
package serializable;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.ObjectInputStream;
import java.net.InetAddress;
import java.net.Socket;
public class JabberClient {
static final int port = 8080;
public static void main(String args[]) {
MyObject o;
try
{
//아이피 객체 생성, 이름은 없다~
InetAddress addr = InetAddress.getByName(null);
//얻어진 아이피 주소를 출력함.
System.out.println("addr = " + addr);
//네트워크를 사용할 소켓 생성, 아이피 주소와 포트번호로 한다
Socket socket = new Socket(addr, port);
System.out.println("socket = " + socket);
//객체를 입력할 입력스트림 생성,
ObjectInputStream FromServer = new
ObjectInputStream(socket.getInputStream());
//서버로 전송할 데이터출력 스트림을 생성
DataOutputStream ToServer = new
DataOutputStream(socket.getOutputStream());
//카운트할 변수 선언
int i=0;
while(true){
//빈 객체에 서버로부터 읽어온 객체를 입력
o = (MyObject)FromServer.readObject();
System.out.print("trying to send acknowledgement ... ");
//스레드 생성, 대기시간은 0.5초
Thread.sleep(500);
//출력스트림에 증가된 i값을 증가시켜서 보내줌
ToServer.writeInt(i++);
System.out.println("succeeded");
System.out.println(o);
}
}
catch (EOFException f){
System.exit(0);
}
catch(Exception e){
e.printStackTrace();
}
}
}//class
소켓이 맺어진 후에...
서버에서 MyObject 객체를 생성하면 컨스트럭터에의해 카운트가 1이 된다.
그후 setName() 메쏘드를 호출하고 나면 카운트가 2가 되고 이것을 스트림으로 바꾸어 객체를 전송하게 된다.
클라이언트는 전송된 객체를 받아 ACK 성공 메시지를 뿌리고 카운트 값을 찍는다.
그후 서버에서 setName() 메쏘드를 다시 호출하여 객체의 카운트 상태값을 하나 증가시켜 3으로 만든후 객체를 스트림으로 전송한다.
클라이언트는 두번째 객체를 받아 ACK 성공 메시지를 뿌리고 카운트 값을 찍는다.
이때 카운트 값이 갱신된 3값으로 찍혀야 하는데...
여전히 2로 찍혀져 나온다?
왜일까???
이것은 자바의 버그가 아니다.
이것은 자바 시리얼라이제이션에서는
처음 전송한 객체의 object reference를 인위적으로
reset시켜 주지 않으면 객체의 상태가 바뀌더라도 전송되는 객체의 object reference는 처음 전송한 객체의 object reference로 계속 가지게 되므로 이와 같은 현상이 발생한다.
이문제를 해결하려면...
객체를 스트림으로 전송하기 전에...
reset() 메쏘드를 통하여 같은 object reference를 사용하지 않도록 리셋시켜주어야 한다.
즉,
ToClient.reset(); 부분에 대한 이해가 이번 강좌의 포인트이다.
이부분이 있고, 없고의 차이를 반드시 이해하기 바란다.
객체를 소켓으로 계속 전송하게 될 경우...
실수하면 위와같은 버그아닌 오류에 봉착하기 쉽다.
'JAVA' 카테고리의 다른 글
overriding 실습 (ex:할아버지/아버지/아들) (0) | 2009.11.21 |
---|---|
자바 중간고사 정리 (0) | 2009.11.21 |
자바의 기본 타입 (0) | 2009.11.06 |
자바 기초 문법 (0) | 2009.11.06 |
2009/01/05 RealChoky 어록 (0) | 2009.11.05 |