커넥션 관리

반응형
반응형

HTTP 명세는 HTTP 메시지에 대해서 자세히 설명하고 있지만,
HTTP 커넥션과 HTTP 메시지 흐름에 대해서는 충분히 설명하고 있다고 하지 않는다.
이번 기회에 제대로 공부해보자.

- HTTP는 어떻게 TCP 커넥션을 사용하는가
- TCP 커넥션의 지연, 병목, 막힘
- 병렬 커넥션, keep-alive 커넥션, 커넥션 파이프라인을 활용한 HTTP의 최적화
- 커넥션 관리를 위해 따라야할 규칙

TCP 커넥션

- 세계 어디에 존재하든 클라이언트 애플리케이션은 서버 애플리케이션과 TCP/IP 커넥션을 맺을 수 있다.
- 이렇게 전달 받은 메시지들은 손실 또는 손상되거나 순서가 바뀌지 않고 안전하게 전달된다.
 1. 사용자는 url을 입력한다.
 2. 브라우저는 위 url에서 호스트명을 추출한다..
 3. 브라우저는 포트번호를 얻는다.
 4. 브라우저는 url을 ip의 포트번호로 TCP 커넥션을 생성한다.
 5. 브라우저가 서버로 HTTP메소드 요청 메시지를 보낸다.
 6. 브라우저가 서버에서온 HTTP메서드 응답 메시지를 읽는다.
 7. 브라우저는 이 커넥션을 끊는다.

위 내용을 간추려보면
url에서 호스트명을 추출하고 그것을 ip로 변환을 시키고 상황에 맞는 메소드를 전달하고
그것을 서버와 조율하고 그 조율이 종료되는 즉시 커넥션이 끊는것이 TCP커넥션이라 생각이 든다.

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

그러면 HTTP는 왜 TCP를 사용하는 것일까?
속도가 빠른 UDP를 사용하면 더 효율적이지 않을까?
실제로 HTTP3.0부터 더 이상TCP를 사용하지 않고 UDP를 사용한다고 한다.
하지만 어찌되었든 지금 대세는 HTTP1.1이다. 물론, HTTP2.0 ,HTTPS도 존재하지만,
지금 대중적으로 사용되는 HTTP 버전은 HTTP/1.1이다.
그래서 여기서 말하는 HTTP는 HTTP/1.1이라고 할 수 있다.

아무튼, 서론이 길었는데 HTTP가 TCP를 사용하는 이유는 안정성 때문이다.
TCP의 특징중 하나가 다시한번더 확인하는 절차를 갖는것인데...
이것 때문에 보다 정확하게 데이터를 전달 할 수 있다.
정확하다는 장점이 있는 반면, 속도가 느리다는 단점을 가지고 있다.

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

- HTTP는 프로토콜 스택에서 최상위 계층(애플리케이션 계층)이다.
- HTTP에는 보안 기능을 더한 HTTPS는 TLS 혹은 SSL이라 불리기도 하며 HTTP와 TCP 사이에 있는 암호화 계층이다.
HTTP가 메시지를 전송하고자 할때, TCP커넥터를 통해 전송이 되어진다.

여기부터는 개소리일지도 모릅니다.;;

클라이언트(브라우저)가 서버에게 데이터를전송해지는데
데이터는 Network Interfaces에서 시작이 된다.
그리고 IP, TCP를 거치면서 데이터는 점점 커질 것이다.

이렇게 되면 클라이언트가 서버쪽으로만 데이터가 전송되겠죠?
마침내 HTTP 프로토콜에 도착했습니다.
그래도 그 반대로도 가능해야 겠죠?
HTTP에 한때 뭉쳐진 데이터는 TCP가 세그먼트단위로 잘게 쪼갭니다.
참고로 UDP는 데이터그램단위라고 합니다.

이렇게 쪼갠 데이터는 네트워크 계층의 IP를 만나게 됩니다.
하지만 IP는 다른 데이터 단위를 사용합니다. 이렇게 데이터 단위를 나누는 이유는 
어느곳에서 데이터를 사용하는지 알기 위함입니다.
즉, 사람이 알기 쉽게 작성되었다고 할 수  있습니다.
또 쪼갭니다. 
IP는 패킷라는 데이터 단위가 존재하는데 패킷 단위로 TCP에서 전달된 데이터를 쪼갭니다.
이렇게 잘게 쪼개진 데이터를 Network Interfaces로 전달되서 
우리는 브라우저에서 URI을 확인 할 수 있는 것이라고 생각합니다.

 TCP 커넥션 유지하기

TCP는 클라이언트와 서버와의 약속입니다.
하나의 클라이언트가 하나의 서버와 약속을 하지 않을 수 도 있다는 뜻입니다.
TCP 커넥션은 네가지 값으로 식별한다고 합니다.
<발신지 IP 주소, 발신지 포트, 수신지 IP 주소, 수신지 포트>

대충 그림을 그려보면 다음과 같습니다.

어떻게 보면 HTTP는 TCP 커넥션의 집합체라고 생각이 듭니다.
왜냐하면 통신을 하기 위해서는 하나의 통신으로는 부족하다고 생각이 들기 때문입니다.

 TCP 소캣 프로그래밍

소캣API는 HTTP 프로그래머에게 TCP와 IP의 세부사항을 숨긴다.
소캣API는 유닉스 운영체제용으로 먼저 개발되었지만, 지금은 소켓 API의 다양한 구현체들 덕분에 대부분의 운영체제와 프로그램 언어에서
사용할 수 있게 되었다.

어떻게 보면 소캣API는 유닉스의 유산이라 생각이 든다.
간단하게 소캣 API가 어떤것이 있는지 살펴보자.
s = soket(<parameters>) 연결이 되지 않은 익명의 새로운 소켓 생성
bind(s, <local 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 커넥션의 입출력만 닫음
getsockopt(s, ...) 내부 소켓 설정 옵셥값을 읽음
setsockopt(s, ...) 내부 소켓 설정 옵션값을 변경

위 내용은 슈도코드으로 사용방법을 알려주고 있다.
- 소켓 API를 사용하면, TCP 종단 데이터 구조를 생성하고, 원격 서버의 TCP 종단에 그 종단 데이터 구조를 연결하여
데이터 스트림을 읽고 쓸 수 있다.
- 커넥션 생성은 서버와 거리, 서버의 부하, 인터넷 혼잡도에 따라서 시간이 걸린다.

위 내용을 바탕으로 자바로 구현해보자.

간단하게 client와 server로 구현했다.
통신은 단 한번만 이뤄진다.

public static void main(String[] args) throws IOException {
    System.out.println("서버 실행중...");
    ServerSocket server = new ServerSocket();
    server.bind(new InetSocketAddress("localhost", 7777));
    Socket socket = server.accept();
    InputStream in = socket.getInputStream();
    OutputStream out = socket.getOutputStream();
    int b = in.read();
    System.out.printf("%s\n", b);

    out.write("확인".getBytes());

    in.close();
    out.close();
    socket.close();
    server.close();
  }

서버를 만들고 서버를 실행시켜준다.

서버 준비


그래야 통신을 할 수 있기 때문이다.

public static void main(String[] args) throws IOException {
    Socket client = new Socket("localhost", 7777);
    InputStream in = client.getInputStream();
    OutputStream out = client.getOutputStream();
    out.write("전달".getBytes());
    int b = in.read();
    System.out.printf("%s\n", b);

    in.close();
    out.close();
    client.close();
  }

클라이언트도 준비가 되었다.
서버가 켜지면 클라이언트에서 데이터를 보낸다.
그러면 서버는 클라이언트에서 데이터를 받는다.

클라이언트 -> 서버


곧바로 서버는 클라이언트에게 데이터를 보낸다.
클라이언트는 그 데이터를 받고 기분이 좋아진다.

서버 -> 클라이언트

단, 주의점은 위 코드는 한글이지만 byte형식으로 변환을 시켜야 되기 때문에
예상과 다른 결과가 출력될지도 모른다.즉, 숫자값으로 나온다는 이야기다.

자바로 코딩하면서 느낀점은 생각보다 소캣 API에서 없는것이 많았다.
예를들면, listen이라던지, connect라던지... 내가 못찾는거 일수도 있겠지만...
아무튼 지금은 자바 공부가 아니기 때문에 이쯤에서 멈춰야지

이번 파트 생각보다 쉽지 않는것 같다.

TCP의 성능에 대한 고려

HTTP는 TCP 바로 위에 있는 계층이기 때문에 HTTP 트랜잭션의 성능은 그 아래의 계층인 TCP 성능에 
영향을 받는다고한다.
또, TCP 프로토콜의 내부을 자세히 알 필요가 있다고 한다.
그래야 HTTP의 성능이 어떤 문제가 있으며, 어떤것이 좋은지 알 수 있다.

 HTTP 트랜잭션 지연

HTTP 트랜잭션이 처리되는 과정

클라이언트나 서버가 너무 많은 데이터를 내려받거나 복잡하고 동적인 자원들을 실행하지 않는 한, 대부분의 HTTP 지연은 TCP 네트워크 지연 때문에 발생한다.

아래선은 트랜잭션이 각 단계마다 처리되는 시간을 알 수  있다.
놀라운 사실은 처리되는 시간은 다른 시간에 비해 짧다는 것을 알 수 있다.
그 이유는 클라이언트나 서버가 너무 많은 데이터를 내려받거나 복잡하고 동적인 자원들을 실행하지 않는 한, 대부분의 HTTP 지연은 TCP네트워크 지연 때문에 발생 한다고 한다.
그 이유에 대해 알아보자.
1. 클라이언트는 URI에서 웹 서버의 IP 주소와 포트 번호를 알아내야 한다. 그래야 접속할 수  있기 때문이다.
근데 사용자는 IP주소로 입력하지 않는다 그래서 DNS같은 서비스를 이용해서 IP주소로 변환을 시켜줘야하는데
그 작업이 너무 힘들다. 그래서 오래 걸린다.
근데 이건 과거일이고 지금은 꽤 빨라졌다고 한다.
2. 클라이언트는 TCP 커넥션 요청을 서버에게 보내고 서버가 커넥션 허가 응답을 회신하기를 기다린다.

TCP & UDP 짤

왜냐하면 그게 TCP니까.

3. 커넥션이 맺어지면 클라이언트는 HTTP 요청을 새로 생성된 TCP파이프를 통해 전송한다.

약속은 한번만 하는게 아니죠 

4. 웹 서버가 HTTP 응답을 보내는 것 역시 시간이 소요된다.
이게 TCP라니까...

결국 TCP는 데이터를 전송, 대기, 받는것이 연속이기 때문에 지연이 생길 수 밖에 없다.

성능 관련 중요 요소

자 지금 부터 TCP 성능의 중요 요소들을 살펴봅시다.


 TCP 커넥션 핸드세이크 지연

어떤 데이터를 전송하든 새로운 TCP 커넥션을 열 때면, TCP 소프트웨어는 커넥션을 맺기 위한 조건을 맞추기 위해
연속으로 IP 패킷을 교환한다.
다음은 TCP 커넥션이 핸드 쉐이크를 하는 순서다.
1. 클라이언트는 새로운 TCP커넥션을 생성하기 위해 작은 TCP패킷을 서버에게 보낸다.
(보통은 40~60바이트)
2. 서버가 그 커넥션을 받으면 몇 가지 커넥션 매개 변수를 산출하고, 커넥션 요청이 받아들여졌다면, TCP패킷을 클라이언트에게 보낸다.
3. 클라이언트는 커넥션이 잘 맺어졌음을 알리기 위해 서버에게 다시 확인 응답 신호를 보낸다.

어떻게 보면 get메소드를 사용하기 위해 TCP를 거쳐야 한다는 사실을 알게 되었다.

확인응답 지연

인터넷 자체가 패킷 전송을 완벽히 보장하지는 않기 때문에 TCP는 성공적인 데이터 전송을 보장하기 위해서 자체적인 확인 체계를 가진다.
만약에, 송신자가 특정시간내에 패킷을 받지못한다면, 패킷이 파괴되었거나 오류가 있는것으로 판단하여 재 전송한다.

확인 응답은 어차피 크기가 작기 때문에 데이터 패킷과 함께 전송하기도 한다.
이것을 데이터 패킷에 확인응답을 편승한다라고 말한다.
많은 TCP 스택은 '확인 응답 지연' 알고리즘을 구현한다.
간단히 설명하면 송출할 확인 응답을 특정 시간동안(보통 0.1 ~0.2초) 버퍼에 저장하구
편승할 데이터 패킷을 찾는다고 한다.
근데 생각해보면 특정 시간 동안 저장해둔다는데 그렇다는건 패킷에 편승시킬 기회를 감소시키는게 아닐까 생각이 든다.
왜냐하면, 버퍼에 저장하는 시간이 늘어남에 따라 자연스럽게 편승할 기회는 줄어들기 떄문이다.
지연의 원인이 되는 확인 응답 지연 관련 기능을 수정하거나 비활성화 할 수 있다고 한다.
TCP설정을 수정하려면, TCP의 내부 알고리즘이 피하려는 문제를 애플리케이션이 발생시키지 않을 것이라는 확신을 할 수  있어야 한다.

 TCP 느린 시작

데이터 전송 속도는 TCP 커넥션이 만들어진 지 얼마나 지났는지에 따라 달라질 수 있다고 한다.
이 말이 무슨말이냐면, 거리는 시간에 따라 변화한다는 사실이다.
TCP커넥션은 성공함에 따라 속도가 점점빨라진다.
그 이유는 처음에는 최대 속도를 제한하구
성공 여부에 따라 속도 제한을 높이기 때문이다.
그래서 웹을 접속할때 처음에는 속도가 느린데 한 번 접속하면 빨라지는것도 이 때문인가?

설명은 않했지만, 4개의 패킷을 보내게 되는 것을 '혼잡 윈도를 연다'라고 한다고 한다.

 네이글 알고리즘과 TCP_NODELAY

애플리케이션이 어떤 크기의 데이터든지 TCP 스택으로 전송 할 수 있도록, TCP는 데이터 스트림 인터페이스를 제공한다.
하지만 각 TCP 세그먼트는 40바이트 상당의 플래그와 헤더를 포함하여 전송하기 때문에, TCP가 작은 크기의 데이터를 포함
한 많은 수의 패킷을 전송한다면 네트워크의 성능은 크게 떨어진다.

네이글 알고리즘은 네트워크 효율을 위해서, 패킷을 전송하기 하기 전에 많은 양의 TCP데이터를 한 개의 덩어리로 합친다.

네트워크 효율 업

- 세그먼트가 최대 크기가 안되면 전송을 하지 않는다.
- 다만 모든 패킷이 확인 응답을 받았을 경우에는 최대 크기보다 작은 패킷의 전송을 허락한다.
- 다른 패킷들이 아직 전송중이면 데이터는 버퍼에 저장한다.
- 전송되고 나서 확인 응답을 기다리던 패킷이 확인응답을 받았거나 전송하기 충분한 만큼의 패킷이 쌓였을 때 버퍼에 저장되어 있던 데이터가 전송한다.
뭔가 체계적인 것같으면서도... 뭔가 문제가 있는것 같다...
다 좋은데 최대 크기가 안되면 전송하지 않는것이 HTTP 성능에 관해 문제가 있는 것 같다.
왜냐하면,  TCP패킷이 전송되지 않음은 TCP가 정상적으로 끝나지 않음을 의미한다.
그렇다는건 그것들이 끝나길 기다리는 HTTP는 영영원히 기달리지도 모른다.

- HTTP성능 향상을 위해 HTTP 스택에 TCP_NODELAY 파라미터 값을 설정하여 네이글 알고리즘을 비활성화하기도 한다.
- 이 설정을 했다면, 작은 크기의 패킷이 너무 많이 생기지 않도록 큰 크기의 데이터 덩어리를 만들어야 한다.

TCP 성능 향상에 도움은 되었지만 TCP를 사용하는 HTTP의 성능은 저하시킨다는게 아이러니하지만 이런것도 있군요.
가벼운 마음으로 공부하고 있습니다.

TIME_WAIT의 누적과 포트 고갈

- 성능 측정 ㄴㄴ 심각한 성능 저하 발생됨
- 일반적인 상황일 때는 ㄱ ㅊ

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

특정 커넥션이 생성이 되면, 그와 같은 IP주소와 포트 번호가 생성되는 것을 2분동안 방지한다고 한다.

중복 제거!

2분이라는 시간은 네트워크 환경에서는 굉장히 긴 시간이다.
이 시간 동안 수십개의 포트와 IP주소가 발생할지도 모른다.
그렇다는 이야기는 2분동안 아무것도 하지 말라는 이야기인데, 너무 가혹한것 같다고 생각이 든다.
그래서 포트가 부족한걸까?
하지만 다행스럽게도 현대에는 더 빠른 라우터로 인해 패킷이 중복으로 발생하는 경우는 줄었다고 한다.

포트 고갈 문제를 격지 않더라도, 커넥션을 너무 많이 맺거나 대기 상태로 있는 제어 블록이 너무 많아지는 상황은 주의해야 한다.
커넥션이나 제어 블록이 너무 많이 생기면 극심하게 느려지는 운영체제도 있다.

 HTTP 커넥션 관리

흔히 잘못 이해하는 Connection 헤더

HTTP는 서버와 클라이언트 사이에 중개 서버가 오는 것을 허락한다.
중개 서버하나하나 거치면서 전달이 된다.
어떤 경우에는, 현재 맺고 있는 커넥션에 적용될 옵션을 지정해야 할 때가 있다.
HTTP Connection 헤더 필드는 커넥션 토큰을 쉼표로 구분하여 가지고 있다.
다른 커넥션에는 전달하지 않는다.

토큰에는 3가지 종류가 있다.

 - HTTP 헤더 필드 명은, 이 커넥션에만 해당되는 헤더들을 나열한다.
 - 임시적인 토큰 값은, 커넥션에 대한 비표준 옵션을 의미한다.
 - close값은, 커넥션이 작업이 완료되면 종료되어야 함을 의미한다.

커넥션 토큰은 현재 커넥션에만 적용되기 때문에 다른 커넥션에는 전달한면 안 된다.
Connection 헤더는 홉별 헤더 명을 기술하는데, 이는 '헤더 보호하기'라 한다.

HTTP 애플리케이션이 Connection 헤더와 함께 메시지를 전달 받으면, 수신자는 송신자에게서
온 요청에 기술되어 있는 모든 옵션을 적용한다.
홉에 메시지를 전달하기 전에 Connection 헤더와 Connection 헤더에 기술되어 있던 모든 헤더를 삭제한다.

* 홉 : 각 서버를 의미
* 홉별 : 특정 두 서버간의 영향을 미치고 다른 서버에 간에는 영향을 미치지 않음을 의미

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

커넥션 관리가 재대로 이뤄지지 않으면 당연히 성능이 안좋아진다.

이렇게 순차적으로 트랜잭션이 발생했다고 해보자.
그러면 3번째 트랙잰션이 발생하려면 첫번째와 두 번째 트랜잭션이 종료되야 한다는 것인데..
상당한 소실이 예상된다.

또, 다른 단점은 객체의 크기를 알아야 되는 경우도 있는데, 모든 객체를 내려받기 전까지 텅 빈 화면을 보여줄지도 모른다.
순차적으로 보여주는게 편할지 몰라도 시간적으로 보면 상당히 손해가 생긴다.

나는 개인적으로 이게 동기 방식인것 같다.
왜냐하면 동기 방식도 순차적으로 발생되기 때문이다.

다른 커넥션 방식에 대해 짧게 알아보자.

병렬 커넥션 여러개의 TCP 커넥션을 통한 동시 HTTP 요청
지속 커넥션 커넥션을 맺고 끊는 데서 발생하는 지연을 제거 하기 위한 TCP 커넥션의 재활용
파이프라인 커넥션 공유  TCP 커넥션을 통한 병렬 HTTP 요청
다중 커넥션 요청과 응답들에 대한 중재(실험적인 기술)

병렬 커넥션

위에서 말한 순차 커넥션이 직렬이라면
이건 병렬?

그리기 귀찮아서 인터넷에서 퍼옴

지금 그림 I2를 기준에 까만 선으로 3개의 길이 보이는 것을 알 수 있다.
한명이 길을 하나를 선택할 수 있고,
여러명이 존재한다면...
1번길과 2번길, 3번길에는 n명이 존재한다.
그 수는 서로 다르고 딱히 정해져있지 않지만...
1번길과 2번길을 동시에 사용이 되어질 수 도 있다.
한명이라면 불가능하겠지만, 여러명이기 때문에 가능하다.
이런것이 병렬이다. 

이제 본격적으로 병렬 커넥션에 대해 학습해보자.

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

위에서 언급했듯이 순차 커넥션은 직렬이다.
직렬의 가장 큰 특징은 모든 길을 통과해야하것인데
병렬 같은 경우는 모든 길을 통과 할 필요는 없다.
위의 그림을 예시로 보면 3개의 길중 하나의 길만 통과하면 된다.

하나의 길을 통과하는게 빠를까?
세개의 길을 통과하는게 빠를까?

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

물론, 일반적인 상황에서는 병렬이 더 빠르다.
하지만 항상 병렬 커넥션이 빠른것은 아니라고 한다.
이유가 무엇일까?

만약에, 길 하나가 통과할 수 있는 범위가 정해져있는 경우라면 어떻게 될까?
예를들어 길 하나당 10명이 통과 할 수 있다고 해보자.
또, 여러 길을 하나로 합치면 그 만큼 사람이 통과할 수 있다고 해보자.
그러면 길이 3개라면 30명이 통과 할 수 있다.

하지만 애초에 위 그림의 병렬 같은 경우는 하나의 길만 통과하면 된다.
결국 제한은 30명이 아니라 10명이 되는것인데...
이거는 성능에서 굉장히 불이익을 받게 된다.

왜냐하면 10명이 다 통과할때까지 기다려야 하기 때문이다.
이쯤되면 위에서 비동기 동기라고 표현한것은 틀린 표현이라는 것을 배우게 되었다.

아무튼, 순차적인 커넥션인 경우에는 30명이 동시에  입장이 가능하지만,
병렬 커넥션은 10명만 수용이 가능하다.
이것을 대역폭이라고 부른다.
계산하는 방법은 조금더 생각해야 겠네요..
뭐가 틀렸는지 순차접근과 병렬이 값이 같게 나오네요...

아무튼 대역폭에 따라 병렬 커넥션이 순차 커넥션 보다 느릴 수 있다는 사실이다.

브라우저는 실제로 병렬 커넥션을 사용하긴 하지만 적은 수(대부분 4개)의 병렬 커넥션만 허용한다.
서버는 특정 클라이언트로부터 과도한 수의 커넥션이 맺어졌을 경우,
그것을 임의로 끊어버릴 수 있다.

병렬 커넥션은 더 빠르게 '느껴질 수' 있다

그렇다고 합니다.ㅎㅎ

지속 커넥션

 - 사이트 지역성 : 서버에 HTTP 요청을 하기 시작한 애플리케이션은 웹 페이지 내의 이미지 등을 가져오기 위해서 
그 서버에 요청하게 된다.

HTTP/1.1을 지원하는 기기는 처리가 완료된 후에도 TCP 커넥션을 유지하여 앞으로 있을 HTTP 요청에 재사용 가능.
처리가 완료된 후에도 계속 연결된 상태로 있는 TCP커넥션을 지속 커넥션이라고 부른다.

비지속 커넥션 : 각 처리가 끝나면 커넥션 종료
지속 커넥션 : 클라이언트나 서버가 커넥션을 끊기 전까지는 트랜잭션 간에도 커넥션을 유지

 지속 커넥션 vs 병렬 커넥션

병렬 커넥션의 단점 
  - 각 트랜잭션마다 새로운 커낵션을 맺고 끊기 때문에 시간과 대역폭이 소요된다.
  - 각각의 새로운 커넥션은 TCP의 느린 시작 때문에 성능이 떨어진다.
  - 실제로 연결할 수 있는 병렬 커넥션의 수에는 제한이 있다.

지속 커넥션의 장점
  - 사전작업과 지연을 줄여준다.
  - 튜닝된 커넥션을 유지한다.
  - 커넥션의 수를 줄여준다.

튜닝된 커넥션 : TCP 느린 시작에서, 패킷을 수차례 성공적으로 전송한 결과로 한 번에 다수의 패킷을 전송할 수 있는 권한의 
상태의 커넥션을 의미한다.

오늘날에는 적은 수의 병렬 커넥션을 맺고, 그것을 유지한다.
즉, 지속 커넥션을 사용한다고 할 수 있습니다.

어떻게 보면 지속 커넥션은 병렬 커넥션과 서로 상호 작용하는 관계라고 할 수 있다.
적은 수의 병렬 커넥션을 사용하면서 그것을 지속적으로 사용하기 위해 지속 커넥션을 사용한다라...
너무 효율적인데...

지속 커넥션에는 2가지 방식이 존재한다.

 HTTP/1.0+의 Keep-Alive 커넥션

- 1996년에 실험적으로 만든 HTTP/1.0의 확장 커넥션으로
설계상 문제가 많이 존재했었다.
- 이 문제는 추후에 HTTP/1.1에 수정되었다.

 Keep-Alive 동작

 - HTTP/1.1 명세에 빠지기는 했지만... 아직도 keep-alive가 널리 사용되고 있다.
 - HTTP/1.0 keep alive 커넥션을 구현한 클라이언트는 커넥션을 유지하기 위해서
요청도 Connection:Keep-Alive 헤더를 포함시킨다.
 - 만약에 응답에 Connection: Keep-Alive 헤더가 없다면, 클라리언트는 서버가 Keep-alive를 지원하지 않으며
  응답 메시지가 전송되고 나면 서버 커넥션을 끊을 것이라 추정한다.

내 생각에는 Connecton: Keep-Alive라는 헤더를 통해 지속 커넥션이 사용이 되어지는지 확인하는 것 같다고 생각이 든다.

Keep-Alive 옵션

timeout 커넥션이 얼마간 유지될것인지 유지될것을 의미( 이대로 동작한다는 보장은 없다)
max 커넥션이 몇 개의 HTTP 트랜잭션을 처리할 때까지 유지될 것을 의미한다. (이것도 보장 못함)

- Keep-Alive 헤더의 사용은 선택사항 이지만, Connection: Keep-Alive 헤더가 있을 때만 사용할 수 있다.

 Keep-Alive 커넥션 제한과 규칙

 1. 모든 메시지에 Connection: Keep-Alive헤더를 추가해야 한다.
안 그러면 커넥션이 끊겨진다. 클라이언트 서버 모두
2. 커넥션이 끊어지기 전에 엔티티 본문의 길이를 알 수 있어야 커넥션을 유지할 수 있다.
이 말은 엔티티 본문이 정확한 Contnet-Length 값과 함께 멀티파트 미디어 형식을 가지거나
청크 전송 인코딩으로 인코드 되어야 하기 때문이다.
 - keep-alive 커넥션에서 잘못된 Content-Length 값을 보내는 것은 좋지 않은데, 트랜잭션이 끝나는
시점에 기존 메시지의 끝과 새로운 메시지의 시작점을 정확히 알 수 있어야 한다.
3. 프락시와 게이트웨이는 Contnention 헤더의 규칙을 철저히 지켜야 한다.
4. 기술적으로 HTTP/1.0을 따르는 기기로부터 받는 모든 Contention 헤더는 무시해야 한다.
 - 오래된 프락시 서버로 부터 실수로 전달될 수 있기 때문이다.

정리해보면 클라이언트 와 서버모두 모든 메시지에 Connection: Keep-Alive가 존재해야
지속 커넥션을 사용할 수 있구
Content-Length의 길이가 짧다고 생각해보자.
그러면 커넥션이 끊기 겠지 왜냐하면, 엔티티가 전송이 완료되었기 때문이다.
하지만 길다면..., 전송이 완료되지 않았기 때문에
커넥션은 끊기지 않는 뭐 그런건가...
하지만 Content-Length의 길이가 쓸데없이 길다면? 
쓸모없는 소모전만 계속 되기 때문에 불필요한 Content-Length는 사용하지않는 것이 좋다고
생각합니다. 

 Keep-Alive와 멍청한 프락시

프락시

아니 그래서 프락시가 뭐냐고?
언젠가 나올것 같아서 기다리고 있는데 프락시가 뭔지 잘 모르겟네...
이미 알려줬는데 내가 모른 건가?

 

프록시 서버 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 도식화한 프록시 서버. 두 컴퓨터 사이에 끼어 있는 컴퓨터가 바로 프록시 서버이다. 프록시 서버(영어: proxy server 프록시 서버[*])는 클라이언트가 자신을 통해

ko.wikipedia.org

프락시는 중간에서 처리해주는 비서같은 존재라고 생각이 든다.
아무튼 프락시를 이용하면 서버가 일해야 되는 것을 대신 일을 해주기 때문에
서버 입장에서는 개이득이다.

아무튼 멍청한 프락시가 있다는건 현실세계에서는 멍청한 비서가 있다는 말인데...
왜지?

Connection 헤더의 무조건 전달

Connection 헤더를 무조건 전달한다고?

이 프락시는 Connection을 무조건 전달을 한다.
만약, 새로운 요청이 들어왔다면?
기존에 작업하고 있는 Connection이 끊어지기만을 기다린다.

근데 Keep-alive는 지속 커넥션이기 때문에 끊어지지 않는다.
ㅇ?
뭔가 이상하다.
얘는 Keep-alive를 이해하고 있는게 맞는지 의심스러울 정도다.
그냥 Connection이 보인다고 전달하는 느낌이다.

아무튼 이런 이상한 상황이 계속 지속이 되다면... 어떻게 될까?
브라우저는 아무 응답 없이  로드 중단이라는 표시만 나오지 않을까?

이를 해결 하기 위해서는 프락시는 Connection 헤다와 Connection헤더에 명시된 헤더들은
절대 전달하면 안 된다고 한다.

확실하지는 않지만 서버와 클라이언트는 Connection: Keep-alive 즉, 지속 커넥션을 이용한다는건 서로가 알고 있는 사실이고 이것을 프록시에 전달하지 않는다고 생각이 든다.
그래야 커넥션이 종료되면 그 즉시 종료 될 수 있기 때문이다.
아마 프록시의 역할은 서버와 클라이언트에게 지속 커넥션을 알려주는 역할이 아닌것 같다.
얘네도 스스로 알고 있어야지... 이런것 까지 프록시가 하면... 

이것을 해결하기 위해 Proxy-Connection이라는 것을 이용해서 위 문제를 해결 했다.

 HTTP/1.1의 지속 커넥션

HTTP/1.1부터는 더 이상 keep-alive를 지원하지 않는다.
대신에 이보다 상향된 지속 커넥션을 지원하는데 자세히 알아보자.

- 별도의 설정을 하지 않는 한, 모든 커넥션은 지속 커넥션으로 설정이 되어있다.
- 만약에 트랜잭션이 종료된 이후에 커넥션을 끊으려면 Connection: close 헤더를 명시한다.

파이프라인 커넥션

이런 다 날라갔다.
기왕 날라간김에 걍 설명하자면...
커넥션에 하나의 파이프라인을 설치해서 여러번 호출이 가능하게 만드는 것이다.
어떻게 보면 지속 커넥션과 비슷해보이지만,
다른점은 하나의 트랜잭션 요청이 종료가 되기전에 다른 트랜잭션을 호출이 가능하다는 점이다.
이 파이프라인 커넥션에는 주의점이 존재한다.

1.HTTP 클라이언트는 커넥션이 지속 커넥션인지 확인하기 전까지는 파이프라인을 이어서는 안된다.
2.HTTP 응답은 요청 순서와 같게 와야 한다.
3. HTTP 클라이언트는 커넥션이 언제 끊어지더라도, 완료되지 않은 요청이 파이프라인에 있으면 언제든 다시 요청을 보낼 준비가 되어 있어야 한다.
4. HTTP 클라이언트는 POST 요청같이 반복해서 보낼 경우 문제가 생기는 요청은 파이프라인을 통해 보내면 안된다.
POST같은 메서드를 비멱등 메서드라고 한다.

멱등이란?
 연산을 여러번해도 값이 달라지지 않는 성질을 말한다.

그러니까 POST는 여러번 호출하며 결과가 달라진다는 것을 알 수 있다.
POST말고 다른 비멱등 메서드는 무엇이 존재할까?

출처 : https://developer.mozilla.org/ko/docs/Glossary/Idempotent

나중에 실험해봐야 겠다. 

커넥션 끊기에 대한 미스터리

  '마음대로' 커넥션 끊기

 - 보통 커넥션은 메시지를 다 보낸 다음 끊지만, 에러가 있는 상황에는 헤더의 중앙이나 다른 엉뚱한 곳에서 끊을 수 있다.
 - 서버가 그 유휴상태에 있는 커넥션을 끊는 지점에, 서버는 클라이언트가 데이터를 전송하지 않을 것이라고 확신하지 못한다.

 Connection-Length와 Truncation

 - 각 HTTP응답은 본문의 정확한 크기 값을 가지는 Content-Length 헤더를 가지고 있어야 한다.

Content-Length는 본문의 길이에 따라 정해지기 때문에,
이것으로 메시지가 얼마나 긴지 알 수 있을 것 같다....

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

 - HTTP 애플리케이션은 예상치 못하게 커넥션이 끊어졌을 때 적절히 대응할 수 있는 준비가 되어 있어야 한다.
 - 비멱등 메서드는 함부로 사용하지 말것!

우아한 커넥션 끊기

 - TCP 커넥션은 양방향이다.
 - TCP 커넥션의 양쪽에는 데이터를 읽거나 쓰기 위한 입력 큐와 출력 큐가 있다.

전체 끊기와 절반 끊기

 - 애플리케이션은 TCP 입력 채널과 출력 채널 중 한 개만 끊거나 둘다 끊을 수 있다.
 - close()를 호출하면 TCP 커넥션의 입력 채널과 출력 채널의 커넥션을 모두 끊는다.
 - 입력 채널이나 출력 채널 중 하나를 개별적으로 끊으려면 shutdown()을 호출하면 된다.

TCP 끊기와 리셋 에러

 - 단순한 HTTP 애플리케이션은 전체 끊기만을 사용할 수 있다.
 - 애플리케이션이 각기 다른 HTTP 클라이언트, 서버, 프락시와 통신할 때, 
 - 파이프라인 지속 커넥션을 사용할 때,
 - 기기들에 예상치 못한 쓰기 에러를 발생하는 것을 예방 하려면
 - 절반 끊기를 사용해야 한다.

 - 출력을 끊는 것이 안전하다.
 - 클라이언트에서 더는 데이터를 보내지 않을 것임을 확신할 수 없는 이상, 커넥션의 입력 채널을 끊는 것은 위험하다.

 connection reset by peer : 이미 끊긴 입력 채널에 데이터를 전송 할때 발생
  - 대부분 운영체제는 이것을 심각한 에러로 취급하여 버퍼에 저장된, 아직 읽히지 않은 데이터를 모두 삭제한다.
  -> 파이프라인 커넥션에서 더 악화된다.

HTTP 완벽 가이드 1부 마무리
막판에 힘이 빠져서 대충 공부한것 같다는 느낌이 들었구
2부는 1~2주정도 다른거 공부하고 다시 공부할 예정
작성하지 못한 추가자료는 귀찮아서 추가하지 않을 거임.

 

추가 자료

https://datatracker.ietf.org/doc/html/rfc2068
https://datatracker.ietf.org/doc/html/draft-ietf-http-connection 
https://www.w3.org/Protocols/HTTP/Performance/ 
 

 

 

반응형

'WEB > http' 카테고리의 다른 글

프록시  (0) 2021.07.20
웹 서버  (0) 2021.07.05
HTTP 메시지  (0) 2021.06.09
URL과 리소스  (0) 2021.05.26
HTTP 개관  (0) 2021.05.19

댓글

Designed by JB FACTORY