Search
🌐

[HTTP 완벽 가이드] 4. 커넥션 관리

1. TCP 커넥션

TCP/IP
HTTP 통신의 기본 프로토콜
패킷 교환 네트워크 프로토콜의 계층화된 집합
TCP 커넥션 단계
1.
브라우저가 호스트명 호출 (예. www.naver.com)
2.
브라우저가 호스트명에 대한 IP 찾음 (202.43.78.3)
3.
브라우저가 포트 번호 획득 (80)
4.
브라우저가 획득한 IP의 포트로 TCP 커넥션 생성
5.
브라우저가 서버로 HTTP GET 요청 메세지 전송
6.
브라우저가 서버에서 온 HTTP 응답 메세지 수신
7.
브라우저가 컨넥션을 끊음

1.1. TCP 스트림

HTTP에게 신뢰할 만한 통신 방식 제공
cf) HTTP/HTTPS의 프로토콜 스택
TCP 스트림은 세그먼트로 나뉘어 IP 패킷으로 전송됨
1.
세그먼트라는 단위로 데이터 스트림을 잘게 쪼갬
2.
세그먼트를 IP 패킷이라는 봉투에 담아서 인터넷을 통해 데이터 전달
3.
각 TCP 세그먼트는 하나의 IP 주소에서 다른 IP 주소로 IP 패킷에 담겨 전달
IP 패킷의 구성
IP 패킷 헤더 (보통 20바이트)
발신지, 목적지 IP 주소, 크기, 기타 플래그
TCP 세그먼트
TCP 세그먼트 헤더(보통 20바이트)
TCP 포트 번호, TCP 제어 플래그, 데이터 순서와 무결성 검사를 위한 숫자값
TCP 데이터 조각(0 혹은 그이상의 바이트)

1.2. TCP 커넥션

TCP 커넥션은 네가지 값으로 식별
발신지 IP 주소, 발신지 포트, 수신지 IP 주소, 수신지 포트
위 네가지 값이 모두 동일한 커넥션은 중복으로 존재 할 수 없음

1.3. TCP 소켓 프로그래밍

TCP 소켓 인터페이스
운영체제가 제공하는 TCP 커넥션 관련 기능
소켓 API
TCP 종단(endpoint) 데이터 구조 생성
원격 서버의 TCP 종단에 데이터 구조를 연결하여 데이터 스트림 읽고 쓰기 가능
TCP API
네트워크 프로토콜의 핸드셰이킹
TCP 데이터 스트림과 IP 패킷 간의 분할 및 재조립 캡슐화
클라이언트와 서버간 TCP 소켓 인터페이스 커뮤니케이션 프로세스
클라이언트
서버
(S1) 새로운 소켓 생성 (socket)
(S2) 80 포트로 소켓 묶음
(S3) 소켓 커넥션 허가 (listen)
(S4) 커넥션 기다림 (accept)
(C1) IP 주소와 포트를 얻음
(C2) 새로운 소켓 생성(socket)
(C3) 서버의 IP 포트로 연결 (connect)
(S5) 애플리케이션 커넥션 종시
(C4) 성공적으로 연결
(S6) 요청을 읽기 시작 (read)
(C5) HTTP 요청 보냄 (write)
(C6) HTTP 응답 대기 (wait)
(S7) HTTP 요청 메세지 처리
(S8) HTTP 응답 보냄 (write)
(C7) HTTP 응답 처리
(C8) 커넥션 닫음 (close)
(S9) 커넥션 닫음 (close)

2. TCP 성능

HTTP 트랜잭션 처리 시간은 실제로는 매우 짧음
TCP 커넥션 설정, TCP 커넥션 요청 및 응답 회신이 오히려 더 김
대부분의 HTTP 지연은 TCP 네트워크 지연으로 발생
DNS 이름 분석(호스트명 → IP 주소 변환)
TCP 커넥션 요청 및 응답 회신
커넥션 생성 이후 TCP 파이프를 통해 요청 메세지 전달 및 서버 처리
웹 서버 HTTP 응답 회신
TCP 관련 지연
TCP 커넥션 핸드셰이크 설정
TCP slow start
nagle 알고리즘
TCP piggyback / acknowledge 확인 응답 지연 알고리즘
TIME_WAIT 지연과 포트 고갈

2.1. TPC 커넥션 핸드셰이크 지연

TCP 커넥션 핸드셰이크 프로세스
1.
클라이언트 - 새로운 TCP 커넥션 생성을 위해 TCP 패킷을 서버로 전송
SYN 플래그 - 커넥션 생성 요청
2.
서버 - 커넥션 요청 받아드리고 TCP 패킷을 클라이언트로 전송
SYN + ACK 플래그
3.
클라이언트 - 커넥션이 맺어졌는지 확인 응답 신호 보냄
ACK + 데이터
크기가 작은 HTTP 트랜잭션에서는 TCP 구성하는데 상당히 많은 시간을 사용함

2.2. 확인 응답 지연

인터넷: 패킷 전송 완벽히 보장 X → TCP는 성공적인 데이터 전송 보장을 위해 확인 체계 가지고 있음
TCP 세그먼트는 순번과 데이터 무결성 체크섬을 가짐
세그먼트 수신자가 세그먼트를 받으면 확인 응답 패킷을 송신자에게 반환
확인 응답 메세지를 받지 못하면 데이터 재전송
확인 응답 편승 (piggyback)
같은 방향으로 송출되는 데이터 패킷에 확인응답을 편승
송출 데이터 패킷과 확인응답을 하나로 묶어서 네트워크 효율적 사용
많은 TCP 스택은 확인 응답 지연 알고리즘 구현
송출할 확인응답을 특정 시간(0.1~0.2초) 버퍼에 저장
확인 응답 편승을 위한 송출 데이터 패킷 찾음
없는 경우 별도 패킷 전송
HTTP 동작 방식은 요청/응답 두가지 형식으로만 이루어짐 → 확인 응답 편승 기회 적음
확인응답 지연 알고리즘으로 인한 지연 발생

2.3. TCP 느린 시작 (slow start)

TCP의 데이터 전송 속도는 커넥션 생성 시기와 연관있음
최초에는 커넥션 최대 속도 제한
데이터 성공적으로 전송 → 속도 제한을 높여나감
혼잡 제어 기능
TCP가 한번에 전송할 수 있는 패킷 수 제한
튜닝된 컨게션은 더 빠름

2.4. 네이글(Nagle 알고리즘) & TCP_NODELAY

작은 크기의 데이터 & 많은 패킷 전송 → 네트워크 성능 저하
예) TCP 세그먼트 - 40바이트 & 데이터 - 1바이트
네이글 알고리즘
패킷 전송 전 많은 양의 TCP 데이터를 한개의 덩어리로 합침
세그먼트가 최대 크기가 되지 않으면 전송 X
다만, 다른 모든 패킷이 확인 응답을 받은 경우 전송 허락
크기가 작은 HTTP 메세지는 패킷을 채우지 못해 지연 발생
TCP_NODELAY
네이글 알고리즘 비활성화

2.5. TIME_WAIT 누적과 포트고갈

중복 패킷 생성 방지 (충돌 방지)
TCP 커넥션을 끊으면 커넥션 IP 주소와 포트 번호를 메모리의 제어영역에 기록
보통 2MSL(120초) - 세그먼트 최대 생명 주기 2배 정도 유지
TIME WAIT 상태로 진입
TCP 커넥션이 종료되었지만 일부 패킷들이 여전히 네트워크에서 도착할 수 있는 시간
이전 TCP 커넥션이 완전히 종료 되기전에 도착할 스 있는 패킷들을 처리하여 충돌을 방지하기 위한 목적
동일한 주소/포트번호를 사용하는 새로운 TCP 커넥션 생성 방지
이전 커넥션과 관련된 패킷이 새로운 커넥션에 삽입되는 문제 방지
포트 고갈
각각 한개의 클라이언트와 웹서버가 있는 경우 → 발신지 포트만 변경 가능
<발신지 IP 주소, 발신지 포트, 목적지 IP 주소, 목적지 포트>
발신지 포트 수는 제한 (약 6만개) & 120초 동안 커넥션 재사용 불가
60,000 / 120 - 500 → 초당 500개 커넥션 제한
서버가 초당 500개 이상 트랜잭션 처리할만큼빠르지 않을 경우 포트 고갈 발생 X

3. HTTP 커넥션 관리

HTTP 커넥션 성능 향상을 위한 기술들
병렬 커넥션
여러개의 TCP 커넥션을 통한 동시 HTTP 요청
지속 커넥션
커넥션을 맺고 끊는데 발생하는 지연을 제거하기 위한 TCP 커넥션 재활용
파이프라인 커넥션
공유 TCP 커넥션을 통한 병렬 HTTP 요청
다중 커넥션 (multiplexed)
여러 개의 요청과 응답을 동시에 처리하는 지속 커넥션을 제공
HTTP/2.0

3.1. 병렬 커넥션

장점
클라이언트가 여러개의 HTTP 커넥션을 맺음으로써 HTTP 트랜잭션을 병렬로 처리 가능
단점
네트워크 대역폭이 좁은 경우, 제한된 대역폭에서 객체 전송은 느리므로 병렬 커넥션 장점이 거의 없음
예) 브라우저 - 28.8kbs 모뎀으로 인터넷 연결
다수의 커넥션으로 인한 성능 문제
다수의 커넥션 → 메모리 과다 소모 & 서버 성능 저하
병렬 커넥션 수 제한
각 트랜잭션 마다 새로운 커넥션 맺고 끊음 → 시간 & 대역폭 소요
새로운 커넥션(TCP slow start) → 성능 낮음

3.2. 지속 커넥션

처리가 완료된 후에도 계속 연결 상태로 있는 TCP 커넥션
장점
커넥션 맺기 위한 사전 작업과 지연 방지
튜닝된 커넥션 유지
적정 수의 커넥션 수 유지
지속 커넥션 타입
HTTP/1.0+의 keep-Alive
HTTP/1.1 의 지속 커넥션
HTTP/1.0+의 keep-Alive
요청에 Connection: Keep-Alive 헤더 포함
커넥션 유지를 원할 경우 응답에도 동일한 헤더를 포함
엔티티 본문의 길이 (Content-Length)를 알아야 커넥션 유지 가능
멍청한 프록시
keep alive는 Connection 헤더를 지원하지 않는 프록시와 상호작용하지 않음
커넥션이 유지되었다고 믿는 클라이언트에서 요청을 보냄에도 프록시는 그 요청을 무시함. 무한 대기중에 빠짐 → 서버 타임아웃 까지 대기
→ 프록시에는 Connection 헤더를 전달하면 안됨
영리한 프록시
비표준인 Proxy-Connection 확장 헤더를 프록시에 전달하여 해결
HTTP/1.1 의 지속 커넥션
기본적으로 지속 커넥션이 활성화 되어 있음
트랜잭션이 끝난 후 커넥션을 끊으려면 헤더에 Connection: close 명시 필요
엔티티 본문의 길이 (Content-Length)를 알아야 커넥션 유지 가능

3.3. 파이프라인 커넥션

HTTP/1.1 에서는 지속 커넥션을 통해 요청 파이프라이닝 가능
큐의 개념. 응답이 도착하기 전에 여러개의 요청을 큐에 쌓음
대기 시간이 긴 네트워크 상황에서 네트워크 왕복으로 인한 시간 감소로 성능 향상 가능
제약 사항
지속 커넥션인 경우만 파이프라이닝 가능
응답과 요청 순서가 같아야 함
커넥션이 끊키더라도 큐에 대기중인 요청은 다시 보낼 수 있어야함
POST와 같은 비멱등 요청은 포함하면 안됨

3.4. 커넥션 끊기

커넥션 관리 (언제 어떻게 커넥션을 끊을지)에 대한 명확한 기준은 없음
어떠한 HTTP 클라이언트, 서버. 프록시 등은 언제든지 TCP 전송 커넥션을 끊을 수 있음
에러가 없더라도 언제든지 끊을 수 있음
우아한 커넥션 끊기
TCP 커넥션은 기본적으로 양방향임. 양쪽에는 읽고 쓰기 위한 입력/출력 큐가 있음
출력 큐(채널) - 데이터 전송하는 채널
입력 큐(채널) - 데이터 수신하는 채널
전체 끊기
close() - 입력 채널 & 출력 채널 모두 다 끊음
절반 끊기
shutdown() - 입력 채널 or 출력 채널 둘 중 하나를 개별적으로 끊음
일반적으로 예상치 못한 쓰기 에러 발생 예방을 위해 절반 끊기를 사용
보통 커넥션의 출력 채널을 끊는것이 안전
커넥션 반대편 기기는 모든 데이터를 버퍼로 읽고 데이터 전송이 끝난 후에 커넥션이 끊킨 것을 알 수 있음
입력 채널을 끊는 경우..?
끊긴 입력 채널에 데이터 전송 → connection reset by peer 에러 발생
버퍼에 저장된, 아직 읽히지 않은 데이터 모두 삭제