[CS] CSAPP : 11장 네트워크 프로그래밍

11.1 클라이언트-서버 프로그래밍 모델

모든 네트워크 응용 프로그램은 클라이언트-서버 모델에 기초한다.

1. 클라이언트-서버 모델

  • 하나의 애플리케이션은 서버 프로세스와 하나 이상의 클라이언트 프로세스로 구성된다
  • 서버는 특정 자원을 관리하고, 자원을 조작하여 클라이언트에게 서비스를 제공한다
    • 예) 웹 서버는 디스크에 저장된 파일을 관리하고 클라이언트 요청에 따라 반환

2. 트랜잭션 (Transaction)

클라이언트-서버 간 상호작용은 4단계 트랜잭션으로 구성

  1. 클라이언트가 서비스를 필요로 할 때, 서버에 요청(request) 보냄 (Client : ”스펀지밥 이미지 줘”)
  2. 서버는 요청을 받고, 해석, 자원을 조작 (Server : ”스펀지밥이 어딨는지 디스크에서 읽어보자..”)
  3. 서버가 응답을 클라이언트에게 전송 (Server: “오키 드림”)
  4. 클라이언트가 응답을 받아 처리 (Client: “화면에 띄움. 사용자야 이거야 스펀지밥”)

3. 중요 개념

  • 클라이언트와 서버는 프로세스이다. (물리적 호스트가 아니다)
  • 하나의 호스트에서 여러 클라이언트 및 서버가 동시 실행 가능
  • 클라이언트와 서버가 어디 호스트에 있든 모델은 동일하게 적용

11.2 네트워크

1. 네트워크는 I/O 장치처럼 동작한다

  • 호스트 입장에서는 네트워크는 단순한 입출력 장치이다
  • 네트워크 어댑터를 통해 데이터를 전송, 수신.
  • 시스템 버스를 통해 메모리와 직접 연결된다. (DMA 방식 사용)*
  • ✅ 왜 DMA 방식일까?

2. 물리적 네트워크 계층 구조

🔽 하위 계층: LAN (Local Area Network)

  • 가장 기본적인 단위는 LAN(Local Area Network). 이를 구현하기 위해 가장 많이 쓰이는 기술이 Ethernet
  • Ethernet 구성 요소
    • 이더넷 세그먼트 : 전선과 허브로 구성. 여러 호스트를 연결하는 물리적 전송 매체. (100 Mb/s or 1Gb/s 의 비트 대역폭을 가진다)
    • 이더넷 어댑터 : 호스트(컴퓨터) 내부에 장착된 네트워크 인터페이스 장치 (NIC : Network Interface Card). 프레임 단위로 데이터를 송수신함. 전 세계적으로 고유한 48비트 MAC 주소를 갖는다.
    • *<논리적 구성 요소 (구조/연결 관점)>**
    • 허브 : 여러 호스트 세그먼트를 묶어주는 장치. 모든 포트로 받은 데이터를 복사하여 브로드캐스트.
    • 브릿지 : 여러 세그먼트를 연결하며 목적지 MAC 주소 기반으로 데이터 전달을 효율화. 허브보다 더 높은 전선 대역폭을 가짐.
  • <물리적 구성 요소 (하드웨어)>
  • 프레임 : 네트워크에서 전달되는 데이터 전송 단위. 이더넷에서 어댑터는 데이터를 네트워크로 보내기 전에 프레임으로 포장한다.
    • 프레임 구성
    • 헤더 : 전송 제어용 정보 포함
      • 목적지 MAC 주소
      • 출발지 MAC 주소
    • 페이로드 : 실제 전송할 데이터 (예, 웹 요청, 파일 조각 등)
  • 이더넷 세그먼트를 브리지를 통해 연결. 대역폭을 효율적으로 사용할 수 있게 한다
    • 왜 더 효율적?
    • → 모든 프레임이 전체 네트워크에 퍼지지 않고 목적지로만 향함.
    • → 전체 네트워크 충돌 가능성 감소 + 병렬 처리 가능성 증가
  • → 여러 브리지-브리지 간 연결로 더 큰 LAN 구성할 수 있다.

🔼 상위 계층: 라우터를 통한 Inter-LAN 연결

  • 라우터는 네트워크 간 연결을 구성한다. 각 LAN을 구분하고, IP 패킷을 적절한 LAN으로 전달한다
  • 물리적으로 서로 다른 네트워크 기술( 이더넷 ↔ 와이파이 )을 라우터가 중개 가능
  • WAN (Wide Area Network) : 여러 지역에 흩어진 네트워크를 라우터들이 Point-to-point 연결로 이어서 구성하는 네트워크

3. 프로토콜의 기능

  • 프로토콜 : 여러 장치/시스템이 데이터를 주고 받을 수 있도록 약속된 “언어와 규칙”을 사용하는 것
  • 기능 1 : 명명법 (”어디로, 누구에게 가!”)
    • 어떤 호스트나 프로세스를 전 세계적으로 고유하게 식별하기 위한 시스템
    • MAC 주소 : 이더넷 어댑터의 고유한 48비트 주소 → 로컬 LAN 식별용
    • IP 주소 : 전 세계적으로 유일한 32비트 주소 (IPv4 기준) → 인터넷 전체 범위에서 호스트 식별
    • 포트 번호 : 호스트 내에서 실행 중인 응용 프로세스 식별
  • 기능 2 : 전달 기법 (”어떻게 갈까?”)
    • IP 패킷 : IP 계층에서 목적지 IP 주소 기반으로 라우팅 수행
    • 라우터 : IP 헤더를 보고 다음 홉 (next hop) 결정
    • 링크 계층 : 목적지 MAC 주소를 통해 실제 데이터 전달

4. 데이터의 이동 과정

internet에서 데이터가 호스트 A에서 호스트 B로 이동하는 과정에는 다음과 같은 단계가 있다.

  1. 클라이언트에서 커널로 데이터 복사
    • 클라이언트 응용 프로그램이 시스템 콜을 호출
    • 사용자 버퍼의 데이터를 커널 버퍼로 복사
  2. 데이터 캡슐화 및 LAN1 프레임 생성
    • 커널의 프로토콜 소프트웨어가..
      • 데이터에 IP 헤더 추가 (→ 인터넷 패킷 생성)
      • IP 패킷에 LAN1 프레임 헤더 추가 (→ 목적지 MAC은 라우터)
    • 이더넷 프레임 완성 → 어뎁터에 전달
  3. LAN1 어댑터가 네트워크로 전송
    • LAN1 어댑터가 프레임을 전기 신호로 네트워크에 전송
  4. 라우터가 프레임 수신
    • 라우터의 LAN1 어댑터가 수신 → 프로토콜 소프트웨어에 전달
  5. 라우팅 결정 및 프레임 재구성
    • 라우터가 IP 헤더를 읽고 목적지 IP를 확인
    • 라우팅 테이블을 조회해 다음 홉이 LAN2임을 결정
    • LAN1 프레임 헤더 제거 → LAN2 프레임 헤더 생성 (목적지 MAC은 Host B)
    • 어댑터로 전달
  6. 라우터의 LAN2 어댑터가 새 프레임을 네트워크로 전송
  7. Host B가 프레임 수신 → 프로토콜 소프트웨어로 전달
  8. 데이터 최종 전달
    • 프로토콜 소프트웨어가 프레임 헤더와 IP 헤더 제거
    • 나머지 순수 데이터를 응용 프로세스의 가상 주소 공간으로 복사 (서버가 system call로 읽음)

11.3 글로벌 IP 인터넷

TCP/IP 프로토콜

  • 인터넷은 TCP/IP 프로토콜 스택이라는 계층적 프로토콜 체계에 의해 운영된다
  • 각 계층은 상하 인접 계층과만 통신.
  • 캡슐화를 통해 데이터 전달
계층 주요 프로토콜 기능
응용 계층 (Application) HTTP, FTP, DNS 등 사용자 수준 서비스 제공
전송 계층 (Transport) TCP, UDP 프로세스 간 데이터 전송, 오류 제어
인터넷 계층 (Internet) IP 호스트 간 패킷 라우팅 및 주소 지정
네트워크 엑세스 계층
(Network Access)
이더넷, PPP 등 동일 네트워크 내 프레임 전송

 

✅ TCP vs UDP

구분 TCP UDP
신뢰성 Reliable Unreliable
연결성 Connection-oriented Connection less
재전송성 재전송 요청함 (오류 및 패킷 손실 시) 재전송 없음
특징 Flow control을 위해 windowing 사용 신뢰성을 보장하지 않지만, 고속 데이터 전송
용도 신뢰성이 필요한 통신
(HTTP, SMTP, FTP)
총 패킷수가 적은 통신
(스트리밍, 실시간 게임, DNS 요청)

 

TCP : 전화. 상대방 번호를 알아야 전화 걸 수 있음. 동일하게 상대방 IP 주소와 Port 번호를 알아야함. 그리고 전화 걸어도 받을때까지 대기해야함.

UDP : 편지. 편지가 왔는지 안왔는지 우편함 열어봐야 앎. 데이터가 와도, UDP 소켓을 통해서 자신의 포트를 열어봐야 데이터가 도착했는지 알 수 있음.

11.3.1 IP 주소

  • 인터넷의 각 호스트는 고유한 32비트 IP 주소를 갖늗나
    • 표현 방식 : 점으로 구분된 십진수 (dotted-decimal)
    • 내부적으로는 네트워크 바이트 순서(big-endian)의 4바이트 이진수
  • 변환 함수
    • inet_pton : 문자열 → 이진 IP
    • inet_ntop : 이진 IP → 문자열
    • → 시스템 간 IP 주소를 표준화된 형식으로 주고 받기 위해 필수
  • Big Endian vs Little Endian
    • 여러 바이트로 구성된 데이터를 메모리에 저장할 때, 바이트 순서를 어떻게 정할 것인가 정하는 방식
    • 특히 멀티바이트 숫자 (2, 4, 8바이트 등)을 저장하거나 전송할 때 중요
    • 네트워크 전송은 항상 Big Endian 사용하지만, Intel CPU(x86)은 내부적으로 Little Endian 사용
    • → 그래서 데이터를 네트워크로 보낼때 htonl() (host to network long), 받을때는 ntohl() (network to host long) 같은 함수로 바이트 순서를 변환한다.
구분 Big Endian Little Endian
저장 방식 가장 큰 바이트(상위 바이트)를 먼저 저장 가장 작은 바이트(하위 바이트)를 먼저 저장
사람에게 익숙한 방식 예 (왼→오 읽기) 아니오 (오→왼 읽기)
사용 예 네트워크 (IP, TCP 등), 일부 RISC CPU x86 계열 CPU (Intel, AMD 등)

11.3.2 인터넷 도메인 이름

  • 도메인 이름이란?
    • 사람은 숫자로 된 IP주소 대신 기억하기 쉬운 문자열 주소를 사용하기 위해 만듬
    • www.google.com
  • 도메인 이름의 계층 구조
    • 루트 (.) : 최상위 도메인 (com, edu, org 등)
    • 2차 도메인 : google
    • 3차 이하 도메인 : mail
    • 최종 : mail.google.com
    • ⇒ 모두 google.com 이라는 2차 도메인 아래에서 서브 도메인 (mail, maps, drive)으로 구분됨
Root (.)
 └── com
     └── google
         └── mail

🔁 도메인 ↔ IP 매핑: DNS 시스템

  • 예전에는 HOSTS.TXT 라는 수동 파일로 관리
  • 현재는 DNS (Domain Name System)라는 전 세계 분산 데이터베이스가 담당
    • 입력 : 도메인 이름
    • 출력 : 해당하는 하나 이상의 IP 주소
  • 하나의 도메인 → 여러 IP 가능
  • 여러 도메인 → 동일 IP 가능. 즉, 일대다 / 다대일 매핑 모두 가능

🔁 localhost

  • 각 인터넷 호스트는 지역적으로 정의된 도메인 이름인 localhost를 가지고 있고
  • 항상 루프백 주소 127.0.0.1 에 매핑된다
  • nslookup (domain name) 으로 IP 주소 확인 가능

11.3.3 인터넷 연결

인터넷 연결이란?

  • 호스트(컴퓨터)가 인터넷에 접속하려면, ISP (인터넷 서비스 제공자)를 통해 라우터에 연결됨
  • 라우터는 하나 이상의 네트워크 인터페이스(어댑터)를 통해 외부 인터넷과 통신
  • TCP 기반 통신은 신뢰성 있는 바이트 스트림을 양방향으로 주고받는 방식
  • → 양쪽 모두 읽기/쓰기가 가능하여 완전한 양방향 통신(Full-Duplex)

연결 방식

  1. 로컬 네트워크 → 라우터
    • 사용자 컴퓨터는 이더넷 또는 무선 네트워크를 통해 로컬 라우터에 연결
  2. 라우터 → ISP 백본 → 인터넷
    • 실제 전기 신호/패킷이 지나가는 물리적 경로 → 데이터를 어디로 보낼지 결정
    • 라우터는 ISP 핵심망 (backbone)에 연결됨
    • 중간에 있는 여러 라우터를 거쳐 데이터가 목적지 호스트에 전달됨
  3. 소켓과 종단점
    • 애플리케이션 수준의 논리적 연결 관계 → 데이터를 누구에게 보낼지 식별
    • 소켓 : 프로세스가 네트워크 통신을 하기 위해서 사용하는 추상적 I/O 객체
    • 소켓의 종단점(endpoint)은 아래 정보로 유일하게 식별됨
      • IP 주소
      • 포트 번호
    • (cliaddr : cliport, servaddr : servport) : (클라이언트 IP : 클라이언트 포트, 서버 IP : 서버 포트)
    • 이 때 클라이언트가 열어주는 포트는 커널이 할당한 단기 포트 (ephemeral port) : 49152 ~ 65535 범위
    • 서버 포트는 지정된 고정 포트 : 80 (HTTP), 443 (HTTPS), 22 (SSH)
    • ✅ 왜 서버는 고정 포트를 사용하고, 클라이언트는 단기 포트를 사용하는가?
      • 서버는 외부에서 요청을 “들어야” 하기 때문에 항상 고정된 포트에 바인딩(bind) 필요
      • 클라이언트는 그럴 필요 없음.
      • 서버는 114라는 고객센터 번호를 열어두고, 클라이언트의 요청을 대기함→ 심지어 클라이언트가 한명일수도 여러명일수도 모름!
      • → 하지만 클라이언트는 서버를 찾아가려면, 꼭 114라는 번호를 누르고 찾아가는거임. 다른 번호로 전화 걸면 다른 서버거나, 엇? 여기 그런데 아닌데요 라며 에러를 띄움 (Connection refused)
      • → 클라이언트가 언제, 어떤 번호로 전화할지 모름!

11.4 소켓 인터페이스

소켓 인터페이스란?

  • 네트워크 응용 프로그램을 만들기 위한 시스템 호출 API 집합
  • 파일 I/O와 유사하게 작동하는 추상화된 방식

11.4.1 소켓 주소 구조체

  • 소켓은 커널 입장에서의 “통신 종단점”
  • 소켓은 통신을 위한 파일 디스크립터처럼 사용되는 객체이다
  • 프로그램 관점에서는 소켓은 통신을 위한 식별자를 가진 파일처럼 다뤄진다 (read, write, close 등)
/*IP Socket address structure*/
struct sockaddr_in{
    uint16_t sin_family; // 주소 체계 (AF_INET : IPv4)
    uint16_t sin_port;   // 포트 번호 (네트워크 바이트 순서, big endian (16비트))
    struct in_addr sin_addr;  // IP 주소 (32비트, 네트워크 바이트 순서)
    unsigned char sin_zero[8]; // 패딩용 바이트 (구조체 크기 맞춤용으로 사용안함)
}
/*Generic socket address structure (for connect, bind, accept)*/
struct sockaddr{
    uint16_t sa_family;   // Protocol family
    char sa_data[14];     // Address data
}
  • 왜 이렇게 설계되었나?
    • 초기 C언어에는 void* 타입이 없어서
    • 다형성을 흉내내기 위해 범용 구조체를 사용
  • 왜 포인터로 전달하는가?
    • 아래 함수에서 IPv4, IPv6, Unix 도메인 소켓 등 다양한 주소 체계를 지원해야 하기 때문에, 구체적인 주소 구조체(sockaddr_in, sockaddr_in6, sockaddr_un)가 아닌, 공통 상위 타입 struct sockaddr * 를 인자로 받음.
    • 따라서 우리가 사용하는 struct sockaddr_in을 전달하려면 명시적으로 struct sockaddr *로 형변환해야 합니다.
    • C에서는 struct sockaddr_in *struct sockaddr *암시적 형변환이 되지 않기 때문에, 컴파일러 오류 없이 넘기기 위해 명시적 캐스팅이 필요합니다

11.4.2 ~ 4.6 구현 함수

  1. socket()
    • 소켓 생성 함수
    • 반환된 소켓은 파일 디스크립터처럼 사용
    • 아직 연결되거나 바인딩되지 않은 초기 상태의 소켓
    • #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); // Return : nonnegative descriptor if OK, -1 on error
    • domain : IPv4, IPv6 설정
    • type : TCP (SOCKET_STREAM), UDP(SOCKET_DGRAM)
    • protocol : IP 사용시 0
  2. connect()
    • 클라이언트용 함수
    • 서버의 IP와 포트 번호가 담긴 주소 구조체를 인자로 받아 연결을 시도
    • 연결이 성공하면 해당 소켓은 양방향 데이터 송수신 가능 상태
    • #include <sys/socket.h> int connect(int clientid, const struct sockaddr *addr, socklen_t addrlen); // Return 0 if OK, -1 on error
    • clientid : 클라이언트의 소켓 디스크립터
    • addr : 서버의 IP 주소와 Port 번호를 담고있는 주소 구조체의 포인터. sockaddr*로 형변환 하여 전달.
    • addrlen : addr 구조체의 크기 (주소 길이)
  3. bind()
    • 서버용 함수
    • 소켓에 IP 주소와 포트 번호를 명시적으로 연결
    • 서버는 고정된 포트에서 클라이언트의 요청을 받기 위해 사용
    • #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // Returns 0 if OK, -1 on error
    • sockfd : socket()의 리턴값
  4. listen()
    • 서버가 bind로 주소를 지정한 후, 이 소켓을 클라이언트의 연결 요청을 받을 수 있도록 설정
    • backlog 인자로 대기열 크기를 설정 (연결 요청을 저장하는 큐의 길이 = 연결 가능한 클라이언트 수)
    • #include <sys/socket.h> int listen(int sockfd, int backlog); // Return 0 if OK, -1 on error
      • 서버는 accept()를 호출하고 대기 상태로 들어감
        • 이때 listenfd 는 리스닝 소켓
        • 아직 클라이언트 연결 요청이 없으니 블로킹 상태에 들어감.
      • 클라이언트가 connect()을 호출하여 연결 요청
        • 클라이언트가 서버의 IP 및 포트로 연결 시도
        • 커널은 이 요청을 리스닝 소켓 큐에 추가
      • 서버 accept() 함수가 연결을 수락
        • 커널은 클라이언트 요청을 수락
        • 새로운 연결 소켓(connfd)을 생성
        • 클라이언트 주소 정보(addr)을 채우고 connfd 반환
      • ✅ 왜 accept()는 클라이언트가 없으면 블로킹 되는가?
        • 클라이언트 요청이 없는데, connfd를 돌려줄 수 없으니까! → 그냥 연결 올때까지 대기하는거임
      • ✅ 리스닝 소켓 큐는 메모리 어디에 위치하나?
        • 운영 체제 메모리는 사용자 공간과 커널 공간으로 나뉨.
        • 결론: 커널 공간의 소켓 자료구조 내 존재한다.
        • 운영체제 메모리accept()
    • ✅ 왜 accept()는 새 소켓을 만드는가?
      • 연결마다 독립된 입출력 채널이 필요하다.
      • 그래야 서버가 여러 클라이언트를 동시에 or 순차적으로 처리할 수 있다
    • ✅ 왜 accept()는 새 소켓을 만드는가?
      • 연결마다 독립된 입출력 채널이 필요하다.
      • 그래야 서버가 여러 클라이언트를 동시에 or 순차적으로 처리할 수 있다.
      • 연결 수락되면 fork() 로 자식 프로세스 생성
      • 자식은 새로 생성된 connfd만 가지고 클라이언트 처리서버의 구조에 따라 새 소켓을 만드는 방식의 차이가 있다! → 본질은 똑같다.
    • 🔹 프로세스 기반 서버
      • 연결 수락되면 fork() 로 자식 프로세스 생성
      • 자식은 새로 생성된 connfd만 가지고 클라이언트 처리
    • 🔹 스레드 기반 서버
      • 연결 수락되면 pthread_create() 등으로 새 스레드 생성
      • 새 스레드는 넘겨받은 connfd로 클라이언트 요청 처리

11.5 웹 서버

11.5.1 웹 기초

  • HTTP 개요 : 웹 클라이언트와 서버는 HTTP라는 텍스트 기반 애플리케이션 계층 프로토콜을 사용해 통신한다
  • HTML과 웹의 차별성 : FTP와 달리 HTML을 기반으로 텍스트와 그래픽의 표시 방법을 지정하고, 하이퍼링크를 통해 인터넷 상의 다른 자원과 연결될 수 있다.

11.5.2. 웹 컨텐츠

  • 콘텐츠의 개념 : 웹 클라이언트와 서버에게 “콘텐츠란” MIME (Multipurpose Internet Mail Extensions) 타입이 지정된 바이트의 시퀀스이다. MIME 타입은 콘텐츠 형식을 설명하는 식별자이다. (text/html, image/jpeg)
  • 콘텐츠 제공 방식 2가지 :
    1. 정적 콘텐츠 (Static Content) : 서버가 디스크 파일을 읽어서 클라이언트에 반환 (Html, 이미지 파일)
    2. 동적 콘텐츠 (Dynamic Content) : 실행 파일을 실행한 결과물 (런타임에 만든 출력)을 클라이언트에 반환 (CGI 프로그램 실행 결과
  • 서버의 해석 규칙 :
    • 정적/동적 콘텐츠를 구분하는 표준 규칙은 없으며, 서버가 내부 규칙을 정한다
    • / 은 루트 디렉터리가 아닌 해당 서버의 콘텐츠 홈 디렉토리를 의미한다. 그래서 단독 요청은 보통 /index.html 같은 기본 페이지로 확장된다

11.5.3 HTTP Transaction

  • HTTP 요청 구조 :
    1. 요청 라인 : GET / HTTP/1.1 형태로 구성 (메서드, URI, 버전)
    2. 헤더 : Host 는 HTTP/1.1에서 Required 헤더
    3. 빈줄 : 헤더의 끝을 나타냄
  • HTTP 응답 구조 :
    1. 응답 라인 : HTTP/1.0 200 OK
    2. 응답 헤더 : MIME 타입, 콘텐츠 길이 등 포함
    3. 빈줄 : 응답 헤더의 끝
    4. 본문 : 요청된 콘텐츠
  • 요청 메서드 : GET, POST, PUT, DELETE 등 다양한 메서드가 있으나 이 절에서는 GET만 설명.
    • GET / HTTP/1.1 : index.html을 리턴하라는 뜻이며, 서버에게 나머지 요청들이 HTTP/1.1 포맷임을 알린다.
  • HTTP 버전 : HTTP/1.0와 HTTP/1.1은 호환 가능하지만, HTTP/1.1은 캐싱, 보안, 지속 연결 등 고급 기능을 지원한다
  • 헤더 활용 : 특히 Host 헤더는 프록시 서버가 요청된 콘텐츠의 도메인을 식별할 수 있게 해준다
    • Host : www.aol.com : HTTP/1.1 부터 필수로 요구되는 헤더로, 프록시 캐시가 사용한다.
    • → 필요 이유
    • 하나의 IP 주소에 여러 도메인을 운영하는 경우, 서버는 host 헤더를 보고 어떤 웹사이트의 리소스를 요청했는지 판단한다
    • 클라이언트 ↔ 프록시 서버 ↔ 웹 서버 구조에서, 프록시는 Host 헤더를 참조해 어느 웹 서버의 리소스를 요청한 것인지 판단
    • 여러 프록시가 프록시 체인으로 연결될 수 있고, 각각 Host 정보를 사용하여 요청을 전달.
    • 프록시 체인을 왜 쓸까?
      1. 보안 강화 : 여러 프록시 서버를 거치면서 원본 클라이언트 IP 주소를 숨길 수 있음. Tor 네트워크
      2. 접속 제어 및 모니터링 : 체인 중간에 위치한 프록시 서버에서 접속 로그를 기록하고, 사용자 요청을 검사하여 보안 정책 적용 가능. (악성 코드 차단, 접근 금지 사이트 필터링 등)
      3. 캐싱 효율 증가 : 상위 프록시 서버가 자주 요청되는 리소스를 캐시해두고 같은 콘텐츠를 여러 사용자에게 빠르게 제공
      4. 네트워크 경로 최적화 : 일부 프록시 서버는 더 빠른 경로를 찾아 지연 시간 줄임
      5. 접속 우회 : 국가나 조직 차원에서 차단된 사이트도 외부에 있는 프록시 서버를 경유하여 접속 가능

11.5.4 동적 컨텐츠의 처리

  1. 클라이언트 → 서버로 인자 전달 방법
    • GET 방식 : URI의 ? 이후에 인자 전달, 인자들을 &로 구분
      • 예 : /cgi-bin/adder?15000&213
      • 공백은 %20 으로 인코딩
  2. 서버 → CGI 프로그램 (자식 프로세스)으로 인자 전달
    • 서버는 fork로 자식 프로세스를 만들고 execve로 CGI 프로그램을 실행함
    • 실행 전, 환경변후 QUERY_STRING 에 URI 인자를 저장해서 프로그램이 getenv() 로 읽게 함
    • QUERY_STRING : 15000&213
  3. 기타 정보 전달
    • CGI 표준은 다양한 환경 변수 지원
      • REQUEST_METHOD : GET, POST
      • SERVER_PORT, REMOTE_ADDR, CONTENT_TYPE, CONTENT_LENGTH
  4. 출력은 어떻게 전달되는가?
    • CGI 프로그램은 표준 출력(stdout)에 콘텐츠 출력
    • 서버는 dup2를 사용해 stdout을 클라이언트 소켓으로 리다이렉트
    • 프로그램이 출력하는 모든 HTML은 클라이언트로 직접 전송됨
  5. 응답 헤더 처리
    • 부모 서버는 자식이 어떤 콘텐츠를 출력할지 모름
    • CGI 프로그램이 Content-Type, Content-length 등 응답 헤더를 직접 작성