[Java] 51. 네트워크 - 프로그램(2)

2025. 7. 1. 16:57·Java/IO, Network
네트워크 - 프로그램(2)

네트워크 - 프로그램(2)

#Java/adv2/Network


/네트워크 프로그램4 - 자원 정리1: 네트워크 프로그램에 try-catch-finally 도입

  • 스트림, 소켓은 여러 곳에서 닫는다 유틸리티 클래스로 뽑자
    • close하다가 예외 발생시 단순히 로그만 남기자
    • input, output, socket 닫는 것 각각을 메서드로 뽑아서, 각각 예외를 잡도록 하자. 하나를 닫는 과정에서 예외가 발생해도 다음 자원을 닫을 수 있다.
    • 순서에 신경쓰자(여기선 socket을 마지막에 닫자)
  • Client
    • 자원 선언은 try 밖으로 빼고, 자원 정리 시 finally 코드 블럭에서 SocketCloseUtil.closeAll() 만 호출
  • Session
    • Client와 동일한 구조 적용
  • Server는 동일
  • 해결한 문제: 클라이언트에서 연결 종료 시 서버의 Session 에 EOFException 이 발생하면서 자원을 제대로 정리하지 못했던 문제를 finally를 통해 자원을 정리하도록 하였다.
    • 참고) EOFException는 IOException의 자식이다.

/네트워크 프로그램5 - 자원 정리2 : try-with-resources 도입

  • Client
    • try (자원 선언 및 할당)
    • 선언되는 순서의 반대로 자원 정리가 적용된다는 점에 유의하자.
    • OutputStream, InputStream, Socket 모두 AutoCloseable 을 구현하고 있다.
  • Session
    • 세션의 경우 socket을 생성하여 사용하는게 아니라, 생성할 때 외부에서 받아온다.
    • try (socket; DataInputStream input = ….) 이렇게 객체의 참조만 넣어두면 자원 정리 시점에 AutoCloseable 이 호출된다.
    • socket.isClosed()를 통해 실제로 자원이 정리가 되었는지 체크할 수 있다.
  • Server는 동일

/네트워크 프로그램6 - 자원 정리3

  • 서버를 종료할 때, 서버 소켓과 연결된 모든 소켓 자원을 다 반납하고 서버를 안정적으로 종료하는 방법
  • 서버의 특정 비즈니스 로직을 넣어 종료하는 경우가 아니라, 서버를 직접 종료하면서 자원도 함께 정리하는 방법
  • 셧다운 훅(Shutdown Hook) 적용
    • 자바는 프로세스가 종료될 때, 자원 정리나 로그 기록과 같은 종료 작업을 마무리 할 수 있는 셧다운 훅이라는 기능을 지원한다
    • 프로세스 종료 2가지 분류
    • 정상 종료
      • 모든 non 데몬 스레드의 실행 완료로 자바 프로세스 정상 종료
      • 사용자가 Ctrl+C를 눌러서 프로그램을 중단
      • kill 명령 전달 (kill -9 제외)
      • IntelliJ의 stop 버튼
    • 강제 종료
      • 운영체제에서 프로세스를 더 이상 유지할 수 없다고 판단할 때 사용
      • 리눅스/유닉스의 kill -9 나 Windows의 taskkill /F
    • 정상 종료의 경우에는 셧다운 훅이 작동해서 프로세스 종료 전에 필요한 후 처리를 할 수 있다.
  • Client는 기존과 동일
  • Server
    • 세션 매니저 필요
      • 원래 메인 스레드에서는 while문을 통해 OS의 백로그 큐에서 연결을 확인하고 별도의 스레드에서 세션(소켓)을 만들어줬다.
      • 문제는 세션을 만들고나서 부터는 별도 스레드에서 동작하기 때문에, 이 세션들을 추적하지를 않는다. 서버가 종료될 때 이 세션들을 추적해서 다 종료를 해줘야한다.
      • 자료구조에다가 다 담아둔 다음에 꺼내서 종료해주자. 요걸 SessionManager라고 하자.
    • SessionManager
      • add() : 클라이언트의 새로운 연결을 통해, 세션이 새로 만들어지는 경우 add() 를 호출해서 세션 매니저에 세션을 추가한다. 세션이 세션매니저의 add()에 자신(this)을 전달하여 추가하는 구조로 구현.
      • remove() : 클라이언트의 연결이 끊어지면 세션도 함께 정리된다. 이 경우 remove() 를 호출해서 세션 매니저에서 세션을 제거한다.
  • Session
    • 세션은 다음 두 가지 경우에 종료될 수 있다.
      • 클라이언트 연결 종료 (or IOException 발생)
      • 서버 종료 시 서버의 SessionManager가 세션을 종료시킴
    • 이를 위해 자원.close()하는 코드를 별도의 public 메서드로 분리하자.
      • 이를 위해서는 socket, input, output을 Session의 필드에 선언해야 한다. (run, close 두 군데에서 접근)
      • 또한, SessionManager 또한 필드에 선언하여 Session 생성시 SessionManager의 참조를 얻고, 클라이언트에서 연결 종료시 sessionManager의 세션 목록에서 자신을 제거할 수 있도록 하자.
      • 세선 종료 시, 서버 종료 시 동시에 호출될 수 있으므로 synchronized로 동시성 제어를 하자.
        • 동시 접근시 첫 번째 이후에 접근하는 close()가 이미 닫힌 자원을 다시 닫지 않도록 하기 위해서, closed 변수를 플래그 용도로 선언하여, 이미 닫힌 경우 return 하도록 해준다.
    • try-with-resources를 사용할 수 없는 구조다.try-with-resources는 사용과 해제를 함께 묶어서 처리할 때 사용한다.

/네트워크 프로그램6 - 자원 정리4 : ShutDownHook 등록

  • ShutDownHook 클래스
    • 필드에 ServerSocket과 sessionManager 참조
    • Runnable 구현
      • run()에서 sessionManager.closeAll();, serverSocket.close(); 호출
      • 호출 후Thread.sleep(1000);로 자원 정리 대기 해야 한다.
        • 보통 모든 non 데몬 스레드의 실행이 완료되면 자바 프로세스가 정상 종료되지만, Ctrl+C, kill 명령, 인텔리제이 stop 등으로 종료하면 non 데몬 스레드 종료 여부랑 무관하게 자바 프로세스가 종료된다.
        • 단, 셧다운 훅의 실행이 끝날 때 까지는 기다려준다.
          • sleep을 통해 다른 스레드가 자원을 정리하거나 필요한 로그를 남길 수 있도록 셧다운 훅의 실행을 잠시 대기한다.
  • 셧다운 훅 등록
    • ShutdownHook shutdownHook = new ShutdownHook(serverSocket, sessionManager);
    • Runtime.getRuntime().addShutdownHook(new Thread(shutdownHook, "shutdown"));
  • 정상 종료
    • ShutdownHook이 sessionManger.closeAll() 호출
      • ShutdownHook 스레드가 session.close()
      • 기존에 readUTF()로 블로킹 중이던 스레드는 SocketException을 만나 catch, finally문으로 이동하여 sessionManager에서 자신을 제거하고 close() 호출.
      • ShutdownHook 스레드와 세션을 실행중이던 스레드 동시에 close() 호출될 수 있음 어차피 synchronized로 먼저 실행된 얘만 close함. 뒤 얘는 플래그 보고 return
    • ShutdownHook이 serverSocket.close() 호출
      • 메인 스레드는 serverSocket.accept()에서 블로킹이였고, 셧다운 훅의 close()로 SocketException가 발생해 catch문으로 이동
    • ShutdownHook이 세션매니저와 서버 소켓을 close하고 Thread.sleep()을 해야, 블로킹 상태던 기존 스레드들이 catch로 이동해 로그를 남기거나 다른 필요한 자원들을 정리할 수 있음. (ShutDownHook의 자원 정리 대기 이유)

네트워크 연결 시 발생할 수 있는 예외들

  • /네트워크 예외1 - 연결 예외
    • UnknownHostException: 호스트를 알 수 없다.
      • Socket socket = new Socket("999.999.999.999", 80);
      • Socket socket = new Socket("google.gogo", 80);
    • ConnectException: 연결이 거절됨
      • Socket socket = new Socket("localhost", 45678); // 미사용 포트
        • java.net.ConnectException: Connection refused
      • 연결이 거절되었다는 것은, 우선은 네트워크를 통해 해당 IP의 서버 컴퓨터에 접속은 했다는 뜻
      • 해당 서버 컴퓨터가 45678 포트를 사용하지 않기 때문에 TCP 연결을 거절
      • IP에 해당하는 서버는 켜져있지만, 사용하는 PORT가 없을 때 주로 발생
      • 네트워크 방화벽의 무단 연결 방지 시에도 이 예외 발생.
      • 서버 컴퓨터의 OS는 이때 TCP RST(Reset)라는 패킷을 보내서 연결을 거절한다. 클라이언트가 연결 시도 중에 RST 패킷을 받으면 이 예외 발생
        • TCP RST(Reset) 패킷: 연결에 문제가 있다는 뜻. 이 패킷을 받은 대상은 연결을 바로 해제해야 함.

  • /네트워크 예외2 - 타임아웃: 네트워크 연결을 시도해서 서버 IP에 연결 패킷을 전달했지만 응답이 없는 경우
    • 실무에서 자주 발생하는 부분
    • 타임아웃이 발생하는 경우
      • 해당 IP로 연결 패킷을 보내지만 해당 서버가 너무 바쁘거나 문제가 있어서 연결 응답 패킷을 보내지 못하는 경우도 있다.
      • 공유기를 이용하여 공유기가 사용하는 IP 대역의 192.168.1.250 을 사용하여 연결하면 연결 패킷을 보내지만 IP를 사용하는 서버가 없으므로 TCP 응답이 오지 않는다.
    • OS 기본 대기 시간
      • OS에는 연결 대기 타임아웃이 설정되어 있다. 맥은 75초, 윈도우는 21초, 리눅스는 75~180초 사이
      • 해당 시간이 지나면 java.net.ConnectException: Operation timed out 이 발생한다.
      • 이렇게 오래 대기하는 것은 좋은 방법이 아니다. 연결이 안 되면 빠르게 고객에게 문제가 있다고 알려줘야 한다.
    • /TCP 연결 타임아웃 - 직접 설정
      • Socket socket = new Socket();
        socket.connect(new InetSocketAddress("192.168.1.250", 45678), 1000); // 1초대기
      • Socket 객체를 생성할 때 인자로 IP, PORT를 모두 전달하면 생성자에서 바로 TCP 연결을 시도한다.
      • 하지만 IP, PORT를 모두 빼고 객체를 생성하면, 객체만 생성되고, 아직 연결은 시도하지 않는다.
      • 추가적으로 필요한 설정을 더 한 다음에 socket.connect() 를 호출하면 그때 TCP 연결을 시도한다.
        • 대표적으로 타임아웃을 설정할 수 있음.
        • public void connect(SocketAddress endpoint, int timeout) throws IOException { ... }
          • InetSocketAddress : SocketAddress 의 자식이다. IP, PORT 기반의 주소를 객체로 제공한다.
          • timeout : 밀리초 단위로 연결 타임아웃을 지정할 수 있다.
        • 타임아웃 시간이 지나도 연결이 되지 않으면 SocketTimeoutException이 발생한다
    • /TCP 소켓 타임아웃 - read 타임아웃
      • 소켓 타임아웃 또는 read 타임 아웃이라고 부르는 타임아웃이다. (매우 중요!!)
      • 연결이 잘 된 이후에 클라이언트가 서버에 어떤 요청을 했다고 가정하자. 서버가 계속 응답을 주지 않는다면?
        • ex) 서버에 사용자가 폭주하고 매우 느려져서 응답을 계속 주지 못하는 상황
      • socket.setSoTimeout(3000); // 타임아웃 시간 설정
        • int read = input.read(); 의 경우 기본은 무한대기 이지만, socket.setSoTimeout() 을 사용하면 밀리초 단위로 타임아웃 시간을 설정할 수 있다. 여기서는 3초를 설정했다.
      • read 타임아웃 시간이 지나면 SocketTimeoutException이 발생한다.
      • 실무에서 소켓 타임아웃(read 타임 아웃)을 누락하여 장애가 자주 발생한다고 한다.
        • 외부 서버와 통신을 하는 경우 반드시 연결 타임아웃과 소켓 타임아웃을 지정하자.

/네트워크 예외3 - 정상 종료

  • TCP에는 정상 종료, 강제 종료, 즉 2가지 종류의 종료가 있다.
  • /정상 종료
    • socket.close() 를 호출하면 4-way handshake 과정을 거친다.
    • 누가 close했든 FIN 패킷을 받으면 socket.close() 를 호출해서 FIN 패킷을 상대방에게 전달해야 한다.
    • 서버가 연결 종료 한다고 해보자. 서버는 종료를 위해 socket.close() 를 호출한다. (나 종료 했어~)
      • 서버는 클라이언트에 FIN 패킷을 전달한다.
    • 클라이언트는 FIN 패킷을 받는다.
      • 클라이언트의 OS에서 FIN에 대한 ACK 패킷을 전달한다. (ㅇㅋ 확인했어)
    • 그리고 클라이언트도 종료를 위해 socket.close() 를 호출한다.
      • 클라이언트는 서버에 FIN 패킷을 전달한다. (나도 종료했어~)
    • 서버의 OS는 FIN 패킷에 대한 ACK 패킷을 전달한다. (ㅇㅋ 확인했어)
    • 예를 들어 서버가 연결을 맺고 1초뒤에 소켓을 close하는 상황을 생각해보자.
      • 서버와 클라이언트가 연결을 맺는다.
      • 클라이언트는 서버의 메시지를 3가지 방법으로 읽는다.
        • read() : 1byte 단위로 읽음
        • readLine() : 라인 단위로 String 으로 읽음
        • readUTF() : DataInputStream 을 통해 String 단위로 읽음
      • 1초 뒤 서버에서 socket.close() 를 호출하여 클라이언트에 FIN 패킷을 보낸다.
      • 클라이언트는 FIN 패킷을 받는다.
      • 서버가 소켓을 종료했다는 의미이므로 클라이언트는 더는 읽을 데이터가 없다.
      • FIN 패킷을 받은 클라이언트의 소켓은 더는 서버를 통해 읽을 데이터가 없음을 -1(EOF) 로 반환한다.
        • read() -1
        • BufferedReader().readLine() null
        • DataInputStream.readUTF() EOFException
        • if문이나, try-catch-finally로 위 조건이 충족되면 자원을 정리해준다.
      • 여기서 중요한 점은 EOF가 발생하면 상대방이 FIN 메시지를 보내면서 소켓 연결을 끊었다는 뜻이다.
        • 이 경우 소켓에 다른 작업을 하면 안되고, FIN 메시지를 받은 클라이언트도 close() 를 호출해서 상대방에 FIN 메시지를 보내고 소켓 연결을 끊어야 한다.

/네트워크 예외4 - 강제 종료

  • /강제 종료: TCP 연결 중에 문제가 발생하면 RST 라는 패킷이 발생한다. 이 경우 연결을 즉시 종료해야 한다.
  • 서버가 연결을 종료하여 FIN 패킷을 클라이언트에 보냈다. 이 때 만약 클라이언트가 output.write()로 서버에 메시지를 전달하면?
    • 일단 먼저 FIN 패킷을 받으면 클라이언트의 OS에서 FIN에 대한 ACK 패킷을 전달한다.
    • 그리고 output.write()로 인해 데이터 전송하는 PUSH 패킷이 서버에 전달된다.
      • 서버는 이미 FIN으로 종료를 요청했는데, PUSH 패킷으로 데이터가 전송되었다. 서버가 기대하는 건 FIN 패킷.
    • 서버는 TCP 연결에 문제가 있다고 판단하고 즉각 연결을 종료하라는 RST 패킷을 클라이언트에 전송한다.
  • RST 패킷이 도착했다는 것은 현재 TCP 연결에 심각한 문제가 있으므로 해당 연결을 더는 사용하면 안된다는 의미이다.
    • RST 패킷이 도착하면 자바는 read() 로 메시지를 읽을 때 다음 예외를 던진다.
      • MAC: java.net.SocketException: Connection reset
    • RST 패킷이 도착하면 자바는 write() 로 메시지를 전송할 때 다음 예외를 던진다.
      • MAC: java.net.SocketException: Broken pipe
    • (TCP에서 RST 패킷은 연결 상태를 초기화(리셋)해서 더 이상 현재의 연결을 유지하지 않겠다는 의미를 전달한다.)

네트워크 예외를 다루는 기준

  • 예외를 다 따로따로 이해하고 구분해서 다루는 건 어렵다. 기본적으로 정상 종료, 강제 종료 모두 자원 정리하고 닫도록 설계하고, 자세히 분류해야 하는 경우 그 때 예외를 세부적으로 구분하여 처리하자.

Ref) 김영한의 실전 자바 - 고급 2편, I/O, 네트워크, 리플렉션 강의 | 김영한 - 인프런

'Java > IO, Network' 카테고리의 다른 글

[Java] 53. HTTP 서버 만들기  (0) 2025.07.01
[Java] 52. 채팅 프로그램(feat. Command Pattern)  (0) 2025.07.01
[Java] 50. 네트워크 - 프로그램(1)  (0) 2025.07.01
[Java] 49. File, Files  (0) 2025.07.01
[Java] 48. 자바 I/O 활용  (0) 2025.07.01
'Java/IO, Network' 카테고리의 다른 글
  • [Java] 53. HTTP 서버 만들기
  • [Java] 52. 채팅 프로그램(feat. Command Pattern)
  • [Java] 50. 네트워크 - 프로그램(1)
  • [Java] 49. File, Files
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 (457)
      • Software Development (27)
        • Performance (0)
        • TroubleShooting (1)
        • Refactoring (0)
        • Test (8)
        • Code Style, Convetion (0)
        • DDD (0)
        • Software Engineering (18)
      • Java (71)
        • Basic (5)
        • Core (21)
        • Collection (7)
        • 멀티스레드&동시성 (13)
        • IO, Network (8)
        • Reflection, Annotation (3)
        • Modern Java(8~) (12)
        • JVM (2)
      • Spring (53)
        • Framework (12)
        • MVC (23)
        • Transaction (3)
        • AOP (11)
        • Boot (0)
        • AI (0)
      • DB Access (1)
        • Jdbc (1)
        • JdbcTemplate (0)
        • JPA (14)
        • Spring Data JPA (0)
        • QueryDSL (0)
      • Computer Science (130)
        • Data Structure (27)
        • OS (14)
        • Database (10)
        • Network (21)
        • 컴퓨터구조 (6)
        • 시스템 프로그래밍 (23)
        • Algorithm (29)
      • HTTP (8)
      • Infra (1)
        • Docker (1)
      • 프로그래밍언어론 (15)
      • Programming Language(Sub) (77)
        • Kotlin (1)
        • Python (25)
        • C++ (51)
        • JavaScript (0)
      • FE (11)
        • HTML (1)
        • CSS (9)
        • React (0)
        • Application (1)
      • Unix_Linux (0)
        • Common (0)
      • PS (13)
        • BOJ (7)
        • Tip (3)
        • 프로그래머스 (0)
        • CodeForce (0)
      • Book Review (4)
        • Clean Code (4)
      • Math (3)
        • Linear Algebra (3)
      • AI (7)
        • DL (0)
        • ML (0)
        • DA (0)
        • Concepts (7)
      • 프리코스 (4)
      • Project Review (6)
      • LegacyPosts (11)
      • 모니터 (0)
      • Diary (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
lumana
[Java] 51. 네트워크 - 프로그램(2)
상단으로

티스토리툴바