Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[1부 4장] 커넥션 관리 #4

Open
ruthetum opened this issue Aug 14, 2022 · 1 comment
Open

[1부 4장] 커넥션 관리 #4

ruthetum opened this issue Aug 14, 2022 · 1 comment

Comments

@ruthetum
Copy link
Member

4장 커넥션 관리

Prologue

  • HTTP 명세는 HTTP 메시지 포맷에 대해서 구체적으로 정의하고 있지만, HTTP 커넥션과 메시지 흐름에 대한 내용은 충분히 다루지 않고 있다.

  • 왜? -> HTTP는 application layer protocol이고, 커넥션 및 전송과 관련된 부분은 하위 layer 기반해서 작동하기 때문

  • 이번 장에서는 아래 항목들에 대해서 언급한다.

    • HTTP가 어떻게 TCP 커넥션을 사용하는지
    • TCP 커넥션의 지연, 병목, 막힘 현상
    • 병렬 커넥션, keep-alive 커넥션, 커넥션 파이프라인을 통한 HTTP 최적화
    • 커넥션 관리를 위해 따라야 할 규칙들
  • HTTP에 대한 내용보다는 HTTP 프로토콜을 지원하기 위한 하위 계층의 커넥션 및 기반 기술에 대해 설명

TCP 커넥션

  • 대~~~부분(거의 모든) HTTP 통신은 TCP/IP를 통해 이루어진다.

  • connection이 맺어지면 peer간에 주고 받는 메시지들은 손실/손상되지 않거나 순서가 바뀌지 않고 안전하게 전달된다.

TCP의 메시지 전송

  • TCP 스트림은 세그먼트로 나뉘어 IP 패킷(IP datagram)을 전송한다.

    • Transport layer에서 TCP 전송 단위는 segment, UDP 전송 단위는 datagram
    • Network layer의 전송 단위는 packet
  • HTTP는 IP, TCP, HTTP로 구성된 프로토콜 스택에서 최상위 계층이고, 여기에 보안 기능을 더한 것이 HTTPS

    image

    • TCP와 HTTP 사이에 보안 계층(TLS or SSL) 추가
    • HTTPS 구현, 작동 원리는 TL;DR (1장 참고)

TCP 세그먼트

image

  • TCP 세그먼트는 하나의 IP 주소에서 다른 IP 주소로 IP 패킷에 담겨 전달

TCP 유지

  • 컴퓨터는 항상 TCP 커넥션을 여러 개 가지고 있다.
  • TCP는 포트 번호를 통해서 여러 개의 커넥션을 유지한다.
<발신지 IP 주소>,<발신지 포트>,<수신자 IP주소>,<수신지 포트>

TCP 소켓 프로그래밍

image

cf. https://github.com/Effective-Java-Camp/http-the-definitive-guide/tree/main/examples/tcp-socket

- 예제에서 buffer size를 왜 1460바이트로 설정했을까?
    
    - Ethernet MTU 1500 바이트

TCP 성능에 대한 고려

  • HTTP는 TCP 위에 있는 계층이기 때문에 HTTP의 트랜잭션 성능은 그 아래 계층인 TCP 성능에 영향을 받는다.

image

  • 트랜잭션을 처리하는 시간은 TCP 커넥션을 설정gkrh, 요청을 전송하고, 응답 메시지를 보내는 것에 비하면 비교적 짧다.

  • 너무 많은 데이터를 내려받거나 복잡한 처리가 수반되는 실행이 아닌 이상 대부분의 HTTP 지연은 TCP 네트워크 지연때문에 발생한다.

HTTP 트랜잭션을 지연시키는 원인

  1. 클라이언트는 URI에서 웹 서버의 IP 주소와 포트번호를 알아내야 한다.

    • URI에 기술되어 있는 호스트를 방문한 적이 없는 경우 DNS를 활용해야 하고 이 과정에서 지연이 발생
  2. 클라이언트는 TCP 커넥션 요청을 서버에게 보내고 서버가 커넥션 허가 응답을 회신하기를 기다린다. (3-way handshake)

    • 커넥션 설정 시간은 새로운 TCP 커넥션에서 항상 발생하고, 보통 1-2초의 시간이 소요되지만 수 백개의 HTTP 트랜잭션이 만들어지는 경우 소요 시간이 크게 증가한다.
  3. 커넥션이 맺어지면 클라이언트는 HTTP 요청을 새로 생성된 TCP 파이프를 통해 전송한다.

    • 웹 서버는 데이터가 도착하는 대로 TCP 커넥션에서 요청 메세지를 읽고 처리한다.
    • 요청 메시지가 인터넷을 통해 전달되고, 서버에 의해서 처리되는 데 까지는 시간이 소요된다.
  4. 웹 서버가 HTTP 응답을 보내는 것 역시 시간이 소요된다.

TCP 네트워크 지연

  • TCP 네트워크 지연은 하드웨어 성능, 네트워크와 서버의 전송 속도, 요청과 응답 메시지의 크기, 클라이언트와 서버 간의 거리에 따라 크게 달라진다.

TCP 커넥션의 핸드셰이크 지연

  • 어떤 데이터를 전송하든 새로운 TCP 커넥션을 열게 되면 핸드셰이크 과정이 발생된다.
  • 작은 크기의 데이터 전송에 계속해서 커넥션이 사용된다면 이러한 패킷 교환은 HTTP 성능을 크게 저하시킬 수 있다.
3-Way handshake
  • 송신자(Client)와 수신자(Server) 모두 데이터를 전송할 준비가 되었다는 것을 보장하며, 데이터 전달 시작전에 상대가 준비되었다는 것을 알 수 있게 한다.
  1. Client > Server : SYN (저 세션연결 시작할게요 서버님)

    • 클라이언트가 서버에게 SYN(SYNchronization)플래그를 보낸다. (sequence: x)
  2. Server > Client : ACK (어 연락받았어 잘들리네) SYN (나도 연결할게)

    • 서버가 SYN(x)을 받고, 클라이언트로 받았다는 신호인 ACK(ACKnowledgement) 와 SYN 플래그를 보낸다. (sequence: y, ACK: x + 1)
  3. Client > Server : ACK (넵 알겠습니당 저도 잘들려여)

    • 클라이언트는 서버의 응답으로 ACK(x + 1)와 SYN(y) 플래그를 받고, ACK(y+1)를 서버로 보낸다.

image

cf. Sequence Number를 랜덤으로 생성하는 이유?

  • 3 way handshake 과정에서 클라이언트는 SYN 플래그를 보낼 때 Sequence number에 랜덤으로 숫자 값을 담아서 전송한다.

    • 이러한 초기 sequece number를 ISN이라고 한다.
  • 그렇다면 ISN이 0부터 시작하지 않고 난수를 생성해서 값을 설정하는 이유는 무엇일까?

    • Connection을 맺을 때 사용하는 포트(port)는 유한 범위 내에서 사용하고 시간이 지남에 따라 재사용된다.
    • 따라서 두 통신 호스트가 과거에 사용된 포트 번호 쌍을 사용하는 가능성이 존재한다.
    • 서버 측에서는 패킷의 SYN을 보고 패킷을 구분하게 되는데 난수가 아닌 순차적인 number가 전송된다면 이전의 connection으로부터 오는 패킷으로 인식할 수 있다.
    • 이러한 문제가 발생할 가능성을 줄이기 위해서 난수로 ISN을 설정한다.
4-Way handshake
  • 송신자(Client)와 수신자(Server) 모두 세션을 종료함을 인지한다.
  • 세션종료를 의미하는 FIN 패킷을 서버측에서 받더라도, 패킷유실이나 라우팅지연으로 인한 재전송으로 인한 잔여데이터를 위해 Client는 서버측에서 FIN을 수신하더라도 일정시간(default 240 sec) 동안 ACK 패킷을 서버측에 보내지않고 잉여 패킷을 기다린다. (TIME_WAIT)
  1. Client > Server : FIN (저 이제 세션종료할게요 서버님)

    • 클라이언트는 서버에게 연결을 종료한다는 FIN(FINish) 플래그를 보낸다.
  2. Server > Client : ACK (어 연락받았어 잘들리네 잠만 기다려봐 보내라고 한거 마저 보내줄게)

    • 서버는 FIN을 받고, 확인했다는 ACK를 클라이언트에게 보낸다. 이 때 모든 데이터를 보내기 위해 TIME OUT 상태가 된다.
  3. Server > Client : FIN (일단 급한대로 다 보냈다. 확인하고 종료해줘)

    • 데이터를 모두 보냈다면, 연결이 종료되었다는 FIN 플래그를 클라이언트에게 보낸다.
  4. Client > Server : ACK (아이고 감사합니다. 다 잘받았네여)

    • 클라이언트는 FIN을 받고, 확인했다는 ACK를 서버에게 보낸다. 아직 서버로부터 받지 못한 데이터가 있을 수 있으므로 TIME_WAIT을 통해 기다린다.

image

cf1. https://mindnet.tistory.com/entry/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-22%ED%8E%B8-TCP-3-WayHandshake-4-WayHandshake

cf2. https://yoon1fe.tistory.com/165

확인응답(Acknowledgement) 지연

  • 인터넷 자체가 패킷 전송을 완벽히 보장하지 않기 때문에 TCP는 성공적인 데이터 전송을 보장하기 위해서 자체적인 확인 체계를 가진다.
    • 라우터는 과부하가 걸렸을 때 패킷을 마음대로 파기할 수 있다.
  • 각 TCP 세그먼트는 순번과 데이터 무결성 체크섬을 가진다. 세그먼트의 수신자는 세그먼트를 온전히 받으면 확인응답 패킷을 송신자에게 반환한다.
    • 송신자가 특정 시간 안에 확인응답 메시지를 받지 못 하면 패킷이 파기되었거나 오류가 있는 것으로 판단하고 데이터를 다시 전송한다.
  • 확인응답은 크기가 작기 때문에, TCP는 같은 방향으로 송출되는 데이터 패킷에 확인응답을 편승(piggyback)시킨다.
    • 네트워크를 좀 더 효율적으로 사용하기 위해 송출하는 데이터 패킷과 확인응답을 하나로 묶어서 전송한다.
  • 확인응답을 보내야 하는데 당장 송출된 데이터가 없다면 특정 시간동안(100~200ms) 버퍼에 저장해두고, 송출할 데이터 패킷을 기다린다.
    • 특정 시간 이후동안 송출할 패킷을 찾지 못 한다면 별도의 패킷을 생성하여 확인응답을 전송한다.
    • HTTP 동작 방식의 특성을 고려할 때 편승할 패킷을 찾으려고 하는 경우 해당 방향으로 송출될 패킷이 많지 않기 때문에 확인응답 지연 알고리즘에 의한 지연이 자주 발생한다.
      • OS에 따라 다르지만 지연의 원인이 되는 확인응답 지연 관련 기능을 수정하거나 비활성화할 수 있다.

TCP 느린 시작(slow-start)

  • TCP의 데이터 전송 속도는 TCP 커넥션이 만들어진지 얼마나 지났는지에 따라 달라질 수 있다.
  • TCP 커넥션은 시간이 지나면서 자체적으로 튜닝되어서, 처음에는 커넥션의 최대 속도를 제한하고 데이터가 성공적으로 전송됨에 따라서 속도 제한을 높여나간다.
  • 이렇게 조율하는 것을 TCP slow-start라고 부르고, 이를 통해 인터넷의 급작스러운 부하와 혼잡을 방지한다.
  • 이러한 혼잡제어 기능 때문에 새로운 커넥션은 이미 어느 정도 데이터를 주고받은 튜닝된 커넥션보다 느리다. 튜닝된 커넥션은 더 빠르기 때문에 HTTP에는 이미 존재하는 커넥션을 재사용하는 기능이 있다. -> 지속 커넥션
    • 혼잡제어 관련 내용은 후술

네이글(nagle) 알고리즘과 TCP_NODELAY

  • 어떠한 크기의 데이터든지 TCP 스택으로 전송하게 되면 TCP 세그먼트를 구성해야 한다.
    • 하지만 매우 작은 크기의 데이터를 전송하는 경우에도 40바이트 정도의 플래그와 헤더를 포함하여 전송하기 때문에 작은 크기의 데이터를 포함한 많은 수의 패킷을 전송하는 경우 네트워크 성능이 떨어진다.
  • 이를 해결하기 위해 패킷을 전송하기 전에 많은 양의 TCP 데이터를 한 개의 덩어리로 합치고, 이 알고리즘이 네이글 알고리즘이다.
  • 네이글 알고리즘은 세그먼트가 최대 크기(이더뎃 기준 MTU 1500바이트)가 되지 않으면 전송을 하지 않는다.
    • 다만 다른 모든 패킷이 확인응답을 받았을 경우에는 최대 크기보다 작은 패킷의 전송을 허락한다.
    • 다른 패킷들이 아직 전송 중이면 데이터의 버퍼에 저장된다. 전송되고 나서 확인응답을 기다리던 패킷이 확인응답을 받았거나 전송하기 충분할 만큼의 패킷이 쌓였을 때 버퍼에 저장되어 있던 데이터가 전송된다.
  • 네이글 알고리즘은 HTTP 성능과 관련해서 여러 문제를 발생시킨다.
      1. 크기가 작은 HTTP 메시지는 패킷을 채우지 못 하기 때문에 계속해서 데이터를 기다리며 지연된다.
      1. 네이글 알고리즘은 확인응답 지연과 함께 쓰일 경우 확인응답이 도착할 때까지 데이터 전송을 멈추고 있는 반면, 확인응답 지연 알고리즘은 확인응답을 100-200ms를 지연시킨다.
  • HTTP 애플리케이션에서는 HTTP 스택에 TCP_NODELAY 파라미터 값을 설정하여 네이글 알고리즘을 비활성화기도 한다.

TIME_WAIT 지연과 포트 고갈

  • TCP 커넥션을 끊으면, 커넥션의 종단에서는 커넥션의 IP 주수와 포트 번호를 메모리의 제어 영역에 기록해놓는다.
    • 같은 주소와 프트 번호를 사용하는 새로운 커넥션이 일정 시간동안 생성되지 않게 하기 위함
    • 보통 2분 정도 유지 (2MSL)
    • 이전 커넥션과 관련된 패킷이 해당 커넥션과 같은 주소, 포트 번호를 가지는 새로운 커넥션에 삽입되는 문제를 방지하기 위함
  • 일반적으로 2MSL이라는 종료 지연이 문제가 되지는 않지만, 성능 테스트를 하는 상황에서는 문제가 될 수 있다.
    • 성능 측정 대상 서버는 클라이언트가 접속할 수 있는 IP 주소의 개수를 제한하고, 접속하는 컴퓨터의 수가 제한적이기 때문에 생성할 수 있는 주소와 포트 조합이 제한적이다.
    • TIME_WAIT으로 인해 순간순간 포트를 재활용하는 것이 불가능해진다.

cf. TCP 흐름 제어 및 혼잡 제어 (+ 오류 제어)

흐름 제어(Flow control)

  • 송신측과 수신측의 데이터 처리 속도 차이를 해결하기 위한 기법

    • 수신측이 송신측보다 데이터 처리 속도가 빠르면 문제없지만, 송신측의 속도가 빠를 경우 문제가 생긴다.
  • Flow Control은 receiver가 packet을 지나치게 많이 받지 않도록 조절하는 것

  • 기본 개념은 receiver가 sender에게 현재 자신의 상태를 feedback 한다는 점

해결방법

  • Stop and Wait : 매번 전송한 패킷에 대해 확인 응답을 받아야만 그 다음 패킷을 전송하는 방법
  • Sliding Window (Go Back N ARQ) : 먼저 윈도우에 포함되는 모든 패킷을 전송하고, 그 패킷들의 전달이 확인되는대로 이 윈도우를 옆으로 옮김으로써 그 다음 패킷들을 전송

혼잡 제어(Congestion control)

  • 송신측의 데이터 전달과 네트워크의 데이터 처리 속도 차이를 해결하기 위한 기법

    • 송신측의 데이터는 지역망이나 인터넷으로 연결된 대형 네트워크를 통해 전달된다. 만약 한 라우터에 데이터가 몰릴 경우, 자신에게 온 데이터를 모두 처리할 수 없게 된다. 이런 경우 호스트들은 또 다시 재전송을 하게되고 결국 혼잡만 가중시켜 오버플로우나 데이터 손실을 발생시키게 된다. 따라서 이러한 네트워크의 혼잡을 피하기 위해 송신측에서 보내는 데이터의 전송속도를 강제로 줄이게 되는데, 이러한 작업을 혼잡제어라고 한다.

해결방법

  • AIMD(Additive Increase / Multiplicative Decrease)

    처음에 패킷을 하나씩 보내고 이것이 문제없이 도착하면 window 크기(단위 시간 내에 보내는 패킷의 수)를 1씩 증가시켜가며 전송하는 방법

    패킷 전송에 실패하거나 일정 시간을 넘으면 패킷의 보내는 속도를 절반으로 줄인다.

  • Slow Start (느린 시작)

    AIMD 방식이 네트워크의 수용량 주변에서는 효율적으로 작동하지만, 처음에 전송 속도를 올리는데 시간이 오래 걸리는 단점이 존재

    Slow Start 방식은 AIMD와 마찬가지로 패킷을 하나씩 보내면서 시작하고, 패킷이 문제없이 도착하면 각각의 ACK 패킷마다 window size를 1씩 늘려준다. 즉, 한 주기가 지나면 window size가 2배로 됨

  • Fast Retransmit (빠른 재전송)

    패킷을 받는 쪽에서 먼저 도착해야할 패킷이 도착하지 않고 다음 패킷이 도착한 경우에도 ACK 패킷을 보냄

    중복된 순번의 패킷을 3개 받으면 재전송

    (혼잡한 상황이 일어난 것이므로 혼잡을 감지, window size를 줄임)

  • Fast Recovery (빠른 회복)

    혼잡한 상태가 되면 window size를 1로 줄이지 않고 반으로 줄이고 선형증가시키는 방법

HTTP 커넥션 관리

HTTP Connection 헤더

  • HTTP는 클라이언트와 서버 사이에 proxy server, cache server 등과 같은 중개 서버가 놓이는 것을 허락한다.
  • HTTP Connection header 필드는 커넥션 토큰을 쉼표로 구분하고, 홉별(hop-by-hop) 헤더 명을 기술한다.
    • hop-by-hop : 특정 두 서버 간에만 영향을 미치고 다른 서버 간에는 영향을 미치지 않는다.
    • 메시지를 다른 곳으로 전달하는 시점에 삭제하여 다음 커넥션에 전달하지 않는다.
    • connection header 값이 다음 connection에 영향을 끼치지 않게 한다.
  • HTTP 애플리케이션이 connection header와 함께 메시지를 전달받으면, 수신자는 송신자에게서 온 요청에 기술되어 있는 모든 옵션을 적용한다.
  • connection header는 전송자가 특정 커넥션에만 해당되는 옵션을 지정하게 해준다.

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

  • 커넥션 관리가 제대로 이루어지지 않으면 TCP 성능이 매우 좋지 못해진다.
    • ex. 커넥션 생성 과정에서의 지연, slow-start
  • 순차적인 처리로 인한 지연은 물리적인 지연 외에 사용자 경험과도 관련이 있다.
    • 웹 페이지 렌더렁 시 여러 개의 이미지를 내려받는 경우 사용자들은 순차적으로 하나의 이미지가 로드되는 방법보다 여러 개의 이미지가 동시에 로드되는 것을 좋아한다.
  • 또한 순차적인 로드를 진행하는 경우 브라우저에서 객체를 렌더링할 때 객체의 크기를 알아야 하기 때문에 모든 객체를 내려받기 전까지 빈 화면을 보여줄 수 밖에 없다.
    • 브라우저 입장에서는 연속해서 하나씩 내려받는 것이 효율적이지만, 사용자 경험 관점에서는 효과적이지 못하다.

HTTP 커넥션 성능을 향상시킬 수 있는 4가지 기술

  • 병렬(parallel) 커넥션
    • 여러 개의 TCP 커넥션을 통한 동시 HTTP 요청
  • 지속(persistent) 커넥션
    • 커넥션을 맺고 끊는 데서 발생하는 지연을 제거하기 위한 TCP 커넥션의 재활용
  • 파이프라인(pipelined) 커넥션
    • 공유 TCP 커넥션을 통한 병렬 HTTP 요청
  • 다중(multiplexed) 커넥션

병렬 커넥션

  • 여러 개의 TCP 커넥션을 통한 동시 HTTP 요청을 지원한다.

image

  • 보통 페이지를 더 빠르게 내려받을 수 있다.

  • 하지만 항상 병렬 커넥션이 더 빠르지는 않다.

    • 제한된 대역폭 내에서는 여러 개의 커넥션을 이용하여 요청/응답받는 것에 대한 장점이 거의 없어진다.
  • 더 빠르게 로드하지 못 하더라도 사용자 경험 관점에서 빠르게 느껴질 수 있다.

    • 오브젝트를 동시에 로드하기 때문에 사용자 기준에서 여러 작업이 일어나는 것을 눈으로 확인할 수 있다.
  • 병령 커넥션은 여러 객체가 있는 페이지를 빠르게 전송하지만 단점이 존재한다.

    • 각 트랜잭션마다 새로운 커넥션을 맺고 끊어야 한다. -> 추가적인 시간/대역폭 소모
    • 새로운 커넥션은 slow-start 지연에 영향을 받는다.
    • 실제로 연결할 수 있는 병렬 커넥션 수에 제한이 있다.

지속 커넥션

  • 요청 처리가 완료된 후에도 TCP 커넥션을 유지하여 앞으로 있을 HTTP 요청에 대해 재사용할 수 있다.
    • 커넥션 재사용 -> 튜닝된 커넥션 사용 -> 커넥션 생성 지연 + slow-start로 인한 지연 회피

image

  • 지속 커넥션은 병렬 커넥션과 함께 사용될 때 효과적이다.
    • 애플리케이션은 적은 수의 병렬 커넥션을 맺고, 그 커넥션을 유지한다.

Keep-Alive

  • keep-alive: TCP 커넥션을 종료된 이후에도 얼마나 유지하고 있을 것 인가?

    • 표준 헤더 명세도 아니고, 사용이 크게 권장되지도 않는다.
    • HTTP/1.0과 HTTP/2.0에서는 사용되지 않는다.
  • keep-alive header는 선택 사항이지만 Connection: Keep-Alive 헤더가 있을 때만 사용할 수 있다.

    # 서버가 5개의 추가 트랜잭션이 처로딜 동안 2분동안 커넥션을 유지
    Connection: Keep-Alive
    Keep-Alive: max=5, timeout=120
    
  • 단, 무조건 좋은 기능은 아니다.

    • 오히려 성능을 하락 시키는 경우가 존재한다.
    • 사용자가 많다면 유지되는 연결이 늘어나서 새로운 사용자를 받아들이지 못하는 경우가 빈번히 일어난다.
    • 그래서 사용자가 많고 유동이 많은 서비스에서는 사용이 권장되지 않는다.
  • 멍청한(dumb) 프락시 문제

    image

    • 클라이언트는 keep-alive 헤더를 전송했지만 프락시 서버가 keep-alive 헤더를 단순 확장 헤더로 취급하는 경우에 문제가 발생한다.

    • 원래 Connection 헤더는 hop-by-hop header이기 때문에 전송하면 안되지만 프락시 서버는 keep-alive가 무엇인지 모르기 때문에 서버에 그대로 전달하게 된다.

    • 프락시 서버와 서버 사이에서는 서버는 커넥션을 끊지 않고, 유지하려고 하기 때문에 대기하게 되고, 프락시 서버는 커넥션이 끊어지기를 기다리게 된다.

    • 클라이언트와 프락시 서버 간에는 서버와의 커넥션이 끊어지는 것을 기다리고 있기 때문에 keep-alive 커넥션을 통해 오는 클라이언트의 추가적인 요청을 처리하지 않게 된다.

    image

    • proxy-connection header(확장 헤더)를 활용해서 클라이언트와 프락시 간, 프락시와 서버간 keep-alive 커넥션을 방지한다.
      • 다만 프락시 서버가 많은 구조에서 여전히 문제가 생길 수 있다.
      • 영리한 프락시가 변환해준 connection이 dumb proxy에 들어가는 경우 혹은 반대의 경우 문제가 발생한다.

HTTP/1.1의 지속 커넥션

  • HTTP/1.1은 keep-alive 커넥션을 지원하지 않는 대신 설계적으로 더 개선된 지속 커넥션을 지원한다.
  • HTTP/1.1에서는 지속 커넥션은 기본으로 활성화된다. HTTP/1.1은 별도 설정이 없는 한 모든 커넥션을 지속 커넥션으로 취급한다. HTTP/1.1은 반대로, 끊어야 할 때 Connection: close를 보내도록 한다.
  • 물론 클라이언트나 서버는 임의로 끊을 수도 있다.

지속 커넥션의 제한과 규칙

  • 클라이언트가 요청에 Connection: close를 포함하면, 이후에는 추가적인 요청을 보낼 수 없다.
  • 커넥션에 있는 모든 메시지가 자신의 길이 정보를 정확히 가지고 있을 경우에만 커넥션을 지속할 수 있다. (Content-Length)
  • HTTP/1.1 프락시는 클라이언트와 서버 각각에 별도의 지속 커넥션을 맺고 관리해야 한다.
  • HTTP/1.1 프락시는 클라이언트의 지원 범위를 알고 있지 않은 한 지속 커넥션을 맺어서는 안 된다.
  • HTTP/1.1 클라이언트는 Connection 헤더와는 무관하게 언제든지 연결을 끊을 수 있다.
  • 클라이언트는 전체 응답을 받기 전에 커넥션이 끊어지면 다시 보낼 수 있어야 한다.
  • 클라이언트는 과부하를 방지하기 위해서라도 사용자 1명 당 2개의 커넥션만을 준비한다. 마찬가지로 프락시나 서버도 그러하다.

파이프라인 커넥션

image

  • HTTP Pipelining 이란 HTTP1.1로 스펙이 업그레이드 되면서 클라이언트와 서버간 요청과 응답의 효율성을 개선하기 위해 만들어진 개념이다.
  • HTTP 요청들은 연속적으로 발생하며, 순차적으로 동작한다.
  • HTTP/1.0 에서 HTTP 요청는 소켓에 write 한뒤, 서버의 응답을 받아 다음 요청를 보내는 방식으로 웹이 동작한다.
  • 여러 요청에 대해 여러 응답을 받고, 각 처리가 대기되는 것은 네트워크 지연에 있어서 큰 비용을 요구한다.
  • HTTP/1.1에서는 다수의 HTTP 요청들이 각각의 서버 소켓에 Write 된 후, Browser 는 각 요청들에 대한 응답들을 순차적으로 기다리는 문제를 해결하기 위해 여러 요청들에 대한 응답 처리를 뒤로 미루는 방법을 사용한다.
  • 파이프라이닝이 적용되면, 하나의 커넥션으로 다수의 요청/응답을 처리할 수 있게끔 네트워크 지연을 줄일 수 있다.
  • 결국 완전한 멀티플렉싱이 아닌 응답처리를 미루는 방식이므로 각 응답의 처리는 순차적으로 처리되며, 결국 후순위의 응답은 지연될 수 밖에 없다.

커넥션 종료

  • 커넥션 관리(언제 어떻게 커넥션을 끊는가)에는 명확한 기준이 없다.

1. 마음대로 커넥션 끊기

  • 서버가 임으로 클라이언트와의 커넥션을 끊을 때는 보통 휴지 기간이 긴 경우, 더 이상 클라이언트에게 요청이 오지 않을 것을 확신할 대 끊게 된다.
  • 하지만 이 경우에 클라이언트가 요청을 보낸다면 문제가 발생할 수 있다.

2. Content-Length와 Truncation

  • HTTP 응답은 Content-Length 헤더를 포함한다.
  • 일부 오래된 HTTP 서버는 자신이 커넥션을 끊으려 하면 데이터 전송이 끝났음을 의미하는 형태로 개발됐기 때문에 Content-Length 헤더를 생략하거나 잘못된 입력하는 경우가 있다.
  • 클라이언트나 프락시가 커넥션이 끊어졌다는 HTTP 응답을 받은 후, 실제 전달된 엔티티의 길이와 Content-Length의 값이 일치하지 않거나 헤더에 포함되어 있지 않는다면, 수신자는 서버로 다시 정확한 길이를 물어봐야 한다.

3. 커넥션 끊기의 허용, 재시도, 멱등성

  • 커넥션은 언제든 끊을 수 있기 떄문에, 예상치 못하게 커넥션이 끊어졌을 때에 대한 대응이 준비되어야 한다.
  • 클라이언트는 언제든 재요청을 할 수 있어야 한다. 문제는 파이프라인이다.
    • 클라이언트의 여러 요청이 큐에 쌓이기 때문에 커넥션이 끊어지는 경우 실제로 어디까지 전송되었는지 확인할 방법이 없다.
    • 만일 GET 요청처럼 페이지를 띄우는 요청이었다면 다시 보내면 되겠지만, POST처럼 멱등하지 않은 요청을 반복하는 경우, 예를 들어 주문 요청의 중복 구매를 요청하게 될 수도 있다.
  • 파이프라인은 원칙적으로 안전한 메서드에 한하여 사용해야 한다.
    • 멱등성을 보장하는 메서드 : GET, HEAD, PUT, DELETE, TRACE
  • 비멱등인 요청을 다시 보내야 한다면, 이전 요청에 대한 응답이 돌아올 때까지 일단 기다려야 한다.

4. 우아한 커넥션 끊기

  • 전체 끊기와 절반 끊기

    • 애플리케이션은 입력 채널과 출력 채널 중 한 개만 끊거나 둘 다 끊을 수 있다.
      • close(): 입력 채널과 출력 채널의 커넥션을 모두 끊는다. -> 전체 끊기
      • shutdown(): 입력 채널과 출력 채널 중 하나를 개별적으로 끊는다. -> 절반 끊기
  • TCP 끊기와 리셋 에러

    • HTTP 애플리케이션들 중 일부는 예상치 못한 쓰기 에러를 방지하기 위해 절반 끊기를 사용한다. 보통 커넥션의 출력 채널을 끊는 것이 안전하다.
      • 커넥션 반대 쪽의 기기는 버퍼를 모두 읽고 난 다음에 커넥션이 끊겼음을 인지한다.
    • 클라이언트에서 더 데이터를 보내지 않을 거라고 확신할 수 없는 이상, 커넥션의 입력 채널을 끊는 것은 위험하다.
      • 입력이 끊긴 채널에 데이터를 보내면 OS는 TCP connection reset by peer 메시지를 보낸다.
    • 이 경우에 OS는 심각한 에러로 취급하여 아직 읽히지 않은 데이터를 모두 삭제한다.
  • 우아하게 커넥션 끊기

    • 명세 상으로는 우아하게 끊으라고 되어있지만 우아한 방법은 명확히 명세되어 있지 않다.
    • 일반적으로 우아한 방법은 자신의 출력 채널을 먼저 끊고, 상대방의 출력 채널이 끊기기를 기다린다.
      • 그렇게 되면 메시지의 손실이 일어날 일은 없기 때문
      • 그리고 자동적으로 양 종단의 데이터 전송이 중단되었음을 알 수 있다.
@ruthetum
Copy link
Member Author

TCP/IP overview reference

https://youtu.be/k1gyh9BlOT8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant