HTTP 커넥션 관리 (TCP/IP)
1. TCP와 HTTP 관계
1. TCP: HTTP의 신뢰성을 책임
HTTP는 애플리케이션 계층 프로토콜로서, 데이터 전송의 신뢰성 문제를 전적으로 TCP (Transmission Control Protocol)에 의존.
TCP의 핵심 역할
- TCP는 메시지를 작은 단위로 나누어(세그먼트) 전송하고, 네트워크를 통해 이 바이트들이 순서에 맞게 정확히 목적지에 전달되도록 보장.
프로토콜 스택
- HTTP 통신은 일반적으로 IP (네트워크 계층) → TCP (전송 계층) → HTTP (애플리케이션 계층)의 계층 구조를 따름.
- HTTPS는 이 사이에 TLS/SSL (보안 계층)이 추가되어 암호화 기능을 제공.
2. 데이터 전달의 메커니즘: 세그먼트와 패킷
HTTP 메시지는 네트워크를 통해 전달될 때 여러 계층을 거치며 캡슐화 됨.
세그먼트와 IP 패킷
- HTTP 메시지는 TCP에 의해 세그먼트로 분할되고, 이 세그먼트는 IP 패킷(IP 데이터그램)이라는 봉투에 담겨 전송됨.
- 이 과정은 애플리케이션 프로그래머에게는 보이지 않음.
IP 패킷의 구성
모든 IP 패킷은 데이터를 목적지까지 라우팅하기 위해 다음 정보를 포함합니다:
- IP 헤더 (IP 패킷 정보): 발신지 IP 주소, 목적지 IP 주소, 패킷 크기 등
- TCP 세그먼트 헤더: 발신/목적지 포트 번호, 순서 번호(신뢰성 보장용), 플래그 등
- TCP 데이터 조각: 실제 HTTP 메시지의 일부 데이터
Q. IP 패킷은 보통 몇 바이트인가요?
A. 일반적인 유선 이더넷 환경에서 IP 패킷의 최대 크기는 MTU (Maximum Transmission Unit)에 의해 결정되며, 보통 1,500바이트 내외임. 따라서 IP 패킷(데이터그램)의 크기는 IP 헤더와 TCP 헤더를 포함하여 최대 1,500바이트 내외. 이 크기 안에서 효율적으로 데이터를 전송하기 위해 TCP가 메시지를 세그먼트로 분할함.
2. TCP 커넥션 생성
1. TCP 커넥션의 식별
네트워크 통신이 이루어지는 통로인 TCP 커넥션은 다음 네 가지 요소의 조합으로 유일하게 식별됨.
1. 발신지 IP 주소
2. 발신지 포트
3. 목적지 IP 주소
4. 목적지 포트
2. 소켓 API를 통한 커넥션 생성
클라이언트와 서버는 소켓 API를 이용해 TCP 커넥션을 생성하고 관리함. 소켓 API는 프로그램이 운영체제의 TCP/IP 기능을 사용하도록 돕는 표준 인터페이스.
소켓이란?
소켓 API 함수 자체는 운영체제(Operating System)에서 제공하는 표준 라이브러리/인터페이스이며, 클라이언트와 서버 양쪽 모두에서 소켓을 생성하고 관리하기 위해 호출.
쉽게말해, 소켓 API는 TCP/IP 통신을 위해 프로그램이 운영체제에게 요청을 보내는 '명령어' 모음.
- 서버: 서버는 socket(), bind(), listen(), accept() 등의 API를 호출하여 클라이언트의 연결을 받아들일 소켓을 준비하고 생성.
- 클라이언트: 클라이언트는 socket(), connect() 등의 API를 호출하여 서버에 연결을 요청할 소켓을 생성하고 연결을 시도.
3. 3-way Handshake와 커넥션 지연
HTTP 트랜잭션의 성능은 하위 계층인 TCP 커넥션의 성능에 크게 영향을 받음. 웹페이지를 로딩할 때 발생하는 대부분의 지연은 TCP 네트워크 지연 때문.
설정 시간 소요
- 새로운 TCP 커넥션을 설정하기 위해 클라이언트가 DNS로 IP 주소를 찾고, TCP 3-way handshake를 완료하는 데 보통 1~2초의 시간이 소요될 수 있음.
3-Way Handshake (SYN, SYN+ACK, ACK)
- 이 과정은 HTTP 트랜잭션 시간의 50% 이상을 차지할 수 있음.
- 따라서 기존 커넥션을 재활용하는 것은 HTTP 성능 최적화의 핵심임.
3. TCP 성능 제약
1. TCP 느린 시작 (Slow Start)
TCP는 네트워크 혼잡을 방지하기 위해 커넥션 시작 시 전송 속도를 보수적으로 제한하는 느린 시작을 사용.
- 동작 원리: 커넥션이 성공적으로 설정되면, 처음에는 전송 가능한 패킷 수를 적게 설정한 후, 성공적인 확인 응답(ACK)을 받을 때마다 전송 가능한 패킷의 양을 지수적으로 증가시키며 속도를 높임.
- 지연 발생: 이 과정은 새로운 커넥션을 맺을 때마다 발생하므로, HTTP 요청이 순차적으로 여러 번 발생하면 지연이 누적.
2. 네이글(Nagle) 알고리즘과 TCP_NODELAY
네트워크를 통해 아주 작은 데이터 패킷이 대량으로 전송되는 것을 "작은 패킷 증후군"이라 하며, 이는 네트워크 성능을 크게 저해하기 때문에, 네이글 알고리즘은 이를 방지
- 동작 방식: 작은 데이터가 발생하면 즉시 전송하지 않고 버퍼에 저장. 다음 두 가지 조건 중 하나가 충족될 때까지 전송을 대기.
1. 버퍼에 쌓인 데이터의 크기가 최대 세그먼트 크기(MSS)에 도달했을 때.
2. 이전에 보낸 패킷에 대한 확인 응답(ACK)이 도착했을 때.
- TCP_NODELAY: HTTP와 같이 지연 시간을 최소화해야 하는 애플리케이션은 네이글 알고리즘을 비활성화하는 TCP_NODELAY 옵션을 설정할 수 있음. 다만, 이 경우 프로그래머는 작은 패킷이 남발되지 않도록 직접 데이터 덩어리를 관리해야 함.
4. 커넥션 재활용
TCP의 지연 문제를 근본적으로 해결하고 HTTP 성능을 극대화하는 핵심 기술.
1. 순차적 트랜잭션의 문제점
하나의 페이지를 로드하기 위해 여러 객체(HTML, 이미지 등)를 순차적으로 다운로드하면, 각 객체마다 커넥션 설정 지연이 누적되어 성능이 크게 저하.
2. 병렬 커넥션 (Parallel Connections)
원리
- 여러 개의 TCP 커넥션을 동시에 열어 여러 객체를 병렬로 다운로드함.
한계
- 브라우저는 보통 한 서버당 4~8개로 커넥션 수를 제한.
- 클라이언트의 대역폭이 좁으면 병렬 처리의 이득이 적거나 없음.
3. 지속 커넥션 : Keep-Alive
2-1. HTTP/1.0 Keep-Alive
- 한 번 맺은 커넥션을 여러 트랜잭션 간에 재사용하여 3-way handshake와 느린 시작 지연을 제거.
사용 방식
- 클라이언트가 요청 시 Connection: Keep-Alive 헤더를 명시해야만 커넥션이 유지됨.
제어 옵션
- Keep-Alive 헤더의 timeout은 유휴 시간을, max는 커넥션당 최대 트랜잭션 수를 지정하여 커넥션 유지 정책을 제어.
2-2. HTTP/1.1의 지속 커넥션
- HTTP/1.1에서는 지속 커넥션이 기본 설정임.
종료 방식
- 커넥션을 끊고 싶을 때만 Connection: close 헤더를 명시하면 됨.
- 이로 인해 불필요한 Keep-Alive 헤더를 생략할 수 있어 효율적임.
2-3. 지속 커넥션 유지 조건
성능 향상에 필수적인 지속 커넥션은 메시지 경계 파악 조건이 충족될 때만 가능.
Q. Content-Length나 청크 인코딩이 필요한 이유는?
A.
- Content-Length 값 명시: 엔터티 본문의 길이를 바이트 단위로 정확하게 지정하여, 수신자가 정확히 그 길이만큼만 읽도록 함.
- 청크 전송 인코딩 (Chunked Transfer Encoding): 메시지 본문을 여러 개의 청크(덩어리)로 나누어 보내고, 각 청크의 시작 부분에 청크의 크기를 명시. 데이터 전송이 끝나면 크기가 0인 청크를 보내어 메시지의 끝을 명확히 알림.
결론: 지속 커넥션에서는 여러 메시지가 섞여 들어오므로, 메시지의 끝을 알 수 있는 명확한 표식이 없다면 수신자가 메시지들을 정확하게 분리하여 처리할 수 없게 되기 때문에 Content-Length 또는 청크 인코딩이 필수.
4. 파이프라인 커넥션
HTTP/1.1의 지속 커넥션이 확립되면서 파이프라이닝 기술이 가능해짐.
원리
- 이전 요청에 대한 응답을 기다리지 않고 여러 개의 요청을 연속적으로 전송하여 네트워크 대기 시간을 효과적으로 활용함.
성능 향상
- 첫 번째 응답이 도착하기 전까지 발생하는 유휴 시간을 줄여 성능을 크게 향상시킴.
파이프라인의 제약
파이프라인을 사용할 때는 다음과 같은 중요한 규칙을 지켜야 함:
1. 순서 보장: 서버의 응답은 요청이 들어온 순서대로 전송되어야 함.
2. 비멱등 요청 금지: POST와 같이 서버의 상태를 변경하는 비멱등(Nonidempotent) 요청은 사용할 수 없음.
Q. 파이프라인에서 POST 요청이 에러 발생 시 문제가 되는지?
A. 문제가 됨, 에러 발생 시 클라이언트가 서버에서 어떤 POST 요청이 처리되었는지 알 수 없어, 재전송 시 중복 트랜잭션이 발생하여 데이터 불일치를 초래할 수 있기 때문.
올바른 POST 처리
1. 파이프라인 금지 이유: 파이프라이닝은 응답 순서가 보장되어도 에러 발생 시 재전송 문제(중복 트랜잭션) 때문에 POST에는 적합하지 않음.
2. 전송 방식: POST 요청은 다음 두 가지 방식으로 전송되어야 함.
- 지속 커넥션(Keep-Alive)의 순차적 전송: 하나의 지속 커넥션을 통해 POST 요청을 보낸 후, 반드시 해당 요청에 대한 응답을 완전히 받은 다음에 다음 POST 요청을 보냄. 이는 각 트랜잭션의 성공 여부를 명확히 확인하고, 커넥션 재사용으로 인한 성능 이득은 유지하기 위함.
- 새로운 커넥션(비지속 커넥션): HTTP/1.0 환경처럼 Keep-Alive를 사용하지 않는다면, POST 요청을 보낸 후 커넥션을 닫고 다음 POST 요청 시 새로운 커넥션을 맺음.
따라서 POST는 파이프라이닝을 통해 동시에 여러 개를 보내는 것을 피하고, 지속 커넥션의 이점을 활용하며 하나씩 순차적으로 처리하는 것이 올바른 방법.
5. 프락시 처리
프락시 환경에서 발생하는 커넥션 관리 문제와 해결 방법.
1. Connection 헤더 (Hop-by-Hop 제어)
Connection 헤더는 특정 헤더 필드가 메시지 경로 전체(End-to-End)가 아닌 바로 인접한 두 통신 주체(하나의 홉) 사이의 커넥션에만 적용되어야 함을 명시.
Q. Connection 헤더는 클라이언트-서버가 아닌 홉-별(Hop-by-Hop) 통신인지?
A. Connection 헤더는 프록시와 같은 중개 서버가 다음 홉으로 메시지를 전달하기 전에 삭제해야 할 헤더 목록을 지정함. 이는 현재 홉의 커넥션에만 관련된 제어 정보를 담기 때문.
2. 멍청한 프락시(Dumb Proxy)의 오류
Connection 헤더는 홉-별(Hop-by-Hop) 헤더로, 프락시는 다음 홉으로 메시지를 전달하기 전에 이 헤더를 반드시 제거해야 함.
Q. 프락시의 행(hang) 이란?
A. 'Hang'은 컴퓨터나 프로그램이 멈추거나 응답하지 않는 상태를 의미. 특히 네트워크 환경에서는 데이터를 기다리느라 멈춘 상태
3. Proxy-Connection: 비표준 헤더
이러한 문제를 우회하기 위해 Netscape 브라우저는 Proxy-Connection이라는 비표준 헤더를 사용함.
Q. Proxy-Connection 헤더가 서버에 전달되어도 문제가 없는가?
A. 문제가 없음, 이 헤더는 서버가 이해하지 못하는 비표준 헤더이므로 서버는 이를 무시하고, Connection: Keep-Alive 헤더를 서버로 잘못 전달하는 문제를 피할 수 있음.
영리한 프락시와 홉-별 지속 커넥션
프락시는 최종 서버가 아니라, 클라이언트와 서버 사이에 위치한 중개자. HTTP 통신에서 프락시는 클라이언트와의 연결을 담당하고, 서버와의 새로운 연결을 담당. 따라서 전체 통신 경로는 두 개의 개별적인 '홉(Hop)'으로 나뉨.
1. 첫 번째 홉 (클라이언트 프락시)
2. 두 번째 홉 (프락시 서버)
6. 종료·무결성
커넥션 종료와 데이터 무결성 보장에 관한 중요한 개념들을 다룸.
1. Half-Close: 우아한 커넥션 종료
HTTP 명세는 커넥션 종료에 대해 명확한 기준이 없어 '마음대로 끊기'가 가능. 하지만 오류를 방지하기 위해 절반 닫기(Half-Close)가 사용됨.
TCP의 양방향 구조
- TCP 커넥션은 입력 채널과 출력 채널이 분리되어 있음.
전체 닫기 / 절반 닫기
- 전체 닫기 : close() 함수를 통해 입/출력 채널 모두 닫음.
- 절반 닫기 : shutdown() 함수를 사용해 자신의 출력 채널만 닫고 상대방의 응답(입력 데이터)을 기다리는 방식.
- 이는 특히 파이프라인 커넥션에서 자신의 전송 완료를 알리고 상대방의 응답을 안전하게 받기 위해 사용.
2. Reset by Peer: 연결 종료의 위험
위험 요소
- 클라이언트가 입력 채널을 닫았는데 서버가 계속 데이터를 보내면 TCP reset by peer 메시지가 발생.
- 파이프라인 커넥션에서는 이미 도착한 응답 데이터도 유실되는 문제가 발생.
해결방법
- 일반적으로 애플리케이션은 자신의 출력 채널을 먼저 닫고, 상대방의 입력 채널이 닫히기를 기다리는 방식을 사용.
- 이는 상대방에게 데이터 전송을 중지하라고 알려주면서도, 자신이 보낸 데이터에 대한 응답을 안전하게 받을 수 있는 방법
3. 커넥션 실패 시 재시도 원칙
재시도 원칙
- 커넥션 오류 발생 시, GET, HEAD 등의 멱등 요청은 안전하게 새로운 커넥션을 맺고 재시도할 수 있음.
- 파이프라인 커넥션에서 커넥션이 끊어지면, 클라이언트는 어떤 요청이 서버에서 처리되었는지 알기 어려움. 이 경우 클라이언트는 재시도 시 멱등 요청만 재시도 해야함.
- POST 같은 비멱등 요청은 사용자 확인 없이는 재시도해서는 안 됨.