← --library
이과 · 20이과

네트워크 및 정보 보안

단계1단계2단계3단계4단계5

1단계: 네트워크 심층 분석 — 패킷이 세상을 여행하는 방법


제1부. 이론적 기초 — 연결 이전의 세계

상상해봐. 부산에 사는 친구에게 편지를 보낸다고. 너는 편지를 쓰고(내용), 봉투에 넣고(포장), 주소를 쓰고(주소 표기), 우체국에 맡기면(운반), 우체국은 트럭과 기차와 비행기로 그 편지를 물리적으로 옮긴다(물리적 이동). 이때 편지를 쓰는 너는 "트럭이 어느 경로로 달리는지"를 알 필요가 없다. 우체국은 "편지 내용이 무엇인지"를 읽을 필요가 없다. 각자 자신의 역할만 하면 된다. 이것이 **네트워크 계층(Layer)**의 핵심 철학인 **관심사의 분리(Separation of Concerns)**다.

컴퓨터 네트워크는 1969년 **ARPANET(Advanced Research Projects Agency Network)**에서 출발했다. 당시 미국 국방부는 핵전쟁 상황에서도 살아남는 통신망을 원했다. 중앙 교환기가 파괴되면 전체가 마비되는 회선 교환(Circuit Switching) 방식 대신, 데이터를 작은 조각으로 분할하여 여러 경로로 분산시키는 패킷 교환(Packet Switching) 방식이 채택됐다. 이 결정 하나가 오늘날 인터넷의 근본 구조를 결정지었다.

여기서 잠깐 생각해봐. 네가 1GB짜리 파일을 친구에게 보낼 때, 파일 전체를 통째로 한 덩어리로 보내면 무슨 문제가 생길까? 작은 조각(패킷)으로 나눠 보내면 어떤 장점이 생길까? 이 질문의 답이 패킷 교환의 존재 이유 전부다.

[노트 기록] 패킷 교환 vs 회선 교환: 회선 교환은 통화 전 전용 경로를 확보하여 자원을 낭비하고 중앙에 의존한다. 패킷 교환은 데이터를 독립 패킷으로 분할하여 각 패킷이 독립적으로 최적 경로를 선택한다. 전자는 전화망, 후자는 인터넷의 기반이다.


제2부. OSI 7계층 — 세상을 쪼개는 방법

1984년 **ISO(International Organization for Standardization)**는 OSI(Open Systems Interconnection) 참조 모델을 발표한다. "참조 모델(Reference Model)"이라는 표현이 중요한데, 이는 실제 구현의 설계도가 아니라 **개념적 프레임워크(Conceptual Framework)**다. Tanenbaum의 Computer Networks(5판, Prentice Hall, 2011)는 "OSI 모델은 성공하지 못했지만 OSI 프로토콜은 성공했다"고 표현하는데, 이 말의 의미가 무엇인지 이 단원을 마치면 스스로 해석할 수 있어야 한다.

**7계층 응용(Application)**은 사용자가 직접 상호작용하는 계층이다. HTTP, FTP, SMTP, DNS가 여기 속한다. "데이터를 어떤 의미로, 어떤 형식으로 주고받을 것인가"를 정의한다. **6계층 표현(Presentation)**은 데이터의 인코딩, 압축, 암호화를 담당한다. JPEG, MP3, ASCII 등 데이터 형식 변환이 이 계층의 역할이며, SSL/TLS가 개념적으로 여기 위치한다. **5계층 세션(Session)**은 두 애플리케이션 간 논리적 연결(세션)의 생성·유지·종료를 관리한다. 실제 TCP/IP에서는 이 계층의 기능이 응용 계층에 흡수된다.

**4계층 전송(Transport)**이 본격적으로 흥미로워진다. **TCP(Transmission Control Protocol)**와 **UDP(User Datagram Protocol)**가 이 계층에 존재한다. 전송 계층은 종단 간(End-to-End) 통신의 신뢰성을 책임진다. TCP는 "데이터가 올바른 순서로, 손실 없이, 중복 없이 도착한다"는 것을 보장하는 반면, UDP는 이 보장을 포기하는 대신 속도를 얻는다. **포트 번호(Port Number)**가 이 계층에서 도입되어, 같은 IP를 가진 컴퓨터에서 여러 프로세스가 동시에 네트워크를 사용할 수 있게 한다. 예를 들어 80번 포트는 HTTP, 443번은 HTTPS, 22번은 SSH로 약속된 Well-Known Port다.

**3계층 네트워크(Network)**에는 **IP(Internet Protocol)**가 자리한다. 라우팅(Routing), 즉 패킷이 출발지에서 목적지까지 수십 개의 네트워크를 거쳐 전달되는 경로 결정이 이 계층의 임무다. IP 주소가 이 계층에서 사용되며, 전 세계 수십억 장치 중 특정 목적지를 식별한다. **라우터(Router)**는 이 계층에서 동작하며 수신한 패킷의 목적지 IP를 읽고 **라우팅 테이블(Routing Table)**을 참조하여 다음 홉(Next Hop)으로 전달한다. **2계층 데이터 링크(Data Link)**는 직접 연결된 두 장치 간 전송을 담당한다. MAC 주소가 이 계층에서 로컬 네트워크 내 장치를 식별하며, Ethernet과 Wi-Fi(IEEE 802.11)가 대표 프로토콜이다. 스위치(Switch)가 이 계층에서 동작하고 데이터 단위를 **프레임(Frame)**이라 부른다. **1계층 물리(Physical)**는 비트를 전기 신호, 광신호, 전파로 변환하는 물리적 전송을 담당한다. 이더넷 케이블의 전압 레벨, 광섬유의 광 펄스, Wi-Fi 안테나가 이 계층의 관심사다.

**캡슐화(Encapsulation)**는 OSI를 이해하는 열쇠다. 데이터는 응용 계층에서 출발해 각 계층을 내려가면서 해당 계층의 헤더(Header)(때로는 트레일러)가 앞뒤에 붙는다. 마치 편지를 봉투에 넣고, 그 봉투를 더 큰 상자에 넣는 과정이다. 목적지에서는 역순으로 각 계층이 자신의 헤더를 제거하며 데이터를 위로 올린다. 이것이 **역캡슐화(Decapsulation)**다.

[노트 기록] 각 계층의 데이터 단위(PDU: Protocol Data Unit): 7~5계층 = 메시지/데이터 | 4계층 = 세그먼트(TCP) / 데이터그램(UDP) | 3계층 = 패킷 | 2계층 = 프레임 | 1계층 = 비트. 이 이름들은 단순히 외우는 것이 아니라, 각 계층이 어떤 단위로 데이터를 처리하는지를 표현한 것임을 기억하라.


제3부. TCP/IP 스택 — 실제 인터넷의 설계도

OSI는 아름다운 이론이지만, 실제 인터넷은 TCP/IP 4계층 모델로 동작한다. 1970년대 Vint Cerf와 Bob Kahn이 설계한 이 모델은 OSI의 75계층을 응용 계층 하나로 통합하고, OSI의 21계층을 네트워크 접근 계층 하나로 통합했다. 이 단순화가 TCP/IP가 실제 인터넷에서 채택된 이유 중 하나다.

TCP의 내부로 들어가보자. TCP 3-way Handshake는 연결 수립의 핵심 메커니즘이다. 클라이언트가 서버에 SYN 패킷을 보내며 "연결하고 싶어, 내 시퀀스 번호는 X야"라고 신호한다. 서버는 SYN-ACK로 "알겠어, 네 X를 받았어(ACK=X+1), 내 시퀀스 번호는 Y야"라고 응답한다. 마지막으로 클라이언트가 ACK(ACK=Y+1)를 보내면 연결이 수립된다. 여기서 **시퀀스 번호(Sequence Number)**는 단순한 순서 번호가 아니라 TCP가 바이트 단위로 데이터 순서를 추적하는 핵심 메커니즘이다.

2-way Handshake가 아닌 3-way인가? 만약 SYN → SYN-ACK만으로 연결된다면 어떤 문제가 생길까? 힌트는 "지연된 SYN 패킷"과 "유령 연결"이다. RFC 793의 설계 이유를 직접 찾아보면 TCP 설계 철학의 절반을 이해하게 된다.

**혼잡 제어(Congestion Control)**는 TCP의 가장 정교한 메커니즘 중 하나다. 네트워크가 혼잡할 때 더 많은 패킷을 보내면 상황이 악화된다. TCP는 Slow Start로 시작해 **혼잡 윈도우(cwnd)**를 지수적으로 늘리다가 임계치(ssthresh)에 도달하면 선형으로 증가시킨다. 패킷 손실이 감지되면 cwnd를 절반으로 줄이는 이 AIMD(Additive Increase Multiplicative Decrease) 알고리즘은, 전 세계 수십억 개의 TCP 연결이 네트워크 자원을 공평하게 나눠 쓰게 만드는 놀라운 분산 알고리즘이다(Jacobson, V., 1988, "Congestion Avoidance and Control", ACM SIGCOMM).

IP 라우팅에서는 각 라우터가 라우팅 테이블에 "목적지 네트워크 → 다음 홉 IP → 인터페이스"를 저장한다. 인터넷 규모에서 이 테이블을 유지하는 프로토콜이 **BGP(Border Gateway Protocol)**로, 전 세계 수만 개의 AS(Autonomous System, 자율 시스템) 간 경로 정보를 교환한다. BGP가 잘못 설정되면(BGP Hijacking) 인터넷 트래픽 전체가 엉뚱한 곳으로 향할 수 있다는 사실은, 인터넷의 놀라운 취약성을 보여준다.

**DNS(Domain Name System)**는 응용 계층 프로토콜이지만 네트워크 통신의 전제 조건이므로 짚고 넘어가자. www.google.com을 입력하면 브라우저는 DNS 리졸버에 IP 주소를 질의한다. 리졸버는 Root NS → TLD NS(.com) → Authoritative NS(google.com) 순으로 계층적 질의를 수행해 최종 IP를 반환한다. 이 **재귀적 DNS 조회(Recursive DNS Resolution)**가 단 하나의 URL 입력 뒤에 숨겨진 복잡한 과정이다.

[노트 기록] TCP 세그먼트 헤더 핵심 필드: Source/Destination Port(각 16비트) | Sequence Number(32비트): 바이트 스트림에서 이 세그먼트의 시작 위치 | Acknowledgment Number(32비트): 다음에 받기를 기대하는 바이트 | Flags: SYN·ACK·FIN·RST·PSH·URG | Window Size(16비트): 흐름 제어용 수신 윈도우 크기.


제4부. 소켓 프로그래밍 — 코드로 연결하기

**소켓(Socket)**은 네트워크 통신을 위한 운영체제의 추상화 인터페이스다. 1983년 BSD Unix에서 처음 도입된 이 API는 40년이 지난 지금도 모든 네트워크 프로그래밍의 기반이다. Stevens의 UNIX Network Programming(3판, Addison-Wesley, 2003)은 이 분야의 바이블이다. Unix에서 소켓은 **파일 디스크립터(File Descriptor)**로 표현된다. 이는 Unix의 핵심 철학인 "모든 것은 파일이다(Everything is a file)"의 구현으로, socket() 호출이 정수 형태의 파일 디스크립터를 반환하면 이후 read(), write() 등 일반 파일 I/O 함수로 데이터를 주고받는다.

TCP 서버 소켓의 라이프사이클은 다음과 같다: socket()bind()listen()accept()read()/write()close(). 클라이언트는 socket()connect()read()/write()close()다. bind()는 소켓에 IP와 포트를 할당하고, listen()은 연결 요청 큐를 활성화하며, accept()는 완성된 3-way Handshake에서 새 연결을 꺼낸다. accept()가 새로운 소켓 파일 디스크립터를 반환한다는 점이 중요한데, 리스닝 소켓(Listening Socket)과 연결된 소켓(Connected Socket)을 분리하여 서버가 동시에 여러 클라이언트를 다룰 수 있게 하기 위함이다.

[노트 기록] 소켓 핵심 구조체(C언어): struct sockaddr_in의 주요 필드 — sin_family(AF_INET for IPv4), sin_port(포트, 네트워크 바이트 오더), sin_addr(IP 주소). 바이트 오더 주의: 네트워크는 Big-Endian을 사용한다. htons()(host to network short), htonl()(host to network long), ntohs(), ntohl()로 변환해야 한다. 이를 빠뜨리면 포트 번호가 엉뚱하게 해석되는 고전적 버그가 발생한다.


제5부. 비동기 I/O 모델 — 고성능의 비밀

2001년 Dan Kegel이 발표한 "The C10K Problem"(kegel.com/c10k.html)이라는 글이 있다. 하나의 서버가 동시에 10,000개의 연결을 처리하는 것이 당시 기술로는 거의 불가능했다. 왜인지, I/O 모델의 역사를 따라가며 이해해보자.

**블로킹 I/O(Blocking I/O)**가 가장 단순하다. read() 시스템 콜을 호출하면 데이터가 도착할 때까지 해당 스레드가 멈춘다. 직관적이지만, 한 스레드가 한 연결만 처리할 수 있다는 뜻이다. 클라이언트 1만 명을 처리하려면 스레드 1만 개가 필요한가? 스레드는 생성·전환·메모리(기본 스택 약 8MB) 비용이 크기 때문에, 컨텍스트 스위칭(Context Switching) 오버헤드만으로 시스템이 마비된다. **논블로킹 I/O(Non-blocking I/O)**는 소켓을 논블로킹 모드로 설정(fcntl(fd, F_SETFL, O_NONBLOCK))하면 데이터가 없을 때 read()가 즉시 EAGAIN을 반환한다. 한 스레드가 여러 소켓을 순회하며 읽기를 시도할 수 있지만, 바쁜 대기(Busy Waiting) 문제가 있다 — 데이터가 없어도 계속 read()를 호출하며 CPU를 낭비한다.

**I/O 멀티플렉싱(I/O Multiplexing)**이 이 문제를 해결한다. select(), poll(), epoll()(Linux), kqueue()(BSD/macOS) 같은 시스템 콜로 "이 소켓들 중 하나라도 준비되면 알려줘"라고 커널에 등록한다. 커널은 준비된 소켓이 생기면 반환하고, 없으면 스레드를 대기시킨다. 이제 단일 스레드가 수천 개의 소켓을 효율적으로 관리할 수 있다. select()poll()은 매번 전체 파일 디스크립터 목록을 커널에 전달해야 하므로 O(n)의 성능을 보인다. 반면 epoll()(Linux 2.5.44, 2002년 도입)은 관심 있는 파일 디스크립터를 epoll_ctl()로 미리 등록해두고, epoll_wait()으로 준비된 이벤트만 O(1)에 가깝게 가져온다. 연결이 수만 개일 때 select()epoll()의 성능 차이는 수십 배에 달한다.

**Reactor 패턴(Reactor Pattern)**은 이 I/O 멀티플렉싱 위에 구축된 설계 패턴이다. 이벤트 루프(Event Loop)가 I/O 이벤트를 감지하여 등록된 핸들러를 호출하는 구조로, Node.js(libuv), Nginx, Redis가 모두 이 패턴을 사용한다. Node.js가 "싱글 스레드임에도 수만 개의 동시 연결을 처리"하는 비결이 정확히 이 비동기 I/O + 이벤트 루프 조합이다. 단, CPU 집약적 작업은 별도의 스레드 풀(libuv Worker Threads)에 위임하므로, "Node.js는 싱글 스레드"라는 말이 정확히 무엇을 의미하고 무엇을 의미하지 않는지 이제 구분할 수 있어야 한다.

[노트 기록] I/O 멀티플렉싱 비교: select() = fd_set 배열, 최대 1024개 fd 제한, O(n) 스캔 | poll() = pollfd 배열, fd 수 제한 없음, O(n) 스캔 | epoll() = 이벤트 등록 후 준비된 것만 반환, O(1) 근접 (Linux) | kqueue() = epoll의 BSD 버전, macOS/FreeBSD | io_uring() = 최신 Linux 비동기 I/O (2019년), 커널-사용자 공간 공유 링 버퍼 방식.


제6부. HTTP/2 — 인터넷의 리팩토링

HTTP/1.1은 1997년 이래 10년 이상 인터넷의 근간이었다. 그러나 웹이 복잡해지면서 한계가 드러났다. 가장 치명적 문제는 **HOL 블로킹(Head-of-Line Blocking)**이다. HTTP/1.1은 하나의 TCP 연결에서 요청-응답이 직렬로 처리되어야 한다. 이전 응답이 완료되지 않으면 다음 요청을 보낼 수 없다. 브라우저들은 이를 우회하기 위해 동일 도메인에 여러 TCP 연결을 병렬로 열었는데(보통 6개), 이는 3-way Handshake 오버헤드와 TCP Slow Start가 반복되는 낭비였다. 또한 HTTP/1.1 헤더는 평문 텍스트이며 매 요청마다 반복 전송된다. User-Agent, Accept, Cookie 등 수백 바이트가 수백 번 중복 전송되는 구조적 비효율이다.

**HTTP/2(RFC 7540, 2015년)**는 Google의 SPDY 프로토콜을 기반으로 이 모든 문제를 해결했다. 핵심 개념 세 가지를 이해하자. 첫째, 바이너리 프레이밍(Binary Framing). HTTP/2는 모든 통신을 이진(Binary) 프레임으로 표현한다. 프레임은 Length, Type, Flags, Stream ID, Payload로 구성되며, 이진화는 파싱 효율을 높이고 모호성을 제거한다. 둘째, 스트림 멀티플렉싱(Stream Multiplexing). HTTP/2는 하나의 TCP 연결 위에 **스트림(Stream)**이라는 가상 채널을 여러 개 개설한다. 각 스트림은 독립적인 요청-응답 쌍이며, 여러 스트림의 프레임이 하나의 TCP 연결에서 인터리빙(Interleaving)된다. 단, 이는 HTTP 계층의 HOL 블로킹을 해결할 뿐, TCP 계층의 HOL 블로킹은 여전히 남아있다 — 이것이 HTTP/3의 등장 배경이다. 셋째, HPACK 헤더 압축(RFC 7541). 이전에 전송한 헤더를 **동적 테이블(Dynamic Table)**에 저장하고, 이후 동일 헤더는 인덱스만 전송한다. 새 헤더는 **허프만 코딩(Huffman Coding)**으로 압축한다. 이를 통해 헤더 크기를 평균 85~88% 줄일 수 있다.

**서버 푸시(Server Push)**는 클라이언트가 요청하기 전에 서버가 리소스를 미리 전송하는 기능이다. 브라우저가 HTML을 요청하면 서버는 그 HTML이 참조하는 CSS, JS를 미리 푸시한다. 개념은 좋지만, 캐시 충돌 등의 실무 문제로 Chrome이 2022년 서버 푸시 지원을 제거하는 등 트레이드오프가 복잡하다.


제7부. HTTP/3 / QUIC — UDP 위의 혁명

HTTP/2의 남은 문제는 TCP에서 비롯된다. TCP는 패킷 손실 시 재전송이 완료될 때까지 전체 연결을 대기시킨다. 10개의 스트림이 동시에 진행 중이어도, 하나의 패킷 손실이 모든 스트림을 묶어버린다. 이것이 TCP 레벨 HOL 블로킹이다. 특히 패킷 손실이 잦은 이동통신 환경에서는 HTTP/2가 HTTP/1.1보다 오히려 느릴 수 있다.

**QUIC(Quick UDP Internet Connections)**는 Google이 2013년부터 개발하고 IETF가 RFC 9000(2021년)으로 표준화한 전송 프로토콜이다. QUIC은 UDP 위에 구축된다.

잠깐 생각해봐: UDP는 "신뢰성이 없다"고 배웠는데, 어떻게 QUIC이 UDP를 쓰면서 신뢰성을 보장할까? 힌트는 "신뢰성은 TCP의 고유 기능이 아니라, 누군가가 응용 계층에서 구현할 수 있는 기능"이다. TCP가 신뢰성을 구현한 것처럼, QUIC도 그것을 직접 구현했다.

QUIC이 UDP를 선택한 이유는 오래된 미들박스(Middlebox) 문제 때문이다. 방화벽, NAT, 프록시 등 네트워크 장비들이 오랜 시간 TCP를 기준으로 설계되어 TCP 헤더를 파싱하고 조작한다. 새로운 TCP 변형을 추가하면 이 미들박스들이 패킷을 차단하거나 변형시킨다. UDP는 미들박스가 거의 건드리지 않으므로, QUIC은 UDP를 캔버스 삼아 신뢰성, 암호화, 멀티플렉싱을 자체 구현했다.

QUIC의 핵심 특징을 살펴보자. 0-RTT 연결 수립: HTTP/1.1 + TLS 1.2는 첫 연결에 최소 3RTT(TCP Handshake 1RTT + TLS 2RTT), HTTP/2 + TLS 1.3은 최소 1RTT, QUIC은 이전에 연결한 서버에 대해 0-RTT로 데이터 전송을 시작한다. 단, 0-RTT 데이터는 재전송 공격(Replay Attack)에 취약하므로 멱등성(Idempotent) 요청에만 적용된다. 스트림 레벨 독립성: QUIC의 스트림은 완전히 독립적이어서, 한 스트림의 패킷 손실이 다른 스트림에 영향을 주지 않는다. 이것이 TCP HOL 블로킹을 근본적으로 해결하는 방법이다. 연결 이주(Connection Migration): QUIC 연결은 IP와 포트가 아닌 **연결 ID(Connection ID)**로 식별된다. Wi-Fi에서 LTE로 전환되어 IP가 바뀌어도 연결 ID가 동일하면 기존 연결이 유지된다. TCP는 4-tuple(소스 IP, 소스 포트, 목적지 IP, 목적지 포트)로 연결을 식별하므로 IP가 바뀌면 연결이 끊긴다. 통합 암호화: QUIC은 TLS 1.3을 전송 계층에 통합하여, 암호화되지 않은 QUIC 연결 자체가 존재하지 않는다.

HTTP/3은 HTTP/2의 의미론(Semantics)을 QUIC 위에 올린 버전이다. HPACK 대신 **QPACK(RFC 9204)**을 사용하는데, 이는 QUIC의 스트림 순서 비보장 특성에 맞게 동적 테이블 업데이트를 별도 스트림으로 분리한 설계다. 2023년 기준 전 세계 웹 트래픽의 약 30%가 HTTP/3을 사용하고 있다(Cloudflare Radar 통계).

[노트 기록] 프로토콜 진화 비교: HTTP/1.1 = TCP·텍스트·직렬 요청·TLS 별도 | HTTP/2 = TCP·바이너리·스트림 멀티플렉싱·HPACK·TCP HOL 블로킹 존재 | HTTP/3 = QUIC(UDP 기반)·바이너리·스트림 독립·QPACK·0-RTT·연결 이주.


제8부. 프로젝트 — 스스로 구현해보기

이제 이론을 코드로 구현할 시간이다. 정답은 제공하지 않는다. 막히면 Stevens의 UNIX Network Programming, RFC 문서, man 페이지를 참조하라. Python을 사용해도 좋고, C를 선택해도 좋다. C라면 훨씬 많이 배울 것이다.


[프로젝트 1] 원시 TCP 에코 서버 및 클라이언트 (예상 소요: 10분)

Python 또는 C를 사용해 TCP 에코 서버와 클라이언트를 구현하라. 서버는 클라이언트로부터 임의의 텍스트를 수신하면 그대로 돌려보낸다. 단, 서버는 클라이언트가 연결을 끊어도 종료되지 않고 다음 연결을 계속 기다려야 한다. 구현 후 스스로 답해보라: "동시에 두 명의 클라이언트가 연결 시도를 하면 어떻게 될까? 두 번째 클라이언트는 서버가 첫 번째 클라이언트와의 통신을 마칠 때까지 대기하는가, 아니면 즉시 거부당하는가? 그 이유는 listen()의 인자인 backlog 파라미터와 어떤 관계가 있는가?"


[프로젝트 2] select() 기반 멀티클라이언트 서버 (예상 소요: 15분)

프로젝트 1의 서버를 select()(또는 Python의 selectors 모듈)를 사용해 단일 스레드로 여러 클라이언트를 동시에 처리하도록 개조하라. 리스닝 소켓과 클라이언트 소켓을 동일한 감시 목록에 포함시켜야 한다. 구현 후 답해보라: "클라이언트 수가 5,000개로 늘어나면, 이 select() 구현이 왜 느려지는가? 내부적으로 매번 무슨 일이 일어나기 때문인가? 이를 해결하기 위해 epoll()로 전환하면 어떤 구조적 변화가 필요한가?"


[프로젝트 3] HTTP/1.1 vs HTTP/2 성능 비교 실험 (예상 소요: 10분)

Python의 httpx 라이브러리를 사용해 동일한 공개 서버(HTTP/2를 지원하는 서버)에 HTTP/1.1과 HTTP/2로 각각 50개의 병렬 요청을 보내고, 전체 완료 시간을 측정·비교하라. httpxhttp2=True 파라미터를 지원한다. asynciohttpx.AsyncClient를 조합하면 비동기 병렬 요청을 구현할 수 있다. 측정 항목은 첫 번째 응답의 **TTFB(Time To First Byte)**와 전체 50개 요청의 총 완료 시간이다. 결과를 보고 답해보라: "어떤 조건에서 HTTP/2가 명확히 빨랐는가? 반대로, 단 하나의 대용량 파일을 요청할 때는 두 버전 간 차이가 왜 거의 없는가?"


[프로젝트 4] 패킷 캡처 및 TCP Handshake 해부 (예상 소요: 10분)

scapy(Python) 또는 tcpdump를 사용해 특정 웹사이트 접속 시 발생하는 TCP 3-way Handshake 패킷을 캡처하라. tcpdump -i any -n 'tcp and host <목적지IP>'로 시작해보라. 캡처된 패킷에서 다음 항목을 식별하고 기록하라: SYN 패킷의 시퀀스 번호, SYN-ACK 패킷의 시퀀스 번호와 확인 번호, 최종 ACK 패킷의 확인 번호, 각 패킷의 Window Size, 그리고 SYN 패킷에 포함된 MSS(Maximum Segment Size) 옵션 값. 분석 후 답해보라: "SYN 패킷의 시퀀스 번호가 0이 아닌 임의의 큰 수에서 시작하는 이유는 무엇인가? 만약 항상 0에서 시작한다면 어떤 보안 또는 신뢰성 문제가 생길 수 있는가?"


이 단계를 마치면, 단순히 "OSI 7계층이 있다"를 아는 수준을 넘어, 패킷이 물리 신호에서 출발해 전 세계 라우터를 거쳐 애플리케이션까지 도달하는 모든 계층을 설명하고, 소켓 API로 네트워크 프로그램을 직접 작성하며, 비동기 I/O가 왜 고성능 서버의 핵심인지를 코드 수준에서 이해하고, HTTP/2와 HTTP/3이 이전 프로토콜의 어떤 구조적 문제를 어떻게 해결했는지를 프로토콜 내부 구조로 설명할 수 있어야 한다. 2단계에서는 이 기반 위에 분산 합의 알고리즘과 침투 테스트로 나아간다.

단계 2