[네트워크] TCP 소켓 프로그래밍: read() 함수와 연결 종료 감지의 이해

2025. 4. 5. 01:49·컴퓨터/CS

러스트로 TCP 소켓 프로그래밍에서 리스닝 소켓을 통해 커넥션 소켓을 할당하고 바이트를 읽어드릴 때, 종종 다음과 같은 코드 패턴을 볼 수 있습니다.

match connection_socket.read(&mut receive_buffer) {
    Ok(0) => {
        // 0바이트 수신은 클라이언트가 연결을 닫았음을 의미
        println!("Client {} disconnected.", client_addr);
        break; // 내부 루프 종료 -> 다음 연결 기다림
    }
    // 다른 케이스 처리...
}

 

 

이 코드에서 read() 메서드가 -1을 반환할 때 "클라이언트가 연결을 닫았다"고 처리하는 부분을 봤을 때, 처음에 다음과 같은 의문이 들었습니다.

"그런데 클라이언트가 그냥 아무 동작도 안 하고 있다 == 0바이트를 보내는 것 아닌가? 그렇다면 이 로직은 어떻게 제대로 작동하는 걸까?"

이 의문은 TCP 소켓 프로그래밍의 핵심 개념을 제대로 알지 못해서 든 생각이었습니다.

그래서 이번 글에서는 TCP 연결에서 read() 메서드의 동작 방식과 연결 종료 감지의 메커니즘을 자세히 정리해보겠습니다.

TCP read() 메서드의 동작 원리

1. 블로킹 동작 (응답 대기 상태에서)

클라이언트가 아무 동작도 하지 않고 있는 경우(Idle 상태), 서버 측의 inputStream.read(buffer) 호출은 블로킹(blocking) 됩니다. 즉, 다음 중 하나가 발생할 때까지 메서드는 반환되지 않고 대기 상태로 유지됩니다:

  • 클라이언트로부터 데이터가 수신됨
  • 클라이언트가 연결을 종료함 (FIN 패킷 수신)
  • 네트워크 오류 발생

중요한 점은 클라이언트가 단순히 대기 상태에 있다고 해서 서버에 "0바이트"를 전송하는 것이 아니라는 점입니다. TCP는 데이터가 있을 때만 패킷을 전송하며, 데이터가 없으면 패킷을 보내지 않습니다(heartbeat, keep-alive 패킷은 예외).

가장 위의 의문을 해결할 수 있었습니다. 그렇다면 rust 소켓 프로그래밍에서 0바이트는 어떤 의미길래 받으면 서버가 커넥션을 종료하는걸까요?

2. read()의 반환 값 의미

read() 메서드는 다음과 같은 값을 반환할 수 있습니다:

Ok(n) (n > 0) 클라이언트로부터 n 바이트의 데이터를 성공적으로 읽음
Ok(0) 클라이언트가 연결 종료(FIN) 신호를 보냄
Err(...) 네트워크 오류, 연결 강제 종료(RST) 등 예외 상황 발생

TCP 연결의 수립과 종료: SYN과 FIN 패킷

TCP 연결은 3-Way Handshake라는 과정을 통해 수립되고, 4-Way Handshake 과정을 통해 종료됩니다. 이 과정에서 SYN(Synchronize)과 FIN(Finish) 패킷이 중요한 역할을 합니다.

3-Way Handshake (연결 수립)

  1. 클라이언트 → 서버: SYN 패킷 전송 (연결 요청)
  2. 서버 → 클라이언트: SYN+ACK 패킷 전송 (연결 요청 수락)
  3. 클라이언트 → 서버: ACK 패킷 전송 (연결 수립 완료)

Java에서 new Socket(host, port)를 호출하거나 서버에서 serverSocket.accept()를 호출하면 내부적으로 이 과정이 이루어집니다.

4-Way Handshake (연결 종료)

  1. 클라이언트 → 서버: FIN 패킷 전송 (연결 종료 요청)
  2. 서버 → 클라이언트: ACK 패킷 전송 (종료 요청 확인)
  3. 서버 → 클라이언트: FIN 패킷 전송 (서버의 종료 준비 완료)
  4. 클라이언트 → 서버: ACK 패킷 전송 (연결 종료 완료)

이 과정을 통해 양쪽 모두 안전하게 연결을 종료할 수 있습니다.

0이 의미하는 것: EOF (End-Of-File)

read() 메서드가 -1을 반환하는 경우는 TCP 프로토콜 수준에서 상대방(클라이언트)이 자신의 소켓 송신 스트림을 정상적으로 닫았다는 명시적인 신호(FIN 패킷)를 보냈을 때 입니다.

이는 다음과 같은 상황에서 발생합니다:

  1. 클라이언트 프로그램이 명시적으로 socket.shutdown(Shutdown::Write)를 호출
  2. 클라이언트가 소켓 자체를 close() 호출로 닫음
  3. 클라이언트 프로세스가 정상적으로 종료되어 운영체제가 자동으로 모든 소켓을 닫을 때

서버는 이 FIN 패킷을 수신하고, 해당 소켓의 수신 버퍼에 남아있는 데이터를 모두 읽어들인 후, 다음 read() 호출에서 Ok(0)을 반환(클라이언트가 0바이트 보내는게 아니라 스스로 처리)합니다. 이는 "더 이상 이 연결로부터 읽을 데이터가 없고, 상대방이 보내기를 완료했다"는 의미의 EOF(End-Of-File) 신호입니다.

TCP와 UDP의 차이점

이 동작은 TCP와 UDP의 중요한 차이점 중 하나입니다.

  • TCP: 연결 지향적 프로토콜로, 연결 설정과 종료에 대한 명확한 신호(SYN, FIN 등)가 있으며, 데이터 스트림의 끝(EOF)을 감지할 수 있습니다.
  • UDP: 비연결형 프로토콜로, 데이터그램 단위로 통신하며 연결 상태를 관리하지 않습니다. UDP에서는 recvfrom()이 0바이트 데이터그램을 수신할 수도 있으며, 이는 EOF와 같은 특별한 의미를 갖지 않습니다.

요약

  • 클라이언트 Idle 상태: read()는 블로킹되어 반환되지 않음 (데이터 대기)
  • 클라이언트가 데이터 전송: read()는 Ok(n) (n > 0) 반환
  • 클라이언트가 연결 종료: read()는 Ok(0) 반환 (EOF)
  • 네트워크 오류 발생: read()는 Err(...) 반환

따라서 read()가 Ok(0)을 반환하는지 확인하는 것은 클라이언트가 단순히 idle 상태 (아무것도 보내고 있지 않는) 인 것이 아니라 아니라, 명시적으로 연결 종료 절차를 시작했다는 것을 감지하는 올바른 방법입니다.

이번 기회에 네트워크 소켓 프로그래밍에 필요한 정확한 리소스 관리 방법을 알 수 있었습니다.

'컴퓨터 > CS' 카테고리의 다른 글

[소프트웨어공학] 인터페이스 개념과 연결되는 스프링 DI  (0) 2025.04.15
[운영체제] "파이프는 공유 메모리인가?" 리눅스의 통신 방식  (0) 2025.04.09
[네트워크] TCP와 UDP 소켓 프로그래밍의 근본적인 차이: 실제 에러를 통해 이해하기  (0) 2025.04.04
'컴퓨터/CS' 카테고리의 다른 글
  • [소프트웨어공학] 인터페이스 개념과 연결되는 스프링 DI
  • [운영체제] "파이프는 공유 메모리인가?" 리눅스의 통신 방식
  • [네트워크] TCP와 UDP 소켓 프로그래밍의 근본적인 차이: 실제 에러를 통해 이해하기
givemethatsewon
givemethatsewon
  • givemethatsewon
    프로덕트 중심 개발자
    givemethatsewon
  • 전체
    오늘
    어제
    • 분류 전체보기 (11)
      • 컴퓨터 (4)
        • CS (4)
        • 에러 트러블 슈팅 (0)
        • 알고리즘 (0)
      • 프로덕트 문제 해결 기록 (6)
        • Clody (0)
        • 8fit (0)
        • WIMI (0)
      • etc (1)
        • 경험 (0)
        • 정보 (1)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

    파이프
    rust
    스프링
    의존성
    Jackson
    spring
    tcp
    UML
    socket
    인터페이스
    udp
    공유메모리
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
givemethatsewon
[네트워크] TCP 소켓 프로그래밍: read() 함수와 연결 종료 감지의 이해
상단으로

티스토리툴바