도입

1. 네트워크

네트워크는 왜 필요하며, 어떻게 데이터를 목적지까지 전달하는가?

  • ① 통신의 물리적 한계와 네트워크의 탄생

    • 두 컴퓨터를 선으로 직접 연결하여 통신할 때, 거리가 멀어지면 신호에 노이즈가 끼고 약해진다.
    • 단순히 송신 전압을 높이는 것은 에너지 소모가 커 비효율적이므로, 중간에 데이터를 받아서 다음으로 넘겨주는 중계 방식을 택하게 되었고, 이것이 확장되어 네트워크 망(Network)이 되었다.
  • ② 데이터의 신호 변환과 전송

    • 컴퓨터의 데이터는 네트워크 하드웨어(버퍼)를 거쳐 케이블이나 무선 매체를 탈 수 있는 물리적인 신호(Signal)로 변환되어 전송된다.
    • 이때 신호는 매체와 방식에 따라 다양한 형태를 가진다.
  • ③ 최적의 경로 탐색 (라우팅과 IP)

    • 복잡하게 얽힌 네트워크 망에서 목적지까지 도달하려면 가장 효율적인 길(Shortest Path)을 찾아야 한다.
    • 이러한 길 찾기 기능을 수행하는 장비가 라우터(Router)이며, 목적지의 위치를 정확히 식별하고 찾아갈 수 있도록 지도/주소 역할을 하는 것이 IP이다.

2. 데이터 전송 흐름

  • 네트워크 통신은 크게 다음 3가지 영역을 거쳐 전달된다.
    1. 애플리케이션 영역 (User Space): 통신을 요청하고 데이터를 생성/수신하는 프로그램
    2. 운영체제 커널 영역 (Kernel Space): 데이터를 통신 규약(TCP/IP)에 맞게 쪼개고 포장하는 OS
    3. 하드웨어 영역 (Hardware): 실제 전기적 신호로 데이터를 내보내는 장치

(1) 수신 측: 데이터를 받는 과정

  • L1 Hardware

    • 전기 신호를 받아 데이터(0과 1)로 읽어 들임
  • L2 Device Driver

    • 들어온 데이터가 1,500byte 단위의 조각들임을 확인하고 커널로 올림
  • L3/L4 OS Kernel

    • L3(IP)에서 내 컴퓨터로 온 게 맞는지 확인하고,
    • L4(TCP)에서 쪼개져서 도착한 1,500byte 조각들의 순서를 맞추어 원래의 거대한 데이터로 다시 조립(Reassembly)함
  • L5 Application

    • 다음 받는 컴퓨터의 애플리케이션은 조립이 완료된 데이터를 recv() 함수를 호출하여 통째로 가져다 쓴다.

(2) 송신 측의 흐름 (Application OS Hardware)

  • L5 Application (애플리케이션 계층)

    • 사용자가 쓰는 프로그램(웹 브라우저, 카카오톡 등)
    • 특징: 전송할 데이터의 크기에 한계가 없다. (예: 10GB짜리 영화 파일)
    • 동작: 앱은 네트워크의 복잡한 원리를 알 필요 없이, 그저 OS에게 데이터를 주며 send() 함수만 호출하면 된다.
  • L4/L3 OS Kernel (운영체제 커널 - 전송/네트워크 계층)

    • L4 (TCP/UDP): 앱이 던져준 거대한 데이터를 네트워크가 소화할 수 있는 크기로 쪼갠다. (세그먼트화) 목적지 앱을 찾기 위한 Port 번호를 붙인다.
    • L3 (IP): 쪼개진 데이터에 목적지 컴퓨터를 찾기 위한 IP 주소를 붙인다.
  • L2 Device Driver (데이터링크 계층)

    • 네트워크 카드(NIC)를 제어하는 소프트웨어 드라이버
    • 특징: CSMA/CD 이더넷 프로토콜 환경에서는 1회에 최대 1,500byte(MTU)까지만 전송 가능하다. (OS가 이 크기에 맞춰 데이터를 미리 쪼개어 내려보냄)
    • MAC 주소를 붙여 물리적 전송을 준비한다.
  • L1 Hardware (물리 계층)

    • 랜카드, 랜선 등 물리적 장비
    • L2에서 포장된 데이터를 0과 1의 전기 신호로 변환하여 랜선을 통해 방출한다.

3. 소켓 통신을 통한 두 호스트 간의 데이터 전송 파이프라인

  • 소켓 설명은 교재에 없다.
    • 시험은 책에 있는 내용 + 실습 소스 범위 안에서만

(1) 송신 측 데이터 흐름 (Encapsulation)

  1. APP A (send(A_1))

    • Host A의 애플리케이션이 전송할 데이터를 만들고 send() 시스템 콜을 호출한다.
  2. 소켓 Lib (Socket Library)

    • 애플리케이션의 요청을 운영체제(OS)가 이해할 수 있도록 연결해 주는 API(인터페이스) 역할을 한다.
  3. 소켓 A_1 (송신 버퍼)

    • send()는 데이터를 즉시 내보내는 것이 아니다.
    • OS 메모리 공간에 할당된 소켓 A_1의 송신 버퍼(Send Buffer)에 데이터를 복사해 두고 애플리케이션은 자기 할 일을 하러 돌아간다.
  4. 전송 Protocol (TCP/UDP - L4)

    • OS 커널은 송신 버퍼에 쌓인 데이터를 꺼내어 적절한 크기로 자르고,
    • 포트(Port) 번호를 붙여 세그먼트로 만든다.
    • (TCP의 경우 신뢰성 제어 추가)
  5. Network Protocol (IP - L3)

    • 출발지와 목적지의 IP 주소를 붙여 패킷으로 만든다. (길 찾기 준비)
  6. Data 전송 Protocol (L2 데이터링크 & L1 물리)

    • MAC 주소를 붙이고(프레임), 최종적으로 랜카드(NIC)를 통해 0과 1의 전기/광 신호로 변환하여 케이블로 내보낸다.

(2) 연결선 (네트워크 망)

  • 인터넷 망, 라우터, 광케이블 등 물리적 매체를 통해 신호가 Host B를 향해 날아간다.

(3) 수신 측 데이터 흐름 (Decapsulation)

  1. Data 전송 Protocol (L1/L2)

    • Host B의 랜카드가 전기 신호를 수신하여 데이터로 변환하고,
    • 자신의 MAC 주소가 맞는지 확인한다.
  2. Network Protocol (IP - L3)

    • IP 주소를 확인하여 자신에게 온 데이터가 맞는지 검사한다.
  3. 전송 Protocol (TCP/UDP - L4)

    • 포트(Port) 번호를 확인하여 어떤 소켓(애플리케이션)으로 보낼지 결정하고,
    • 데이터 조각들을 원래 순서대로 조립한다.
  4. 소켓 B_1 (수신 버퍼)

    • 조립된 데이터는 소켓 B_1의 수신 버퍼(Receive Buffer)에 차곡차곡 쌓인다.
    • (아직 앱이 가져가지 않은 상태)
  5. 소켓 Lib

    • 수신 버퍼에 데이터가 도착했음을 애플리케이션에 알린다.
  6. APP B (recv(B_1))

    • Host B의 애플리케이션이 recv() 시스템 콜을 호출하여,
    • 수신 버퍼에 쌓여 있는 데이터를 자신의 메모리 영역으로 가져가서 사용한다.

1. 네트워크 프로그래밍과 소켓의 기초 (전화기 비유)

  • 네트워크 프로그래밍

    • 운영체제가 제공하는 소켓(Socket)이라는 소프트웨어적 장치를 통해
    • 물리적, 소프트웨어적 세부 내용을 신경 쓰지 않고
    • 두 컴퓨터 간에 데이터를 송수신하는 프로그램을 만드는 것
  • 소켓에 대한 간단한 이해

    • 네트워크(인터넷)의 연결 도구
    • 운영체제에 의해 제공이 되는 소프트웨어적인 장치
    • 소켓은 프로그래머에게 데이터 송수신에 대한 물리적, 소프트웨어적 세세한 내용을 신경 쓰지 않게 한다.
    • 운영체제마다 소켓을 구현하는 방식이 다르기 때문에 자세하게 설명하는 교재가 없다.

1.1. 서버

  • 서버(수신자) 소켓의 생성 과정
    • TCP 소켓은 전화를 받는 과정에 비유할 수 있다.
    • 1단계. socket(): 소켓 생성 (전화기 장만)
    • 2단계. bind(): IP와 Port 번호 할당 (전화번호 부여)
    • 3단계. listen(): 연결 요청 대기 상태로 변경 (전화선을 연결하여 벨이 울릴 수 있는 상태)
    • 4단계. accept(): 클라이언트의 연결 요청 수락 (수화기를 들어 전화를 받음)
    • 서버는 클라이언트보다 먼저 실행되어 대기해야 하며, 이렇게 생성된 소켓을 서버 소켓 또는 리스닝 소켓이라고 부른다.
#include <sys/socket.h>
 
// 1. 소켓의 생성
int socket(int domain, int type, int protocol);
	// 성공 시 파일 디스크립터, 실패 시 -1 반환
 
// 2. 주소의 할당
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
	// 성공 시 0, 실패 시 -1 반환
 
// 3. 연결요청 가능한 상태로 변경
int listen(int sockfd, int backlog);
	// 성공 시 0, 실패 시 -1 반환
 
// 4. 연결요청의 수락
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	// 성공 시 파일 디스크립터, 실패 시 -1 반환

cf. Application 레벨의 수신 확인(recv)

소켓 통신 실행 후, 데이터가 실제로 도착했을지 의문이 들어야 한다.

  • 초보자는 send() 함수가 에러 없이 반환(Return)되면 상대방이 데이터를 받았다고 착각한다.
  • 하지만 send()의 성공은 단지 내 컴퓨터의 OS 송신 버퍼에 데이터를 무사히 넘겨주었다는 뜻일 뿐, 상대방에게 도착했다는 뜻이 아니다.
  • (랜선이 끊어져 있어도 버퍼에 여유가 있다면 send()는 성공할 수 있다.)

그렇다면 데이터는 어떤 식으로 전송되고, 도착을 보장하는가?

  • 1단계: OS 레벨의 보장 (TCP)

    • 내 OS(TCP)가 상대방 OS(TCP)로 데이터를 보내면,
    • 상대방 OS는 잘 받았다는 기계적인 응답(TCP ACK)을 내 OS로 몰래 보내준다.
    • 이 과정 덕분에 데이터가 유실되면 OS가 알아서 재전송을 해준다.
  • 2단계: 애플리케이션 레벨의 보장 (의문의 핵심)

    • 상대방 OS 수신 버퍼에 데이터가 무사히 도착했다고 끝이 아니다.
    • 상대방 애플리케이션(APP)이 멈춰버렸거나 에러가 나서 수신 버퍼에 있는 데이터를 읽어가지 않았다면?
    • 즉, 데이터 전달의 진짜 최종 성공은 상대방 프로그램이 데이터를 읽고 정상적으로 처리했을 때 완성된다.

recv(): 완벽한 통신 완료의 마침표

  • 따라서 내가 보낸 데이터가 진짜로 상대방 프로그램에서 잘 처리되었는지 확인하려면, 상대방 프로그램이 처리 완료 후 잘 받았고 처리했다는 응답 메시지(Response Message)를 나에게 다시 send() 해주어야 한다.
  • 그리고 나는 그 응답 메시지를 recv() 함수로 읽어들였을 때 비로소 내가 보낸 데이터가 진짜로 완벽하게 전달되고 처리되었다고 확신할 수 있다.

1.2. 클라이언트

  • 클라이언트(발신자) 소켓의 생성 과정
    • 1단계. socket(): 소켓 생성
    • 2단계. connect(): 서버로 연결 요청 (전화를 거는 행위)
#include <sys/socket.h>
 
// 1. 연결의 요청
int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen);
	// 성공 시 0, 실패 시 -1 반환

2. 환경별 소켓 프로그래밍

2.1. 리눅스 기반의 파일 조작과 소켓

  • 리눅스는 소켓도 파일로 간주한다.

    • 리눅스 운영체제에서는 파일과 소켓을 동일하게 취급하므로,
    • 저수준 파일 입출력(Low-level File I/O) 함수를 소켓 통신에도 그대로 사용할 수 있다.
  • 저 수준 파일 입출력

    • ANSI의 표준함수가 아닌, 운영체제가 제공하는 함수 기반의 파일 입출력
    • 표준이 아니기 때문에 운영체제에 대한 호환성이 없다.
    • 리눅스는 소켓도 파일로 간주하기 때문에 저 수준 파일 입출력 함수를 기반으로 소켓 기반의 데이터 송수신이 가능하다.
  • 파일 디스크립터(File Descriptor, FD)

    • 운영체제가 파일이나 소켓을 구분하기 위해 부여하는 정수 값이다.
    • 기본 할당: 0(표준 입력), 1(표준 출력), 2(표준 에러)
    • 소켓이나 파일을 생성하면 3번부터 순차적으로 번호가 부여된다.
  • 주요 함수

    • open(): 파일 열기 (O_CREAT, O_RDONLY 등 오픈 모드 지정)
    • close(): 파일(또는 소켓) 닫기
    • write(): 파일/소켓에 데이터 쓰기 (송신)
    • read(): 파일/소켓에서 데이터 읽기 (수신)

2.2. 윈도우 기반 소켓 프로그래밍

  • 윈도우 환경(Winsock)에서 소켓 프로그래밍을 하려면

    • 리눅스와 달리 몇 가지 추가 설정과 전용 함수가 필요하다.
  • 헤더 및 라이브러리

    • 헤더파일 빈 칸 출제
#include <winsock2.h> // 헤더를 포함
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib") // 라이브러리를 링커에 추가해
  • 초기 설정 및 해제
    • WSAStartup()
      • 프로그램 시작 시 반드시 호출하여
      • 윈속 라이브러리를 메모리에 로드(초기화)해야 한다.
    • WSACleanup()
      • 프로그램 종료 직전에 호출하여
      • 할당된 윈속 라이브러리 리소스를 해제한다.

2.3. 리눅스와 윈도우 소켓 함수의 주요 차이점 요약

  • 가장 큰 차이점은 윈도우는 리눅스와 달리 파일과 소켓을 별개의 리소스로 엄격하게 구분한다는 것이다.
구분리눅스 (Linux)윈도우 (Windows)
식별자파일 디스크립터 (정수형 FD)핸들 (HANDLE / SOCKET 타입)
연결 종료close(fd)closesocket(s)
데이터 송신write() (파일/소켓 공용)send() (소켓 전용)
데이터 수신read() (파일/소켓 공용)recv() (소켓 전용)
실패 반환값보통 -1 반환INVALID_SOCKET 또는 SOCKET_ERROR 반환