자바 스트림 클래스 사용시 발생하는 오류해결 예
자바 네트워크 프로그래밍에서 자주 사용되는 ObjectInputStream 에서 발생하는 다음과 같은 스트림 오류를 해결한 예이다.
java.io.StreamCorruptedException: invalid type code:
java.io.StreamCorruptedException: invalid stream header:
결과적으로 아래의 코드에서 발생하는 에러 중 invalid type code: 는 스트림을 새로 생성하는 방법으로 해결할 수 있었고, invalid stream header: 에러는 새로 생성한 스트림을 사용하는 것이 아니라 이미 생성된 스트림의 참조를 다른 곳으로 전달하여 계속 재사용하는 방법으로 해결할 수 있었다.
네트워크 상에서 이들 오류를 방지하려면 클라이언트와 서버간에 연결되는 입출력 스트림은 반드시 1대1로 연결되어야 한다는 것이다. 좀더 구체적으로 설명하면, new ObjectOutputStream ( socket.getOutputStream() ) 을 사용하여 출력스트림을 생성했다면 이 스트림에 연결되는 네트워크 상대편에서도 단 한번의 new ObjectInputStream ( socket.getInputStream() ) 으로 연결해야 한다는 것이며, 한개의 출력스트림 생성에 다수개의 입력스트림을 생성하여 사용하려는 경우에는 입력스트림 측에서 invalid stream header 오류가 발생한다. 반대로 한개의 입력스트림에 연결되는 네트워크 상대편에서 다수개의 출력스트림을 생성하여 사용하려는 경우에는 입력스트림 측에서 invalid type code 오류가 발생하게 된다
이 문제를 해결하려면 한번 생성된 스트림은 프로그램 내에서 계속 재사용해야 하고, 어쩔 수 없이 출력스트림을 다시 생성해야 한다면 이에 대응하는 입력스트림도 새로 생성하여 입출력 스트림의 1:1 관계를 유지해 주어야 한다
또 한가지 네트워크상의 오브젝트 스트림을 사용할 때 주의할 점은 ObjectInputStream의 생성자는 연결된 서버의 ObjectOutputStream의 생성자가 실행되기 전까지는 블로킹 상태로 머물러 있다는 것이다. ObjectInputStream 생성자 이후의 코드가 실행되지 않는 경우가 있다면 반드시 확인해야 하며 ObjectInputStream생성자보다 ObjectOutputStream의 생성자가 먼저 실행되도록 조정해야 한다
ObjectOutputStream, ObjectInputStream의 스트림 헤더 설정 절차
서버나 클라이언트측에서 ObjectOutputStream의 생성자가 실행되면 연결된 노드 스트림에 serialization stream header가 쓰여지며 이에 대응하는 서버나 클라이언트측의 ObjectInputStream생성자는 ObjectOutputStream의 생성자가 전달한 스트림 헤더를 받을 때까지 블로킹 상태로 대기하다가 ObjectOutputStream의 생성자로부터 정상적인 스트림 헤더를 받으면 비로소 ObjectInputStream생성이 완료되고 스트림 헤더가 정상적이지 않으면 StreamCorruptedException[invalid stream header]을 던진다. ObjectInputStream에 수신된 정상적인 스트림 헤더는 이후에 readObject()호출시 스트림의 Control Information 과 일치해야 하며 일치하지 않으면 StreamCorruptedException[invalid type code]을 던진다.
정리하자면,
1. ObjectInputStream생성자가 ObjectOutputStream생성자로부터 스트림 헤더를 받지 못하면 블로킹 상태로 무한대기 한다.
2. 이미 연결된 클라이언트와 서버의 입출력 스트림에 새로운 ObjectOutputStream을 생성하여 연결하면 ObjectInputStream.readObject()에서 [invalid type code] 오류가 발생한다.
3. 이미 연결된 클라이언트와 서버의 입출력 스트림에 새로운 ObjectInputStream을 생성하여 연결하면 ObjectInputStream의 생성자에서 [invalid stream header] 오류를 던진다
ObjectInputStream 에서 발생하는 예외의 해결
java.io.StreamCorruptedException: invalid type code: AC --> 스트림을 새로 생성하여 해결
java.io.StreamCorruptedException: invalid stream header: 7371007E --> 이미 생성된 스트림을 재사용하여 해결
ObjectInputStream의 생성자가 블로킹 상태로 대기하는 코드의 예
Server.java
package stream; import java.io.*; import java.net.*; public class Server { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(1234); //ObjectOutputStream을 생성하지 않아서 클라이언트측의 ObjectInputStream생성자자는 무한대기(블로킹)상태가 된다 while(true) { System.out.println("서버 대기중...."); Socket socket = ss.accept(); System.out.println("클라이언트 접속됨"); } } catch (Exception e) { e.printStackTrace(); } } }
Client.java ( ObjectInputStream의 생성자에서 무한대기(블로킹)하는 예 )
package stream; import java.io.*; import java.net.*; public class Client { public static void main(String[] args) { try { Socket socket = new Socket("127.0.0.1", 1234); System.out.println("클라이언트: 서버에 연결 성공"); ObjectInputStream oin = new ObjectInputStream(socket.getInputStream()); //블로킹 상태 System.out.println("클라이언트: 데이터 수신 대기중..."); Integer it = (Integer)oin.readObject(); System.out.println("클라이언트: 수신된 데이터:"+it); } catch (Exception e) { e.printStackTrace(); } System.out.println("클라이언트: 프로그램 종료...."); } }
ObjectOutputStream, ObjectInputStream이 연결된 후에 ObjectOutputStream을 새로 바꾼 경우
Server.java
package stream; import java.io.*; import java.net.*; public class Server { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(1234); while(true) { System.out.println("서버 대기중...."); Socket socket = ss.accept(); System.out.println("클라이언트 접속됨"); //ObjectOutputStream의 생성자는 스트림헤더를 클라이언트에 전송하여 클라이인트측의 ObjectInputStream과 연결된다 ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); //새로운 출력스트림을 생성하여 이미 연결된 클라이언트의 ObjectInputStream과 연결을 시도한다 out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject(new Integer(100)); out.flush(); } //ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); } catch (Exception e) { e.printStackTrace(); } } }
Client.java ( ObjectInputStream 생성자에서 수신된 스트림 헤더와 oin.readObject()에서 수신한 Control Information이 일치하지 않아서 [invalid type code]오류가 발생한 경우 )
package stream; import java.io.*; import java.net.*; public class Client { public static void main(String[] args) { try { Socket socket = new Socket("127.0.0.1", 1234); System.out.println("클라이언트: 서버에 연결 성공"); ObjectInputStream oin = new ObjectInputStream(socket.getInputStream()); System.out.println("클라이언트: 데이터 수신 대기중..."); //java.io.StreamCorruptedException: invalid type code: AC Integer it = (Integer)oin.readObject(); System.out.println("클라이언트: 수신된 데이터:"+it); } catch (Exception e) { e.printStackTrace(); } System.out.println("클라이언트: 프로그램 종료...."); } }
ObjectOutputStream, ObjectInputStream이 이미 연결된 상태에서 ObjectInputStream을 다시 생성하여 연결하려는 경우
Server.java
package stream; import java.io.*; import java.net.*; public class Server { public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(1234); //ObjectOutputStream을 생성하지 않아서 클라이언트측의 ObjectInputStream생성자자는 무한대기(블로킹)상태가 된다 while(true) { System.out.println("서버 대기중...."); Socket socket = ss.accept(); System.out.println("클라이언트 접속됨"); //ObjectOutputStream의 생성자는 스트림헤더를 클라이언트에 전송하여 클라이인트측의 ObjectInputStream과 연결된다 ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject(new Integer(100)); out.flush(); } } catch (Exception e) { e.printStackTrace(); } } }
Client.java ( 이미 연결된 상태에 있는 입출력 스트림에 새로운 ObjectInputStream을 생성하여 연결하려는 경우, ObjectInputStream의 생성자는 ObjectOutputStream 생성자로부터 스트림 헤더를 받지 못하므로 [invalid stream header] 오류를 던진다 )
package stream; import java.io.*; import java.net.*; public class Client { public static void main(String[] args) { try { Socket socket = new Socket("127.0.0.1", 1234); System.out.println("클라이언트: 서버에 연결 성공"); ObjectInputStream oin = new ObjectInputStream(socket.getInputStream()); System.out.println("클라이언트: 데이터 수신 대기중..."); //java.io.StreamCorruptedException: invalid stream header: 73720011 oin = new ObjectInputStream(socket.getInputStream()); Integer it = (Integer)oin.readObject(); System.out.println("클라이언트: 수신된 데이터:"+it); } catch (Exception e) { e.printStackTrace(); } System.out.println("클라이언트: 프로그램 종료...."); } }
위에서 확인된 내용을 실제 네트워크 채팅 프로그램에 적용한 예
Server.java
package stream; import java.net.*; import java.io.*; import java.util.*; public class Server { static Map<String,Socket> clientMap = new HashMap<>(); public static void main(String[] args) { try { ServerSocket ss = new ServerSocket(1234); while(true) { System.out.println("서버 대기중....."); Socket socket = ss.accept(); System.out.println("클라이언트 접속됨"); new ServerLoginThread(socket).start(); } } catch (Exception e) { e.printStackTrace(); } System.out.println("서버 종료------"); } // end of main() } // end of class Server class ServerLoginThread extends Thread { Socket socket; public ServerLoginThread(Socket socket) { this.socket = socket; } @Override public void run() { try{ ObjectInputStream oin = new ObjectInputStream(socket.getInputStream()); while(true){ ChatDTO dto = (ChatDTO)oin.readObject(); System.out.println("서버에서 수신:"+dto); if(dto.getId()!=null && dto.getPwd()!=null){ Server.clientMap.put(dto.getId(), socket); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); dto.setLogin(true); out.writeObject(dto); out.flush(); System.out.println("서버에서 로그인 성공"); //new ServerComThread(socket).start(); new ServerComThread(socket, oin, out).start(); break; } } }catch(Exception e){ e.printStackTrace(); } System.out.println("로그인 쓰레드 종료....."); } } class ServerComThread extends Thread { Socket socket; ObjectInputStream oin; ObjectOutputStream out; ServerComThread(Socket socket){ this.socket = socket; } ServerComThread(Socket socket, ObjectInputStream oin, ObjectOutputStream out){ this.socket = socket; this.oin = oin; this.out = out; } @Override public void run() { try{ // 아래 라인에서 오류발생, java.io.StreamCorruptedException: invalid stream header: 7371007E // 해결: 한개의 입력스트림을 생성하면 참조를 다른 곳으로 전달하여 계속 재사용하는 방법을 사용함 //ObjectInputStream oin = new ObjectInputStream(socket.getInputStream()); while(true){ ChatDTO dto = (ChatDTO)oin.readObject(); System.out.println("서버에서 수신:"+dto); Set<String> keys = Server.clientMap.keySet(); Iterator<String> it = keys.iterator(); while(it.hasNext()){ String id = it.next(); Socket socket = Server.clientMap.get(id); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject(dto); out.flush(); System.out.println("서버->클라이언트 송신:"+dto); } } }catch(Exception e){ e.printStackTrace(); } }// end of run(); }
package stream; import java.io.*; import java.net.*; import javax.swing.JOptionPane; public class Client { public static void main(String[] args) { Socket socket; try { socket = new Socket("127.0.0.1", 1234); System.out.println("클라이언트 접속성공"); //로그인 절차 ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); //아래의 라인을 사용하면 연결된 서버의 오브젝트 출력스트림에서 writeObject()실행시까지 블로킹됨 //ObjectInputStream oin = new ObjectInputStream(socket.getInputStream()); while(true){ String input = JOptionPane.showInputDialog("아이디 암호 입력(공백으로 구분)"); String[] loginData = input.split(" "); ChatDTO dto = new ChatDTO(); dto.setId(loginData[0]); dto.setPwd(loginData[1]); out.writeObject(dto); out.flush(); ObjectInputStream oin = new ObjectInputStream(socket.getInputStream()); ChatDTO res = null; synchronized(socket) { res = (ChatDTO)oin.readObject(); } if(res.isLogin()) { System.out.println("로그인 성공"); new ClientInputThread(socket, oin).start(); break; } } //서버로 메시지 출력 while(true){ String msg = JOptionPane.showInputDialog("메시지 입력"); ChatDTO dto = new ChatDTO(); dto.setMsg(msg); out.writeObject(dto); out.flush(); } } catch (Exception e) { e.printStackTrace(); } System.out.println("클라이언트 종료"); } } class ClientInputThread extends Thread { Socket socket; ObjectInputStream oin; ClientInputThread(Socket socket){ this.socket = socket; } ClientInputThread(Socket socket, ObjectInputStream oin){ this.socket = socket; this.oin = oin; } @Override public void run() { System.out.println("클라이언트 수신 쓰레드 시작...."); try{ while(true){ ChatDTO dto = null; ObjectInputStream oin = new ObjectInputStream(socket.getInputStream()); // 아래의 라인에서 발생한 오류는 위의 라인을 추가하면 해소됨 /// java.io.StreamCorruptedException: invalid type code: AC dto = (ChatDTO)oin.readObject(); System.out.println("클라이언트가 수신한 오브젝트:"+dto.getMsg()); } }catch(Exception e){ e.printStackTrace(); } } }
package stream; import java.io.Serializable; public class ChatDTO implements Serializable { private static final long serialVersionUID = 1L; private String id; private String pwd; private String msg; private boolean login; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public boolean isLogin() { return login; } public void setLogin(boolean login) { this.login = login; } public static long getSerialversionuid() { return serialVersionUID; } }