[HTTP 완벽 가이드] 4장 HTTP 커넥션
TCP 커넥션
커넥션이 맺어지게되면 클라이언트와 서버 컴퓨터 간에 주고받는 메세지들은 손상 혹은 손실이되거나 순서가 바뀌지않고 전달된다.
브라우저에서 URL을 입력하면 발생하는일
신뢰할 수 있는 데이터 전송 통로인 TCP
TCP 커넥션은 인터넷을 안정적으로 연결해주며 순서에 맞게 정확히 전달된다.
TCP 스트림은 세그먼트로 나뉘어 IP 패킷을 통해 전송된다
TCP는 IP패킷
이라고 불리는 작은 조각을 통해 데이터를 전송한다.
IP패킷 구성
IP패킷은 IP 패킷헤더
, TCP 세그먼트 헤더
, TCP 데이터
로 이루어져있다.
- IP 패킷헤더: 발신지와 목적지 IP 주소, 기타 플래그 값을 가지고있다.
- TCP 세그먼트헤더: TCP 포트번호, TCP제어 플래그, 데이터 무결성을 검사하기위한 숫자값을 포함한다.
- TCP 데이터: 실제 데이터가 들어가있음
TCP 커넥션 유지하기
TCP는 포트 번호를 통해 여러개의 커넥션을 유지한다.
TCP 소켓 프로그래밍
운영체제는 TCP 커넥션의 생성과 관련된 여러 기능을 제공한다.
소켓 API를 사용하면, TCP 종단(End Point) 데이터 구조를 생성하고, 원격 서버의 TCP종단에 그종단 데이터 구조를 연결하여 데이터 스트림을 읽고 쓸 수 있다.
하지만 TCP API는 기본적인 네트워크 프로토콜의 핸드세이킹, 그리고 TCP데이터 스트림과 IP 패킷 간의 분할 재조립에 대한 모든 세부사항은 외부로부터 숨긴다.
TCP의 성능에 대한 고려
HTTP는 TCP 바로위에 있는 계층이기 때문에 HTTP 트랜잭션의 성능은 그 아래 계층인 TCP 성능에 영향을 받는다.
HTTP 트랜젝션 지연
트랜잭션을 처리하는 시간은 TCP커넥션을 설정하고, 요청을 전송하고, 응답 메세지를 보내는것에 비하면 상당히 짧다는 것을 알 수 있다.
대부분의 HTTP 지연은 TCP네트워크 지연때문에 발생한다.
HTTP 트랜잭션을 지연시키는 원인은 여러가지 있다.
- 클라이언트는 URI에서 웹 서버의 IP주소와 포트 번호를 알아내야하는데 URI에 기술되어있는 호스트에 방문한 적 이 없다면 DNS분석 인프라를 이용하여 URI에 있는 호스트명을 IP주소로 변환하는데 시간이걸린다.
- 클라이언트는 TCP 커넥션 요청을 보내고 서버가 커넥션 허가 응답을 회신하기 기다리는데 수백 개의 HTTP 트랜잭션이 만들어지면 소요시간은 크게 증가한다.
- 커넥션이 생성되면 클라이언트는 HTTP 요청을 새로 생선된 TCP 파이프를 통해 전송한다. 웹서버는 데이터가 도착하는 대로 TCP 커넥션에서 요청 메세지를 읽고 처리하지만 요청 메세지가 인터넷을 통해 전달되고 서버에의해서 처리하는데까지 시간이 걸림.
- 웹 서버가 HTTP 응답을 보내는 것 역시 시간이 소요됨.
HTTP 프로그래머에게 영향을 주는 가장 일반적인 TCP 관련 지연들
- TCP 커넥션의 핸드세이크
- 인터넷의 혼잡을 제어하기 위한 TCP 느린 시작(slow-start)
- 데이터를 한대 모아 한번에 전송하기 위한 네이글(negle) 알고리즘
- TCP의 (편승) 확인응답(acknowledgment)을 위한 확인 응답 지연 알고리즘
- TIME_WAIT 지연과 포트 고갈
TCP 커넥션 핸드세이크
TCP 커넥션 핸드세이크 과정
- 클라이언트는 새로운 TCP 커넥션을 생성하기 위해 작은 패킷
SYN
이라는 패킷을 서버에게 보낸다. - 서버가 SYN읅 받으면 커넥션 요청이 받아들여졌음을 의미하는
SYN
과ACK
플래그를 포함한 패킷을 클라이언트에게 다시보냄 - 마지막으로 클라이언트는 커넥션이 잘 맺어졌음을 알리기 위해 서버에게 다시 확인 응답 신호를 보냄
- 이후에 확인 응답과 데이터를 보내기시작
HTTP 트랜잭션이 아주 큰 데이터를 주고 받지 않는 평범한 경우에는 SYN, SYN+ACK 핸드 세이크가 눈에 띄는 지연을 발생 시킨다.
확인응답 지연
각 TCP 세그먼트는 순번과 데이터 무결성 체크섬을 가진다.
송신자가 특정 시간 안에 확인 응답 메세지를 받지 못한다면 패킷이 파기되엇거나 오류가 있는 것으로 판단하고 데이터를 다시 전송함.
TCP 느린 시작(slow-start)
TCP 커넥션은 시간이 지나면서 자체적으로 튜닝
되어 처음에는 커넥션의 최대 속도를 제한하고 데이터가 성공적으로 전송됨에 따라서 속도 제한을 높여나간다.
이렇게 조율 하는것을 TCP 느린 시작
이라 부르며, 인터넷의 급작스러운 혼잡을 방지하는데 쓰인다.
HTTP 패킷은 전송할 데이터 양이 많으면 모든 패킷을 한번에 전송할 수 없다.
그 대신 한개의 패킷만 전송하고 확인응답을 기다린후 확인응답을 받으면 2개 패킷을 보낼 수 있으며, 2개의 패킷의 확인 응답을 받으면 총 4개의 패킷을 보낼 수 있게 된다. 이를 혼잡 윈도를 연다(oepning the congeston window라 한다.
네이글(Nagle) 알고리즘
각 TCP 세그먼트는 40바이트 상당의 플래그와 헤더를 포함하여 전송하기 때문에, TCP가 작은 크기의 데이터를 포함한 많은 수의 패킷을 전송한다면 네트워크 성능을 크게 떨어진다.
네이글 알고리즘
은 네트워크 효율을 위해서, 패킷 전송하기 전에 많은 양의 TCP 데이터를 한 개의 덩어리로 합친다.
네이글 알고리즘은 세그먼트가 최대크기가 되지 않으면 전송을 하지 않는다. 다만 다른 모든 패킷이 확인응답을 받았을 겨우에는 최대 크기보다 작은 패킷의 전송을 허락한다.
네이글 알고리즘은 HTTP 성능 관련해 여러 문제를 발생시킨다.
- 크기가 작은 HTTP 메세지는 패킷을 채우지 못하기 때문에 다 채울댸가지 기다리며 지연이 될 수 있다.
- 네이글 알고리즘은 확인응답 지연과 함께 스일 경우 형편없이 작동한다
(지연 알고리즘은 확인응답을 100~200 밀리초 지연시킨다)
TIME_WAT의 누적과 포트 고갈
TCP 커넥션의 종단에서 TCP커넥션을 끊으면, 종단에서는 커넥션의 IP주소와 포트 번호를 메모리의 작은 제어영역에 기록해놓는다.
이 정보는 같은 주소와 포트 번호를 사용하는 새로운 TCP 커넥션이 일정시간 동안에는 생성되지 않게 하기위해 기록해놓는것이다.
이는 이전 커넥션과 관련된 패킷이 그 커넥션과 같은 주소와 포트 번호를 가지는 새로운 커넥션에 삽입하는 문제를 방지한다.
HTTP 커넥션 관리
흔히 잘못 이해하는 Connection 헤더
어떤 경우에는, 두개의 인접한 HTTP 애플리케이션이 현재 맺고 있는 커넥션에만 적용될 옵션을 지정해야 할 떄가 있다.
HTTP Connection 헤더 필드는 커넥션 토큰을 쉼표로 구분하여 가지고 있으며, 다른 커넥션에 전달되지 않는다.
커넥션 토큰이 HTTP 헤더 필드 명을 가지고 있으면 해당 필드들은 현재 커넥션만을 위한 정보이므로 다른 커넥션에 전달되면안된다.
Connection 헤더는 hop-by-hop
헤더 명을 기술하는데, 이것을 헤더 보호기
라고함.
HTTP 애플리케이션이 Connection 헤더와 함께 메세지를 전달받으면, 수신자는 송신자에게서 온 요청이 기술 되 어있는 모든 옵션을 적용한다음 hop에 있는 메세지를 전달하기전에 Connection헤더와 헤더에 기술되어있던 모든 헤더를 제거한다.
순차적인 트랜잭션 처리에 의한 지연
커넥션 관리가 재대로 이루어 지지 않으면 TCP 성능이 매우 안좋아 질 수 있다.
트랜잭션이 새로운 커넥션을 필요로한다면, 커넥션을 맺는데 발생하는 지연과 함게 느린 시작 지연이 발생할 것 이다.
순차적으로 로드하는 방식이 또 하나의 문제점은, 특정 브라우저의 경우 객체를 화면에 배치하려면 객채의 크기를 알아야 하기떄문에 모든 객체를 내려받기 까지는 텅 빈 화면을 이용자에게 보여준다.
병렬 커넥션
병렬 커넥션은 페이지를 더 빠르게 내려받는다.
단일 커넥션의 대역폭 제한과 커넥션이 동작하지 않고 있는 시간을 활용하면, 객체가 여러 개 있는 웹페이지를 더 빠르게 내려받을 수 있을 것 이다.
위 사진은 HTML 페이지를 먼저 내려받고 남은 세개의 트렌잭션이 각각 별도의 커넥션에서 동시에 처리된다.
병렬 커넥션이 항상 더 빠르지는 않다.
병렬 커넥션이 ㅂ일반적으로 더빠르기는 하지만 항상 그렇지는않다.
다수의 커넥션은 메모리를 많이 소모하고 자체적인 성능 문제를 발생시킨다.
브라우저는 실제로 병렬 커넥션을 사용하긴 하지만 적은수 (대부분 4개)
의 병렬 커넥션만 허용한다.
서버는 특정 클라이언트로부터 과도한 수의 커넥션을 맺을경우 그것을 임의로 끊을 수 있다.
병렬 커넥션은 더 빠르게 느껴 질 수 있다.
병렬 커넥션이 페이지를 더 빠르게 로드하지는 않는다.
하지만 병렬커넥션이 화면에 여러 개의 객체가 도시에 보이면서 내려받고 잇느 상활을 볼 수 있기때문에 사용자는 더빠르게 내려받고 있는 것처럼 느낄 수 있다.
지속 커넥션
서버에 HTTP 요청을 하기 시작한 애플리케이션은 웹페이지 내의 이미지 등을 가져오기 위해서 그서버에게 또 요청을 하게 된다. 이 속성을 사이트 지역성
이라 부른다.
HTTP/1.1 을 지원하는 기기는 처리가 완료 된 후에도 TCP 커넥션을 유지하여 앞으로 있을 HTTP 요청에 재사용 할 수 있다.
처리가 완료된 후 에도 커넥션을 연결된 상태로 있는 TCP 커넥션을 지속 커넥션
이라고 한다.
지속 커넥션을 사용함으로써 커넥션을 맺기 위한 준비 작업(핸드 세이크 등)시간을 절약 할 수 있다.
지속 커넥션 vs 병렬 커넥션
병렬 커넥션은 여러 객체가 있는 페이지를 더 빠르게 전송한다.
하지만 병렬 커넥션에는 다음과 같은 단점이 존재한다.
- 각 트랜잭션마다 새로운 커넥션을 맺고 끊기 때문에 시간과 대역폭이 소요됨.
- 각 새로운 커넥션은
TCP 느린 시작
때문에 성능이 떨어짐 - 실제로 연결 할 수있는 병렬 커넥션의 수 에는 제한이 있음
반대로 지속 커넥션은 병렬 커넥션에비해 몇가지 장점이 있다.
- 커넥션을 맺기위한 사전 작업과 지연시간을 줄여줌
- 튜닝된 커넥션을 유지하며 커넥션의 수를 줄여준다.
하지만 지속 커넥션을 잘못 관리할 경우에는 계속 연결된 상태로 있는 수 많은 커넥션이 쌓이게 될 것 이다.
HTTP/1.0+ 의 Keep-alive 커넥션
많은 HTTP/1.0 브라우저와 서버들은 일찍부터 다소 실험적인 Keep-alive
커넥션이라는 지속 커넥션을 지원하기 시작하였다.
초기의 커넥션은 상호 운용과 관련된 설계 문제가 있었지만 HTTP/1.1 에 수정되었다.
위 사진 처럼 지속 커넥션은 커넥션을 맺고 끊는데 필요한 작업이 없어서 시간이 단축 되었다.
Keep-alive 동작
HTTP/1.0 Keep-alive 커넥션을 구현한 클라이언트는 커넥션을 유지하기 위해서 요청에 Connection: Keep-alive
헤더를 포함시킨다.
해당 헤더가 없으면 클라이언트는 서버가 keep-alive를 지원하지 않으며, 응답 메세지가 전송되고 나면 서버가 커넥션을 끊을 것이라고 추정한다.
Keep-alive 옵션
Keep-alive 헤더는 커넥션을 유지하기를 바라는 요청일 뿐이다.
서버가 Keep-alive 요청을 받았다고 해서 무조건 그 것을 따를 필요는없다.
keep-alive의 동작은 keep-alive 헤더의 쉼표로 구분된 옵션들로 제어 가능하며 선택 사항임
- timeout 파라미터는 keep-alive 헤더를 통해보냄. 이는 얼마나 유지될 것인지를 의미함
- max 파라미터는 keep-alive 응답 헤더를 통해보냄 이는 커넥션이 몇 개 HTTP 트랜잭션을 처리할 때 까지 유지 할 것인지 의미함.
keep-alive 커넥션 제한과 규칙
- Keep-alive는 HTTP/1.0에서 기본적으로 사용되지는 않음
- 커넥션을 계속 유지하려면 모든 메세지에 Connection: Keep-alive 헤더를 포함해야함
- 클라이언트는 Connection: Keep-alive 응답 헤더가 없는 것을 보고 서버가 응답 후에 커넥션을 끊을 것임을 알 수 있음
- 커넥션이 끊어지기 전에 엔티티 본문의 길이를 알 수 있어야 커넥션을 유지할 수 있다.
본문이 정확한 Content-Length값과 함꼐 멀티 파트 미디어 형식을 가지거나 청크 인코딩으로 인코드가 되어야함을 뜻함. ( 이것을 통해 트랜잭션이 끝나는 시점에 기존 메세지의 끝과 새로운 메세지 시작을 알 수 있음) - 클라이언트는, 응답 전체를 모두 받기 전에 커넥션이 끊어졌을 경우, 별다른 문제가 없으면 요청을 다시 보낼 수 있게 준비되어 있어야함
HTTP/1.1 의 지속 커넥션
HTTP1/1. 에서는 keep-alive 커넥션을 지원하지 않는 대신, 설계가 더 개선된 지속 커넥션
을 지원함.
지속 커넥션의 목적은 keep-alive 커넥션과 같지만 그에 비해 더 잘 동작함.
HTTP/1.1의 지속 커넥션은 기본으로 활성화 되어 있다.
HTTP/1.1 애플리케이션은 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection: close
헤더를 명시해야함
HTTP/1.1 클라이언트는 응답에 Connection: close가 없으면 응답 후에도 HTTP/1.1은 계속 커넥션을 유지 할 것으로 추정함
지속 커넥션의 제한과 규칙
- 클라이언트가 요청에
Connection: close
헤더를 포함해 보냈으면 클라이언트는 그 커넥션으로 추가적인 요청을 보낼수가 없음 - 클라이언트가 해당 커넥션으로 추가적인 요청을 보내지 않을 것이라며 요청에 Connection: close 헤더를 보내야한다.
- 커넥션에 있는 모든 메세지가 자신의 길이(Content-Length) 정보를 정확히 가지고 있을 때만 커넥션을 지속 시킬 수 있음
- HTTP/1.1 프락시는 클라이언트와 서버 각각 에 대한 별도의 지속 커넥션을 맺고 관리해야함.
- HTTP/1.1 프락시 서버는 클라이언트가 커넥션 관련 기능에 대한 클라이언트의 지원 범위를 알고 있지 않은 한 지속 커넥션을 맺으면 안됨.
- HTTP/1.1 애플리케이션은 중간에 귾어지는 커넥션을 복구 할 수 있어야함.
파이프라인 커넥션
HTTP/1.1은 지속 커넥션을 통해서 요청을 파이프라이닝 할 수 있다.
이는 지속 커넥션의 성능을 더 높여준다.
여러 개의 요청은 응답이 도착하기 전까지 큐에 쌓이고 첫번째 요청이 네트워크를 통해 전달되면 거기에 이어 두번쨰 세번쨰 요청이 전달될 수 있다.
파이프라인 커넥션을 사용하기전에 여러가지 제약 사항이 존재함
- HTTP클라이언트는 커넥션이 지속 커넥션인지 확인 하기 전까지 파이프라인을 이어서는 안됨.
- HTTP 응답은 요청 순서와 같게 와야함.HTTP 메세지는 순번이 매겨 있지않아서 응답이 순서 없이 오면 순서에 맞게 정렬 시킬 방법이 없음.
- HTTP 클라이언트는 커넥션이 언제 끊어지더라도, 완료되지 않은 요청이 파이프라인에 있으면안됨
- HTTP 클라이언트는 POST요청 같이 반복해서 보낼 경우 문제가 생기는 요청은 파이프라인을 통해 보내면 안됨
커넥션 끊기에대한 미스터리
커넥션 관리(언제 어떻게 커넥션을 끊는가)에는 명확한 기준이 없다.
마음대로 커넥션 끊기
어떠한 HTTP클라이언트, 서버, 혹은 프락시든 언제든지 TCP 전송 커넥션을 끊을 수 있다.
지속 커넥션이 일정 시간 동안 요청을 전송하지 않고 유휴 상태 에 있으면 서버는 그 커넥션을 끊을 수 있음
Content-Length
HTTP 응답은 본문의 정확한 크기 값을 가지는 Content-Length 헤더를 가지고 있어야함
커넥션 끊기허용, 재시도, 멱등성
커넥션은 심지어 에러가 없더라도 언제든지 끊을 수 있다.
HTTP 애플리케이션은 예상치 못하게 커넥션이 끊어졌을 때에 적절히 대응 할 수있는 준비가 되어 있어야한다.
어떤 요청 데이터가 전송되었지만 응답이 오기전에 커넥션이 끊기면 클라이언트는 실제로 서버에 얼마만큼 요청이 처리되었는지 전혀 알 수 없다.
한 번 혹은 여러 번 실행 됬는지에 상관업이 같은 결과를 반환한다면 그 트랜 잭션은 멱등한다 라고한다.
GET, HEAD, PUT, DELETE, TRACE 그리고 OPTIONS 메서드들은 멱등하다고 이해하면 된다.
클라이언트는 POST와 같이 멱등 아닌 요청은 파이프라인을 통해 전송하면 안된다.