컴퓨터 네트워크

Network program - 2

으엉어엉 2024. 12. 24. 16:43
728x90
public class SocketCloseUtil {
    public static void closeAll(Socket socket, InputStream input, OutputStream output){
        close(input);
        close(output);
        close(socket);
    }
    //input
    public static void close(InputStream input) {
        if (input != null) {
            try {
                input.close();
            }
            catch (IOException e) {
                log(e.getMessage());
            }
        }
    }
    //output
    public static void close(OutputStream output) {
        if (output != null) {
            try {
                output.close();
            }
            catch (IOException e) {
                log(e.getMessage());
            }
        }
    }
    //socket
    public static void close(Socket socket) {
        if (socket != null) {
            try {
                socket.close();
            }
            catch (IOException e) {
                log(e.getMessage());
            }
        }
    }
}

기본적인 null 체크와 자원 종료시 예외를 잡아서 처리하는 코드가 들어가 있다. 참고로 자원 정리 과정에서 문제가 발생해도 코드에서 직접 대응할 수 있는 부분은 거의 없다. 이 경우 간단히 로그를 남겨서 이후에 개발자가 인지할 수 있는 정도면 충분하다. 각각의 예외를 잡아서 처리했기 때문에 Socket, InputStream OutputStream 중 하나를 닫는 과정에서 예외가 발생해도 다음 자원을 닫을 수 있다. Socket을 먼저 close 해야한다.

 

이번에는 서버를 종료할 때, 서버 소켓과 연결된 모든 소켓 자원을 다 반납하고 서버를 안정적으로 종료하는 방법을 사용해보자. 

 

셧다운 훅(Shutdown Hook)

자바는 프로세스가 종료될 때, 자원 정리나 로그 기록과 같은 종료 작업을 마무리 할 수 있는 셧다운 훅이라는 기능을 지 원한다.

정상종료 VS 강제종료

 

public class SessionManager {
    private List<SessionV6> sessions = new ArrayList<>();
    public synchronized void add(SessionV6 session) {
        sessions.add(session);
    }
    public synchronized void remove(SessionV6 session) {
        sessions.remove(session);
    }
    public synchronized void closeAll() {
        for (SessionV6 session : sessions) {
            session.close();
        }
        sessions.clear();
    }
}

 

 

 

각 세션은 소켓과 연결 스트림을 가지고 있다. 따라서 서버를 종료할 때 사용하는 세션들도 함께 종료해야 한다. 모든 세 션들을 찾아서 종료하려면 생성한 세션을 보관하고 관리할 객체가 필요하다.

 

 
public class Session implements Runnable {
    private final Socket socket;
    private final DataInputStream input;
    private final DataOutputStream output;
    private final SessionManagerV6 sessionManager;
    private boolean closed = false;

    public SessionV6(Socket socket, SessionManagerV6 sessionManager) throws
            IOException {
        this.socket = socket;
        this.input = new DataInputStream(socket.getInputStream());
        this.output = new DataOutputStream(socket.getOutputStream());
        this.sessionManager = sessionManager;
        this.sessionManager.add(this);
    }
    @Override
    public void run() {
        //run 이라 예외 X
        // finally 블록에서 변수에 접근해야 한다. 따라서 try 블록 안에서 선언할 수 없다.
        try{
        while (true) {
                // 클라이언트로부터 문자 받기
                String received = input.readUTF(); // 블로킹
                log("client -> server: " + received);
                if (received.equals("exit")) {
                    break;
                }
                // 클라이언트에게 문자 보내기
                String toSend = received + " World!";
                output.writeUTF(toSend);
                log("client <- server: " + toSend);
            }
        }catch (IOException e) {
            log(e);
        }
    }
    // 세션 종료시, 서버 종료시 동시에 호출될 수 있다.
    public synchronized void close() {
        if (closed) {
            return;
        }
        closeAll(socket, input, output);
        closed = true;
        log("연결 종료: " + socket);
    }
}
public class Server {
    private static final int PORT = 12345;

    public static void main(String[] args) throws IOException {
        log("서버 시작");

        SessionManagerV6 sessionManager = new SessionManagerV6();
        ServerSocket serverSocket = new ServerSocket(PORT);

        log("서버 소켓 시작 - 리스닝 포트: " + PORT);

        // ShutdownHook 등록 .. 종료되기 전 thread가 실행됨
        ShutdownHook shutdownHook = new ShutdownHook(serverSocket, sessionManager);
        Runtime.getRuntime().addShutdownHook(new Thread(shutdownHook, "shutdown"));

        try {
            while (true) {
                Socket socket = serverSocket.accept(); // 블로킹
                log("소켓 연결: " + socket);
                Session session = new Session(socket, sessionManager);
                Thread thread = new Thread(session);
                thread.start();
            }
        } catch (IOException e) {
            log("서버 소캣 종료: " + e);
        }
    }

    //shutdownhook 클래스 별도의 클래스에서 실행되어야함
    static class ShutdownHook implements Runnable {
        private final ServerSocket serverSocket;
        private final SessionManagerV6 sessionManager;

        public ShutdownHook(ServerSocket serverSocket, SessionManager sessionManager) {
            this.serverSocket = serverSocket;
            this.sessionManager = sessionManager;
        }
        //정상종료 될 때 호출됨
        @Override
        public void run() {
            log("shutdownHook 실행");

            try {
                sessionManager.closeAll();
                serverSocket.close();
                Thread.sleep(1000); // 자원 정리 대기
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("e = " + e);
            }

        }
    }
}

 

 

 

728x90

'컴퓨터 네트워크' 카테고리의 다른 글

네트워크 exception  (0) 2024.12.24
Network program  (0) 2024.12.24
네트워크 - 기본 이론  (1) 2024.12.24
InputStream, OutputStream  (1) 2024.12.22
컴퓨터 네트워크 4장- 라우터 내부  (0) 2024.05.18