TCP
커넥션전 세계 HTTP
는 패킷 교환 네트워크 프로토콜들의 계층화 집단인 TCP/IP
통신을 통해 이뤄진다.
TCP 커넥션이 어떻게 일어나는지 확인해보자
http://www.naver.com/index.html
에 접속한다http
통신으로 www.naver.com
에 대한 호스트명을 찾고 IP
주소와 포트 번호를 얻는다.TCP/IP 통신은 세그먼트 단위로 데이터를 전송한다.
이해하기 쉽게 예시를 들면 TCP는 애플리케이션 레이어에서 받은 메시지 단위를 세그먼트 단위로 데이터 스트림을 잘라 패킷 형태로 만든 후 IP 패킷이라 불리는 봉투에 담아 인터넷에 데이터를 전달한다.
IP 패킷의 헤더에는 발신지와 목적지의 IP 주소 , 포트 넘버와 무결성을 검사하기 위해 사용하는 숫자값 등등의 정보가 담겨 있다.
TCP/IP 통신은 발신지 IP , 발신지 포트 , 수신지 IP , 수신지 포트 번호를 이용하여 point to point
연결을 유지한다.
서버와 클라이언트 측의 TCP 통신이 연결되기 위해서는 두 소켓이 연결 되어야 한다.
소켓이란 서버와 클라이언트의 통신을 하기 위한
Interpace
이다.
연결되기 위해 서버는 소켓을 생성하고, 포트 번호에 바인딩하고, 커넥션을 기다린다.
클라이언트는 목적지의 IP , 포트 번호를 얻은 후 소켓을 생성하여 서버 소켓과 연결한다.
응답을 주고 받는 것을 트랜잭션이락고 한다.
TCP 의 연결이 지연되는 다양한 이유가 있는데
등이 지연의 이유가 될 수 있다.
TCP connection 이 일어나기 위해서는 클라이언트와 서버 측의 연결을 시작하기 위핸 3 way handshaking 이 일어나야 한다.
3 way handshaking
1. 클라이언트가 서버측에SYN : 1 , 보내고자 하는 seq #
를 보낸다.
2. 서버는 클라이언트가 보낸SYN : 1 , 보내고자 하는 seq#
에 응답하기 위해ACK SYN : 1 , ACK seq#
를 보낸다.
3. 클라이언트는 서버측이 응답을 잘 받았음을 확인하고 본인의 패킷을 보낸다.
이처럼 TCP 통신이 시작 되는데 있어서 필연적으로 지연이 존재한다.
TCP 는 신뢰성 있는 데이터 통신을 보장하기 위해 sender 와 recevier 간의 feedback 을 이용한다.
데이터가 잘 오면 잘 왔다고 ACK 를 보내고, ACK 가 오지 않거나 엉뚱한 ACK 가 오면 재전송 한다.
그런 feedback 을 할 때 recevier 는 sender 가 요청한 데이터에 응답을 편승 시켜 같이 보낸다.
이 때 요청한 데이터를 찾기 전까지 버퍼
에 저장해뒀다가 요청한 데이터를 찾아 보낵게 되면 해당 데이터와 함께 보내게 된다.
일정 시간동안 데이터를 찾지 못한 경우엔 , 별도 패킷을 만들어 응답을 전송한다.
이처럼 데이터를 찾을 때 까지 확인 응답이 보내지지 않는 것을 확인 응답 지연이라고 한다.
TCP 는 congestion controrl
기능이 있기 때문에 패킷을 보낼 때 한 번에 많이 보내는 것이 아니라
매우 작은 숫자부터 기하 급수적으로 늘려가며 보낸다.
이처럼 처음 패킷을 보낼 때는 이미 패킷을 많은 수를 보내기 시작한 것보다 느리다.
이를 TCP 느린 시작에 의한 지연이라고 한다.
네트워크의 효율을 위해 패킷을 전송하기 전에 많은 양의 TCP 데이터를 한 덩어리로 합친다.
짜잘짜잘하게 여러번 보내는 것은 혼잡을 야기 할 수 있으니까 뭉쳐서 한 덩어리로 보낸다.
세그먼트가 최대 크기가 되지 않으면 전송을 하지 않고, 만약 다른 패킷들이 모두 확인 응답을 받을 경우 기준에 도달하지 않더라도 섹그먼트를 보낸다.
이처럼 세그먼트를 기준점까지 기다리는 동안의 지연이 존재한다.
TCP 커넥션이 중단되면 연결했던 커넥션의 정보를 메모리의 작은 제어영역에 기록해둔다.
같은 주소와 포트 번호를 사용하는 TCP 커넥션이 일정 시간 동안에는 생성되지 않게 하기 위한 것으로 세그먼트의 최대 생명 주기의 두 배정도의 시간 동안만 유지 된다.
커넥션 관리가 이뤄지지 않으면 TCP 성능이 저하된다.
트랜잭션 별로 커넥션을 필요로 한다면 커넥션을 맺기 위한 지연과 함께 느린 시작ㄱ 지연이 동시에 발생하기 때문에 성능이 매우 저하된다.
이를 해결하기 위한 방법이 4가지 존재한다.
parallel
) 커넥션여러개의 TCP 커넥션을 통한 동시 HTTP 요청이다.
만약 서버에서 불러와야 할 이미지가 4개 존재한다면 한 TCP 커넥션에서 4번의 커넥션과 4번의 트랜잭션이 일어나는게 아니라
1개의 서버에서 4개의 이미지를 동시에 4개의 커넥션과 한 번의 트랜잭션으로 병렬적으로 동시에 가져온다.
커넥션과 트랜잭션에서 오는 지연 시간을 겹치게 하여 하나의 지연 시간으로 변경한 것이다.
다만 이는 4개의 서버에서 총 4개의 커넥션이 일어나는 거기 때문에 메모리를 많이 소모하며, 객체가 많이 있을 경우 부담이 매우 된다.
예를 들어 백명의 사용자가 100개의 커넥션을 필요로 한다면 서버는 총 10,000 개의 커넥션을 떠안게 된다.
다만 병렬 커넥션은 더 빠르게 느껴질 수 있다.
화면에 한 번에 객체들이 동시에 내려받는 것 처럼 보일 수 있기 때문이다.
저해상도에서 시작하여 점차 해상도를 높여가는 형태의 이미지를 사용하여 효과를 극대화 시킬 수 있다.
웹 클라이언트는 보통 같은 사이트에 여러 개의 병렬 커넥션을 한다. (보통 6~8개)
이전에는 트랜 잭션 별로 TCP 커넥션을 다시 했다면
지속 커넥션은 처리가 트랜잭션이 완료 된 후에도 TCP 통신을 유지하고 있는 방법이다.
이미 맺어져있는 지속 커넥션을 이용함으로서 커넥션을 맺기 위한 준비 작업에 따르는 시간을 절약 할 수 있다.
지속 커넥션은 커넥션을 매지 위한 사전 작업과 지연을 줄여주고 튜닝된 커넥션을 유지하기 때문에 슬로우 스타트를 할 필요가 없다.
HTTP/1.0 브라우저에서 자주 사용되었던 Keep-Alive 커넥션은 트랜잭션에 대한 응답을 보낼 때 트랜잭션 별로 TCP 연결을 중단하지 말고 연결해두자는 connection : Keep-Alive
헤더를 담아 보냈다.
서버는 받은 헤더를 통해 TCP 연결을 중단하지 않았다.
이 때 파라미터로 얼만큼 유지할지에 대한 시간이나, 몇 개의 트랜잭션 이후일지 등을 추가로 적어주기도 했다.
다만 Keep-Alive
커넥션은 클라이언트와 서버 사이에 존재하는 수많은 프록시들이 모두 keep-alive
커넥션을 이해 할 수 있어야 가능했다.
멍청한 프록시는 패킷을 전달만 할 뿐 헤더에 담긴 내용을 수행하거나 그러지 않는다.
그렇기 때문에 keep-alive
헤더가 날라와도 헤당 헤더를 전송만 할뿐 연결은 중단한다.
그로 인해 서버측와 클라이언트측은 통신이 유지되어 있을 거라 예상하고 패킷을 보내도
프록시 측에서는 연결이 끊겨있기 때문에 패킷이 유실되는 문제가 발생했다.
해결하기 위한 두 가지 방법이 있다.
하나는 keep alive 헤더를 이해 할 수 있는 영리한 프록시를 사용하거나
훕 헤더가 아닌 확장 헤더를 써서 keep-alive
라는 헤더가 서버 측에 전달 되어도 서버는 통신을 유지하지 않는 방법 말이다.
결국 멍청한 프록시를 사용하면 keep-alive 는 사용 할 수 없다. 오류만 피할 수 있다.
멍청한 프록시 문제로 인해 HTTP/1.1
에서는 Persistant connection
을 이용한다.
이는 헤더와 상관없이 TCP 커넥션이 일정 기간 유지되어 있는 것이다.
두 서버와 클라이언트 측의 응답이 종료되었다는 통신을 주고 받기 전까진 유지하고 있는다.
커넥션을 언제 끊는 것인지는 항상 조심해야 할 문제이다.
만약 클라이언트가 서버측으로부터 패킷을 받았더라도
애플리케이션 측에 전송하기 전에 버퍼에 저장하고 있는다.
만약 버퍼에서 애플리케이션 측으로 패킷이 전송되기 전 커넥션이 종료된다면
데이터의 무결성과 일관성에 대해서 문제가 생길 수 있다.
해당 버퍼가 애플리케이션 측으로 넘어가지 않고 제거되어 무결성과 일관성을 해칠 수 있기 때문이다.
이를 해결 하기 위해 나왔단 방법들은 다음과 같다.
이것은 만약 GET
요청이 일어났을 때 서버측은 클라이언트 측에게 보낼 패킷이 더이상 없기 때문에
서버 -> 클라이언트 로 가는 통신만 끊는 것이다.
서버와 클라이언트 측의 데이터 통신이 종료되면 서로의 통신이 완료 되었다는 FIN
패킷을 보낸다.
서로 FIN
을 보내고 FIN 에 대한 ACK
를 양측 모두 주고 받으면
일정 기간동안 기다렸다가 연결을 종료한다.
일정 기간 동안 기다리는 이유는 버퍼에 존재하는 패킷들을 애플리케이션에 적절히 보낼 수 있도록 위한 대기 시간을 종료한다.
이후 통신이 종료된 해당 포트 번호와 IP 주소는 다른 연결에서 사용 할 수 있게 된다.