본문 바로가기

CS/네트워크

[HTTP] 04. 커넥션 관리

이 포스팅은 "HTTP-완벽 가이드" 책을 학습하고 정리한 것입니다.

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=49731592

 

HTTP 완벽 가이드

HTTP 규약이 어떻게 동작하고 웹 기반 애플리케이션을 개발하는 데 어떻게 사용하는지 설명한다. 하지만 이 책은 단순히 HTTP에 대해서만 다루지는 않는다. HTTP가 효율적으로 동작하도록 함께 사

www.aladin.co.kr

 

 

 


 

 

 

4.1 TCP 커넥션

 HTTP 통신은 패킷 교환 네트워크 프로토콜들의 계층화된 집합인 TCP/IP를 통해 이뤄진다. 처음 TCP 커넥션이 맺어지면, 해당 커넥션을 통해 HTTP 트랜잭션이 수행된다. TCP는 신뢰성 있는 통신을 보장하기 때문에, HTTP는 메시지 전송에만 집중할 수 있다.

 

4.1.1 신뢰할 수 있는 데이터 전송 통로인 TCP

 

참고자료

Understanding TCP Sequence and Acknowledgment Numbers - PacketLife.net

 

Understanding TCP Sequence and Acknowledgment Numbers - PacketLife.net

If you're reading this, odds are that you're already familiar with TCP's infamous "three-way handshake," or "SYN, SYN/ACK, ACK." Unfortunately, that's where TCP education ends for many networkers. Despite its age, TCP is a relatively complex protocol and w

packetlife.net

 TCP는 신뢰할 만한 통신 방식을 제공한다. TCP 커넥션의 한쪽에 있는 바이트들은 순서에 맞게 정확히 전달된다. Sequence 넘버와 Ack 넘버를 활용하여, 수신측은 전달받은 세그먼트들의 순서를 확인하여 조합할 수 있다.

 

 seq & ack 넘버에 관해서는 더 추가적인 학습이 필요하지만, 위 링크를 통해 정리해 보았을 떄, seq 넘버는 통신중인 세그먼트의 번호를 의미하고, ack 넘버는 현재까지 전달받은 데이터의 크기를 의미한다. 클라이언트와 서버 각각의 입장에서 보자면 다음과 같다.

 클라이언트 입장에서 seq 넘버는, 요청을 보낸(ex HTTP GET) 것에 대해서 응답을 전달 받을 때, 전달 받은 세그먼트가 여러 요청들 중에셔 어떤 요청에 대한 응답인지를 파악하기 위한 것이다. ack 넘버는 지금까지 서버로 부터 전송받은 데이터의 총량을 의미한다(따라서, 클라이언트가 보내는 ack넘버는 점점 증가할 것이다).

 서버 입장는 클라이언트에게 데이터를 전송하는 입장이다. 이러한 입장을 고려했을 때, seq 넘버는 현재까지 서버가 클라이언트에게 보낸 세그먼트(or 데이터 크기)의 총량을 의미하고, ack 넘버는 클라이언트로 부터 첫 요청을 받았던 때의 “클라이언트가 보낸 seq 넘버”이다.

위 부분은 현재까지 이해한 것을 정리한 것이며, 추가적인 학습이 필요하다.

 

4.1.2 TCP 스트림은 세그먼트로 나뉘어 IP 패킷을 통해 전송된다

 

 HTTP 메시지는 TCP의 ‘세그먼트’라는 단위로 나눠지고, IP 패킷(or 데이터그램)에 감싸져서 전송된다. 만약 HTTPS를 지원하는 애플리케이션이라면, TCP와 HTTP 사이에 TLS/SSL 계층이 추가되고, HTTP 메시지는 세그먼트로 나눠지기 전, 먼저 TLS/SSL에 의해 감싸지게 된다.

 TLS/SSL에 포장된 HTTP 메시지 조각은 아래와 같이 표현된다. TLS/SSL에 감싸지게 되면, 내부 데이터가 HTTP 메시지인지 FTP 메시지인지 구분할 수 없다. 암호화된 데이터이기 때문에, End2End에 속하는 호스트만이 해당 데이터를 복호화할 수 있을 것이다.

 

IP 패킷에 감싸진 TCP 세그먼트는 아래와 같은 구조를 갖는다.

 

 

 그런데, TCP를 통해 전송받은 세그먼트 중, 마지막 세그먼트가 어떤 것인지는 어떻게 알 수 있을까? FIN 태그를 통해 커넥션을 종료함으로써 알 수 있다. HTTP 트랜잭션의 관점에서 TCP를 바라본 탓에, TCP 또한 트랜잭션 단위로 나눠지지 않을까 오해하였다. TCP는 스트림 방식의 통신 계층이다. 스트림이란 말 그대로, 데이터가 물처럼 흘러갈 뿐, 각 데이터를 나눠서 구분하지는 않는다.

 

4.1.3 TCP 커넥션 유지하기

How do multiple clients connect simultaneously to one port, say 80, on a server?

 

How do multiple clients connect simultaneously to one port, say 80, on a server?

I understand the basics of how ports work. However, what I don't get is how multiple clients can simultaneously connect to say port 80. I know each client has a unique (for their machine) port. Doe...

stackoverflow.com

 TCP 커넥션은 네 가지 값으로 식별한다. 아래 네 가지 값으로 유일한 커넥션을 생성하며, 서로 다른 두 개의 TCP 커넥션은 네 가지 주소 구성요소의 값이 모두 같을 수는 없다. 만약에 같다면, 두 커넥션은 동일한 커넥션일 것이다.

<발신지 IP 주소, 발신지 포트, 수신지 IP 주소, 수신지 포트>

 

4.1.4 TCP 소켓 프로그래밍

 

 OS에서 제공하는 TCP API는, 기본적인 네트워크 프로토콜의 핸드셰이킹, 그리고 TCP 데이터 스트림과 IP 패킷 간의 분할 및 재조립에 대한 모든 세부사항을 외부로부터 숨긴다. 아래는 공통 소켓 인터페이스 함수들이다.

 

소켓 API 함수 설명
s = socket({parameters}) 연결이 되지 않은 익명의 새로운 소켓 생성
bind(s, <loacl IP:port>) 소켓에 로컬 포트 번호와 인터페이스 할당
connect(s, <remote IP:port>) 로컬의 소켓과 원격의 호스트 및 포트 사이에 TCP 커넥션 생성
listen(s, … ) 커넥션을 받아들이기 위해 로컬 소켓에 허용함을 표시
s2 = accept(s) 누군가 로컬 포트에 커넥션을 맺기를 기다림
n = read(s, buffer, n) 소켓으로부터 버퍼에 n바이트 읽기 시도
n = write(s, buffer, n) 소켓으로부터 버퍼에 n바이트 쓰기 시도
close(s) TCP 커넥션을 완전히 끊음
shutdown(s, <side>) TCP 커넥션의 입출력만 닫음

 

아래 그림은 클라이언트와 서버가 TCP 소켓 인터페이스를 사용하여 커넥션을 맺는 과정을 나타낸다.

 

4.2 TCP의 성능에 대한 고려

 

 HTTP 트랜잭션의 성능은 그 아래 계층인 TCP 성능에 영향을 받는다. 기본적인 TCP 성능의 특성을 이해함으로써, HTTP의 커넥션 최적화 요소들을 더 잘 알게 되고 더 좋은 성능의 HTTP 애플리케이션을 설계하고 구현할 수 있게 될 것이다.

 

4.2.1 HTTP 트랜잭션 지연

 

 HTTP 요청 & 응답의 전 과정은, (1) DNS 찾기 → (2) TCP 커넥션 설정 → (3) HTTP 요청 전송 → (4) HTTP 트랜잭션 처리 → (5) HTTP 응답 전송 의 과정을 거친다. 애플리케이션이 대용량의 데이터를 다루거나 복잡하지 않은 이상, 대부분의 HTTP 트랜잭션 지연은 TCP 네트워크 지연으로 인해 발생한다.

(그러나, 현재는 인터넷 인프라의 발전으로, DNS 요청 처리 & TCP 커넥션 맺기는 주요한 지연 원인은 아니다)

 

4.2.2 성능 관련 중요 요소

 

HTTP 성능에 영향을 주는 TCP 관련 지연들은 다음과 같다.

  • TCP 커넥션의 핸드셰이크 설정
  • 인터넷의 혼잡을 제어하기 위한 TCP의 느린 시작(slow-start)
  • 데이터를 한데 모아 한 번에 전송하기 위한 네이글(nagle) 알고리즘
  • TCP의 편승(piggyback) 확인응답(acknowledgement)을 위한 확인응답 지연 알고리즘
  • TIME_WAIT 지연과 포트 고갈

 

4.2.4 확인응답 지연

 

 확인응답은 그 크기가 작기 때문에, TCP는 같은 방향으로 송출되는 데이터 패킷에 확인응답을 ‘편승(piggyback)’시킨다. TCP는 송출 데이터 패킷과 확인응답을 하나로 묶음으로써 네트워크를 좀 더 효율적으로 사용하고자 한다. 확인응답 지연은 송출할 확인응답을 특정 시간 동안 버퍼에 저장해 두고, 확인응답을 편승시키기 위한 송출 데이터 패킷을 찾는다. 만약 일정 시간 안에 송출 데이터 패킷을 찾지 못하면 확인 응답은 별도 패킷을 만들어 전송된다. 만약, 이 알고리즘이 지연의 원인이 된다면 확인응답 지연 관련 기능을 수정하거나 비활성화 할 수 있다.

 

4.2.5 TCP 느린 시작

 

 TCP는 커넥션이 생성된 직후에는 최대 속도를 제한하고, 데이터가 성공적으로 전송됨에 따라 속도 제한을 높여간다. 인터넷에 급작스러운 부하와 혼잡을 방지하는데 사용된다(혼잡제어). 이 혼잡제어 기능 때문에, 새로운 커넥션은 이미 어느 정도 데이터를 주고받은 ‘튜닝’된 커넥션보다 느리다. HTTP에서는 지속 커넥션 사용을 방법을 제공하여, ‘튜닝’된 커넥션을 활용할 수 있게 한다.

 

4.2.6 네이글(Nagle) 알고리즘

 

 TCP가 작은 크기의 데이터를 포함한 많은 수의 패킷을 전송한다면 네트워크 성능이 크게 떨어질 수 있다. 이를 방지하고, 네트워크 효율을 높이기 위해, 패킷을 전송하기 전에 많은 양의 TCP 데이터를 한 개의 덩어리로 합치는 알고리즘을, 네이글 알고리즘이라고 한다. 다만, 다른 모든 패킷이 확인응답을 받았을 경우에는 최대 크기보다 작은 패킷이라도 전송을 허락한다. 다른 패킷들이 전송중이고 아직 응답을 못받은 상태라면, 최대 크기가 되기 전까지 버퍼에 저장된다.

 

 그러나, (1) 크기가 작은 HTTP 메시지는 패킷을 채우지 못하기 때문에 무한정 기다려야 될 수 있음 (2)확인응답 지연(piggy)과 함께 쓰일 경우, 전송 지연이 겹겹이 쌓여 느려질 수 있다는 단점이 존재한다.

네이글 알고리즘을 비활성화 하려면 HTTP 스택에 TCP_NODELAY 파라미터 값을 설정하면 된다.

 

4.2.7 TIME_WAIT의 누적과 포트 고갈

 

관련자료

리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 - 3편 : NHN Cloud Meetup

 

리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 - 3편 : NHN Cloud Meetup

리눅스 서버의 TCP 네트워크 성능을 결정짓는 커널 파라미터 이야기 - 3편

meetup.nhncloud.com

 TCP 커넥션의 종단에서 TCP 커넥션을 끊으면, 종단에서는 커넥션의 IP 주소와 포트 번호를 메모리의 작은 제어영역(control block)에 기록해 놓는다. 이 정보는 같은 주소와 포트 번호를 사용하는 새로운 TCP 커넥션이 일정 시간 동안에는 생성되지 않게 하기 위한 것이다. 이를 통해 이전 커넥션과 관련된 패킷이 그 커넥션과 같은 주소와 포트 번호를 가지고 새로운 커넥션에 삽입되는 문제를 방지한다.

 

4.3 HTTP 커넥션 관리

4.3.1 흔히 잘못 이해하는 Connection 헤더

 HTTP는 클라이언트와 서버 사이에 프록시 서버, 캐시 서버 등의 다른 노드가 존재하는 것을 허용한다. HTTP 메시지는 이러한 노드들을 거쳐서 End2End로 전달된다. 그런데, 어떠한 경우는 직접 통신하는 인접한 노드에게만 적용될 옵션을 지정해야 할 때가 있다.

 

 HTTP Connection 헤더 필드는 커넥션 토큰을 쉼표(’,’)로 구분하여 가지고 있으며, 그 값들은 다른 커넥션에 전달되지 않아야 한다. Connection 헤더에는 다음의 정보가 등록될 수 있다.

  • HTTP 헤더 필드명이 Connection 헤더에 등록될 수 있다. 해당 헤더 필드는 이 커넥션에만 적용되고 다음 홉으로 전달될 때는 삭제되어야 한다.
  • close 값은, 커넥션의 작업이 완료되면, 커넥션이 종료되어야 함을 나타낸다.

 커넥션 토큰이 HTTP 헤더 필드 명을 가지고 있으면, 해당 필드들은 현재 커넥션만을 위한 것으로 다음 커넥션에 전달해서는 안된다. 그러나, HTTP 메시지를 전달받은 홉이 이러한 규칙을 수행할 것이라는 보장은 없다.

4.3.2 순차적인 트랜잭션 처리에 의한 지연

 아래 이미지는 순차적으로 진행되는 트랜잭션 처리를 나타낸다.

 같은 서버와 통신하면서, 각각의 HTTP 트랜잭션을 처리할 때마다 커넥션을 새롭게 맺는다면, 커넥션 연결과 느린 시작으로 인한 지연이 발생할 수 있다. HTTP 커넥션의 성능을 향상시키기 위해, ‘병렬 커넥션’, ‘지속 커넥션’, ‘파이프라인 커넥션’, ‘다중 커넥션’ 기술들을 제공한다.

 

4.4 병렬 커넥션

 

HTTP는 클라이언트가 여러 개의 커넥션을 맺음으로써 여러 개의 HTTP 트랜잭션을 병렬로 처리할 수 있게 한다.

4.4.1 병렬 커넥션은 페이지를 더 빠르게 내려받는다

 단일 커넥션은 특정 대역폭의 제한을 갖는다. 또한, HTTP 메시지를 전달한 이후에는, ACK을 받기 전까지 대기하기 때문에, 동작하지 않는 시간이 존재한다. 병렬 커넥션을 사용함으로써, 대역폭을 최대한 활용할 수 있고, 여러 커넥션에서 비슷한 시점에 메시지들을 전송하여, 커넥션 지연 시간을 겹치게 하고, 이를 통해 총 지연시간을 줄일 수 있다.

 

4.4.2 병렬 커넥션이 항상 더 빠르지는 않다

 

 네트워크 대역폭이 좁을 때, 대부분의 시간을 데이터 전송하는데( or 전송받는데) 사용하기 때문에, 병렬 커넥션은 성능상 이점을 얻지 못할 수 있다. 병렬 커넥션의 핵심의 커넥션 지연시간을 겹치게 함으로써 총 지연시간을 줄이는 것인데, 네트워크 대역폭이 최대로 사용될 경우, 병렬 커넥션은 마치 단일 커넥션으로 통신하는 것처럼 유사할 수 있다.

 

 또한, 다수의 커넥션을 맺는 것은 메모리에 부하를 줄 수 있고, 이는 성능 문제를 야기한다. 따라서, 최신 브라우저에서는 6~8개의 커넥션까지만 맺을 수 있도록 제한하고 있다.

 

4.5 지속 커넥션

 

 서버에 HTTP 요청을 하기 시작한 애플리케이션은 웹페이지 내의 이미지 등을 가져오기 위해서 그 서버에 반복적으로 요청할 수 있다. 이 속성을 사이트 지역성(site locality)라 부른다. HTTP/1.1~ 부터는 처리가 완료된 이후에도 커넥션을 유지하여, 이후의 HTTP 트랜잭션에 활용할 수 있도록 한다.

 지속 커넥션은 HTTP 커넥션을 재사용함으로써, 커넥션을 맺을 때의 비용 반복을 줄여준다. 또한, 신규 커넥션 생성 시 발생하는 느린 시작으로 인한 지연을 회피할 수 있다.

 

4.5.1 지속 커넥션 vs 병렬 커넥션

 

 지속 커넥션은 병렬 커넥션과 함께 사용될 때에 가장 효과적이다. 병렬 커넥션은 새로운 커넥션을 맺기 때문에, 커넥션 생성 시간과, 신규 TCP 커넥션에 대한 느린 시작으로 인해 지연을 유발할 수 있다. 지속 커넥션을 사용하여 튜닝된 커넥션을 재사용한고, 신규 커넥션 생성 비용을 줄일 수 있다. 하지만 지속 커넥션을 잘못 관리할 경우, 연결 상태에 있는 커넥션이 계속 쌓이게 되어, 불필요한 소모를 야기할 수 있다.

 

4.5.2 HTTP/1.0+의 Keep-Alive 커넥션

 

 ‘Connection: Keep-Alive’ 와 같이 헤더를 꾸민다 .이 요청을 받은 서버는 그 다음 요청도 이 커넥션을 통해 받고자 한다면, 응답 메시지에 같은 헤더를 포함시키다. 만약, 서버로 부터 받은 응답에 ‘Connection: Keep-Alive’ 헤더가 없다면, 클라이언트는 서버가 keep-alive를 지원하지 않으며, 응답 메시지가 전송되고 나면 서버 커넥션을 끊을 것이라고 예상한다.

 

4.5.4 Keep-Alive 옵션

 

 클라이언트나 서버가 keep-alive 요청을 받았다고 해서 무조건 따를 필요는 없다. 언제든지 현재의 커넥션을 끊을 수 있으며, keep-alive 커넥션에서 처리되는 트랜잭션의 수를 제한할 수 있다.

  • timeout : 커넥션이 얼마간 유지될 시 시간을 나타냄
  • max : 한 커넥션이 수행할 최대 HTTP 트랜잭션 수를 지정
Connection : Keep-Alive
Keep-Alive : max=5, timeout=180

 

4.5.5 Keep-Alive 커넥션 제한과 규칙

  • HTTP/1.0에서 keep-alive는 정식으로 사용되지는 않는다.
  • keep-alive 커넥션을 사용하기 위해, Connection:Keep-Alive 헤더를 보내야 한다.
  • 커넥션을 계속 유지하려면 모든 메시지에 위 헤더를 포함해야 한다.
  • 엔터티 본문의 길이(Content-Length)가 정확해야, 올바른 때에 Keep-Alive 커넥션을 종료할 수 있다.
  • 프락시와 게이트웨이는 Connection 헤더의 규칙을 철저히 지켜야 한다. 메시지를 전달하거나 캐시에 넣기 전, Connection 헤더에 명시된 모든 헤더 필드와 Connection 헤더를 제거해야 한다.
  • 기술적으로 HTTP/1.0을 따르는 기기로부터 받는 모든 Connection 헤더 필드는 무시해야 한다. 오래된 프록시에 헹이 걸릴 수 있기 때문이다.

 

4.5.8 HTTP/1.1의 지속 커넥션

 

 HTTP/1.0의 keep-alive 커넥션과는 달리 HTTP/1.1의 지속 커넥션은 기본으로 활성화되어 있다. HTTP/1.1에서는 별도 설정을 하지 않는 한, 모든 커넥션을 지속 커넥션으로 취급한다. HTTP/1.1 애플리케이션은 트랜잭션이 끝난 다음 커넥션을 끊으려면 Connection: close 헤더를 명시해야 한다. 이는 keep-alive 커넥션이 선택사항이 아닐 뿐만 아니라 지원 자체를 하지 않다는 점에서 이전 HTTP 프로토콜과는 크게 다르다.

 

4.6 파이프라인 커넥션

 

 HTTP/1.1은 지속 커넥션을 통해서 요청을 파이프라이닝할 수 있다. 여러 개의 요청은 응답이 도착하기 전까지 큐에 쌓인다. HTTP 클라이언트는 커넥션이 지속 커넥션인지, 서버로 부터 응답을 받기 전까지 파이프라인을 이어서는 안되기 때문이다. 응답을 통해 지속 커넥션임을 확인하면, 큐에 있는 나머지 요청들도 전송한다. 이를 통해 대기 시간이 긴 네트워크 상황에서 전송 대기시간을 줄일 수 있다. 다만, 몇 가지 제약사항이 존재한다.

  • HTTP 응답은 요청 순서와 같게 와야한다. HTTP 메시지는 순번이 매겨져 있지 않기 때문에, 순서에 맞게 정렬시킬 방법이 없기 때문이다
  • HTTP 클라이언트는 응답을 받기 전까지는, 요청을 보냈던 메시지들을 유지시켜야 한다. 여러 요청을 보낸 이후, 서버와의 커넥션이 끊어진다면, 응답을 받지 못한 메시지들을 다시 전송해야 되기 때문이다.
  • POST와 같은 비멱등 요청을 재차 보내면 문제가 생길 수 있기 때문에, 이러한 요청들은 파이프라이닝을 통해 반복해서 전송하면 안된다.

'CS > 네트워크' 카테고리의 다른 글

[HTTP] 06. 프락시  (2) 2024.01.07
[HTTP] 05. 웹 서버  (2) 2024.01.05
[HTTP] 03. HTTP 메시지  (0) 2024.01.02
[HTTP] 02. URL과 리소스  (0) 2024.01.02
[HTTP] 01. HTTP 개관  (0) 2024.01.02