본문 바로가기

교양/네트워크

HTTP 완벽가이드 - 4장 커넥션 관리

TCP커넥션은 어떻게 이루어 지는가

전송 패턴

웹 브라우저가 TCP커넥션을 통해서 서버에 요청을 보내는 중이다.

1. 첫번째로 URL에 호스트명을 추출한다.

2. 호스트명을 DNS서버를 통해서 IP로 바꾼다.

3. 포트번호를 얻는다.

4. TCP커넥션을 새성한다.

5~6 : 응답과 요청을 한다.

7 : TCP 커넥션을 끊는다.

 

전송 방식

HTTP와 HTTPS의 프로토콜 스택

HTTP가 메시지를 전송할 때, TCP 커넥션을 통해서 데이터를 순서대로 보낸다. TCP는 세그먼트라는 단위로 데이터 스트림을 잘게 나누고, 세그먼트를 IP 패킷이라고 불리는 봉투에 담아서 인터넷을 통해 데이터를 전달한다.

TCP커넥션으로 전송되는 TCP segment의 구조

맨 아래쪽이 TCP 데이터 스트림 덩어리(chunk)이고 나머지는 TCP/IP가 정확히 데이터를 전달하기 위한 메타데이터들이다.

 

TCP 소켓 프로그래밍

HTTP통신을 위해서 TCP/IP커넥션을 직접 설정하기에는 많은 노력과 수고가 든다.(전송계층, 네트워크계층,  데이터링크 계층을 모두 다루어야 한다.) 그렇기 때문에, TCP 소켓을 이용하게 된다면 TCP커넥션을 쉽게 사용할 수 있게 된다.

소켓을 이용하면 다음과 같이

1. 서버에서는 소켓을 생성하고, 포트로 소켓을 묶은 후, 소켓 커넥션을 허락하고 기다린다.

2. 클라이언트는 소켓을 생성하고 서버의 IP:포트로 연결을 요청한다.

3. 서버는 연결을 확인하고 요청을 읽기 시작한다.

4. 클라이언트는 성공 응답을 받아 성공적으로 연결을 하고 요청을 보낸후 응답을 기다린다.

5. 서버는 응답을 처리한다.

6. 모든 요청-응답이 끝난 후에 커넥션을 닫는다.

순으로 코드를 작성해주기만 하면 된다.

C 언어로 작성된 Simple TCP 소켓 프로그래밍

깃 예제 : https://gist.github.com/browny/5211329  

HTTP 및 TCP의 성능

사실 이 장의 하이라이트다. 

다시 HTTP 트랜잭션이 처리되는 과정을 보자.

노란 부분이 실제 HTTP 응답과 요청 메시지를 작업하는데

나머지 부분인 TCP커넥션 연결의 처리 시간이 비슷하거나 더 많은 것으로 보인다.

그렇기 때문에 HTTP 지연은 곧 TCP 네트워크 지연이라고 볼 수도 있다.

 

TCP 성능 저하의 원인

TCP 커넥션 핸드셰이크 지연 

TCP 커넥션을 연결하기 위해서는 클라이언트와 서버간의 특정 조건을 맞추기 위해서 위의 사진처럼 연속적인 IP패킷을 교환한다.

TCP 커넥션이 핸드셰이크를 하는 순서는 다음과 같다.

1. 클라이언트는 TCP 커넥션을 생성하기 위해 작은 TCP 패킷을 보낸다. 이 패킷은 "SYN"라는 커넥션 생성 요청을 의미한다.

2. 서버가 이 커넥션을 받으면 커넥션이 승락됨을 의미하는 "ACK"와 "SYN" 그리고 TCP패킷을 클라이언트에 보낸다.

3. 클라이언트는 TCP 커넥션이 성공적으로 연결됨을 알려주는 확인 응답 신호를 보낸다.

오늘 날의 TCP는 클라이언트가 확인응답 패킷과 함께 데이터를 보낼 수 있다.

 

만약 HTTP 트랙잭션이 작은 데이터를 많이 주고 받는다면, 매번 TCP커넥션 연결을 위한 핸드셰이크를 하게 되므로 많은 시간을 사용하게 된다.

 

확인 응답 지연

 

위의 전송 방식의 TCP segment 구조에서 확인할 수 있듯이 TCP는 데이터가 정확하게 전달 되는 것을 확인하기 위해 무결성 체크섬과 순번을 TCP세그먼트에 담아 보낸다.

이는 곧 TCP가 데이터의 전송을 보장하는 역할을 하게 되고, 이와 같은 역할을 반복수행(이미 데이터 전송에 정확성이 증명된 경우)할 경우 지연이 발생할 수 있다.

 

TCP의 느린시작

 

TCP는 급작스러운 부하와 혼잡을 방지하기 위해서 TCP가 한 번에 전송할 수 있는 패킷의 수를 제한한다. 처음에는 커넥션을 제한하다 데이터가 성공적으로 전송됨에 따라서 속도 제한을 높혀나간다. 이를 "튜닝"이라고 한다.

 

네이글 알고리즘

작은 데이터들을 위해 TCP세그먼트들을 매번 셋팅해서 전송하는것은 비효율 적이다. (데이터보다 세그먼트의 메타데이터들이 더 많은 경우) 그렇기 때문에 네이글이란 개발자는 데이터가 일정 수준으로 커지기 전까지 스택에 쌓아 두었다가 한꺼번에 전송하는 알고리즘을 제시했다. 

이는 오히려 다른 HTTP 성능 문제를 일으켰는데, 앞으로 생길지 생기지 않을지도 모르는 추가적인 데이터를 기다리며 지연되는 등의 경우이다.

 

TIME_WAIT의 누적과 포트 고갈

실 성능에 일어는 경우는 아니고 성능 측정시에 발생하는 오류 정도라고 한다.

간단히 만약 TCP커넥션이 연결 되고 도중에 이 연결이 끊기면 그냥 끊기는게 아니라 패킷을 특정 메모리에 이것을 저장해 둔다.

그리고 다시 연결을 요청시에 일정 시간동안 TCP커넥션(중복되는 패킷)을 다시 생성하지 않게 하는데, 성능 측정의 경우 제한된 IP와 포트로 많은 요청을 하다보니 TCP커넥션의 충돌이 발생하게 되고 TIME_WAIT에러가 발생한다고 한다.

 

HTTP 커넥션 관리

성능 저하의 원인들을 살펴보았으니 이제 해결 방법들을 살펴보자.

< 순차적인 커넥션 >

이전까지는 매번 커넥션 연결 트랜잭션, 또 다른 커넥션 연결 트랜잭션... 을 반복하였다.

이런 식이 아니라 여러 객체를 가져올 수 있다면 얼마나 좋을까

 

이에 대한 기술을 살펴보자

 

병렬 커넥션

< 병렬 커넥션 >

그림에서 살펴볼 수 있듯이 커넥션을 동시에 연결하는 것을 뜻한다. 이를 통해 지연되는 시간을 겹치게 하므로써 총 지연시간을 줄일 수 있게 된다. 하지만 인터넷 네트워크 대역폭(그냥 한번에 전송할 수 있는 양 정도로만 보자)이 충분히 넓지 않다면 한 번에 여러 커넥션을 처리하는 것은 고부하를 일으키며 사실상 성능 향상에 큰 도움이 되지 않을 수 있다.

지속 커넥션

매번 TCP 커넥션을 연결하고 끊고를 반복하는 것은 비효율 적이다. 이 때문에 TCP커넥션을 연결한 후 연결된 TCP를 유지하여 트랜잭션을 하는 방법이다. 이를 통하면 TCP의 느린 시작과 같은 문제를 해결 할 수 있다. 하지만 계속 연결된 TCP를 사용하는 만큼 잘못된 연결이 되지 않도록 주의해야 할 필요가 있따.

 

Keep-Alive

 

지속적 연결과 관련된 기술이다. 커넥션을 유지하기 위해 클라이언트는 요청에 Connection 헤드의 키로 Keep-Alive를 보낸다. 이는 HTTP/1.0 버전에서 확장시켜야만 사용할 수 있는 기능이다.

Keep-Alive는 몇몇 제한을 가지고 있는데 대표적으로 keep-alive는 프락시에서 인식하지 못한다. 이로 인해서 TCP 커넥션 자체에 문제가 발생한다.

이를 위해서 일단 Connetion 헤더를 이해하자.

Connetion 헤더

 

Connetion 헤더는 TCP 연결에 대한 요청과 응답을 담는다.

그 값으로는 세가지 종류를 가지는데

1. HTTP 헤더 필드명

2. 임시적인 토큰 값

3. close

이다. 위의 Connetion에서는 Meter 헤더를 bil-my-credit-card옵션을 적용하며 연결이 끊난 후에 커넥션을 close하라는 의미이다.

 

일반적으로 Connection헤더는 홉별(hop-by-hop)이라는 속성을 가지는데 이 헤더는 다른헤더로 가는 것을 방지한다.

위의 그림에서 Proxy는 원래대로라면 Connection을 Clinet에 전달하지 않는다.

 

Keep-alive와 멍청한 프락시

문제는 여기서 발생한다. Connetion의 홉별로 클라이언트에게 전달하지 않아야 하는데 keep-alive를 이해하지 못하고 Connetion을 일반헤더로 판단하여 클라이언트로 보낸다.

이는 다음과 같은 예시에서 문제를 일으킨다.

1. 클라이언트는 프록시와에 Keep-Alive를 위해 헤더를 보낸다.

2. 프록시는 이를 이해하지 못하고 서버에 보낸다.

3. 서버는 프록시가 Keep-Alive를 요청하기를 바란다고 생각하고 그에 대한 응답을 보내고 커넥션을 유지한다.

4. 프록시는 응답을 이해하지 못하고 클라이언트로 보낸다.

5. 클라이언트는 Keep-Alive를 승낙했다는 응답을 받고 다음 요청을 보낸다.

6. 프록시는 아직 서버와의 커넥션이 종료됨을 기다리기 때문에 클라이언트의 요청을 일단 보류한다.

 

이와 같은 문제점 덕분에 HTTP/1.0 에는 Proxy-Connection 헤더가 추가 되었다. Proxy-Connection는 프록시가 Connection의 헤더들을 이해하고 유저가 원하는대로 작동하게 돕는다.

 

HTTP/1.1의 지속 커넥션

이와 같은 keep-alive의 문제점 덕분에 HTTP/1.1에서는 아예 지속 커넥션을 알아서 유지하고 끊는다.