도입
1. 네트워크
네트워크는 왜 필요하며, 어떻게 데이터를 목적지까지 전달하는가?
-
① 통신의 물리적 한계와 네트워크의 탄생
- 두 컴퓨터를 선으로 직접 연결하여 통신할 때, 거리가 멀어지면 신호에 노이즈가 끼고 약해진다.
- 단순히 송신 전압을 높이는 것은 에너지 소모가 커 비효율적이므로, 중간에 데이터를 받아서 다음으로 넘겨주는 중계 방식을 택하게 되었고, 이것이 확장되어 네트워크 망(Network)이 되었다.
-
② 데이터의 신호 변환과 전송
- 컴퓨터의 데이터는 네트워크 하드웨어(버퍼)를 거쳐 케이블이나 무선 매체를 탈 수 있는 물리적인 신호(Signal)로 변환되어 전송된다.
- 이때 신호는 매체와 방식에 따라 다양한 형태를 가진다.
-
③ 최적의 경로 탐색 (라우팅과 IP)
- 복잡하게 얽힌 네트워크 망에서 목적지까지 도달하려면 가장 효율적인 길(Shortest Path)을 찾아야 한다.
- 이러한 길 찾기 기능을 수행하는 장비가 라우터(Router)이며, 목적지의 위치를 정확히 식별하고 찾아갈 수 있도록 지도/주소 역할을 하는 것이 IP이다.
2. 데이터 전송 흐름
- 네트워크 통신은 크게 다음 3가지 영역을 거쳐 전달된다.
- 애플리케이션 영역 (User Space): 통신을 요청하고 데이터를 생성/수신하는 프로그램
- 운영체제 커널 영역 (Kernel Space): 데이터를 통신 규약(TCP/IP)에 맞게 쪼개고 포장하는 OS
- 하드웨어 영역 (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)
-
APP A (send(A_1))
- Host A의 애플리케이션이 전송할 데이터를 만들고 send() 시스템 콜을 호출한다.
-
소켓 Lib (Socket Library)
- 애플리케이션의 요청을 운영체제(OS)가 이해할 수 있도록 연결해 주는 API(인터페이스) 역할을 한다.
-
소켓 A_1 (송신 버퍼)
- send()는 데이터를 즉시 내보내는 것이 아니다.
- OS 메모리 공간에 할당된 소켓 A_1의 송신 버퍼(Send Buffer)에 데이터를 복사해 두고 애플리케이션은 자기 할 일을 하러 돌아간다.
-
전송 Protocol (TCP/UDP - L4)
- OS 커널은 송신 버퍼에 쌓인 데이터를 꺼내어 적절한 크기로 자르고,
- 포트(Port) 번호를 붙여 세그먼트로 만든다.
- (TCP의 경우 신뢰성 제어 추가)
-
Network Protocol (IP - L3)
- 출발지와 목적지의 IP 주소를 붙여 패킷으로 만든다. (길 찾기 준비)
-
Data 전송 Protocol (L2 데이터링크 & L1 물리)
- MAC 주소를 붙이고(프레임), 최종적으로 랜카드(NIC)를 통해 0과 1의 전기/광 신호로 변환하여 케이블로 내보낸다.
(2) 연결선 (네트워크 망)
- 인터넷 망, 라우터, 광케이블 등 물리적 매체를 통해 신호가 Host B를 향해 날아간다.
(3) 수신 측 데이터 흐름 (Decapsulation)
-
Data 전송 Protocol (L1/L2)
- Host B의 랜카드가 전기 신호를 수신하여 데이터로 변환하고,
- 자신의 MAC 주소가 맞는지 확인한다.
-
Network Protocol (IP - L3)
- IP 주소를 확인하여 자신에게 온 데이터가 맞는지 검사한다.
-
전송 Protocol (TCP/UDP - L4)
- 포트(Port) 번호를 확인하여 어떤 소켓(애플리케이션)으로 보낼지 결정하고,
- 데이터 조각들을 원래 순서대로 조립한다.
-
소켓 B_1 (수신 버퍼)
- 조립된 데이터는 소켓 B_1의 수신 버퍼(Receive Buffer)에 차곡차곡 쌓인다.
- (아직 앱이 가져가지 않은 상태)
-
소켓 Lib
- 수신 버퍼에 데이터가 도착했음을 애플리케이션에 알린다.
-
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(): 서버로 연결 요청 (전화를 거는 행위)
- 1단계.
#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 반환 |