컴퓨터 네트워킹 하향식 접근 - 3. 트랜스포트 계층
3.1 트랜스포트 계층 서비스 및 개요
- 트랜스포트 계층 프로토콜은 각기 다른 애플리케이션 프로세스 간의 `논리적 통신`을 제공한다. 논리적 통신이란 프로세스들이 동작하는 호스트들이 직접 연결된 것처럼 보인다는 것을 의미한다.
- 라우터가 아닌 `종단 시스템`에서 구현된다.
- 애플리케이션 프로세스로부터 수신한 메시지를 작은 조각으로 분할하고, 각각의 조각에 트랜스포트 계층 헤더를 추가한`세그먼트`로 변환하여 네트워크 계층으로 전달한다.
트랜스포트 계층 프로토콜은 각기 다른 호스트에서 동작하는 `프로세스들` 사이의 논리적 통신을 제공하지만, 네트워크 계층 프로토콜을 `호스트들` 사이의 논리적 통신을 제공한다.
인터넷 트랜스포트 계층 프로토콜로, 비신뢰적이고 비연결형인 UDP와 신뢰적이고 연결지향적인 TCP가 있다. UDP와 TCP의 가장 기본적인 기능은 종단 시스템 사이의 IP 전달 서비스를 종단 시스템에서 동작하는 두 프로세스 간의 전달 서비스로 확장하는 것이다. 호스트 대 호스트 전달을 프로세스 대 프로세스 전달로 확장하는 것을 트랜스포트 계층 다중화와 역다중화라고 부른다. 또한 UDP와 TCP는 헤더에 오류 검출 필드를 포함함으로써 무결성 검사를 제공하는데, 이 두가지가 UDP가 제공하는 서비스이다. TCP는 여기에 더해 신뢰적인 데이터 전송과 혼잡 제어를 제공한다.
3.2 다중화와 역다중화
네트워크 계층이 제공하는 호스트 대 호스트 전달 서비스에서 호스트에서 동작하는 애플리케이션에 대한 프로세스 대 프로세스 전달 서비스로 확장하는 것을 살펴보자.
컴퓨터를 이용하여 하나의 FTP 세션과 2개의 텔넷 세션을 실행하면서 웹 페이지를 다운로드하고 있다고 가정하면 우리는 총 4개의 네트워크 애플리케이션 프로세스를 갖는다.(텔넷 프로세스 2개, FTP 프로세스 1개, HTTP 프로세스 1개). 우리의 컴퓨터의 트랜스포트 계층이 하위의 네트워크 계층으로부터 데이터를 수신할 때 트랜스포트 계층은 이 4개의 프로세스 중 하나에게 수신한 데이터를 전달한다.
네트워크 애플리케이션의 한 부분으로서 프로세스가 소켓을 가지고 있고, 이를 통해 네트워크에서 프로세스로 데이터를 전달하고 또한 프로세스로부터 네트워크로 데이터를 전달한다.
네트워크 애플리케이션의 한 부분으로서 프로세스가 소켓을 가지고 있고, 이를 통해 네트워크에서 프로세스로 데이터를 전달하고 또한 프로세스로부터 네트워크로 데이터를 전달한다.
따라서 위의 그림처럼 수신 측 호스트의 트랜스포트 계층은 실제로 데이터를 직접 프로세스로 전달하지 않고 대신 중간 매개자인 소켓에게 전달한다. 어떤 주어진 시간에 수신 측 호스트에 하나 이상의 소켓이 있을 수 있으므로 각각의 소켓은 하나의 유일한 식별자를 갖는다. 이 식별자의 포맷은 소켓이 UDP 소켓인지 TCP 소켓인지에 따라 달라진다.
각각의 트랜스포트 계층 세그먼트는 세그먼트에 필드 집합을 갖고 있다. 수신 측의 트랜스포트 계층은 수신 소켓을 식별하기 위해 이 필드를 검사하고 해당 소켓으로 보낸다. 트랜스포트 계층 세그먼트의 데이터를 올바른 소켓으로 전달하는 작업을 역다중화라고 한다. 출발지 호스트에서 소켓으로부터 데이터를 모으고, 이에 대한 세그먼트를 생성하기 위해 각 데이터에 헤더 정보로 캡슐화하고, 그 세그먼트들을 네트워크 계층으로 전달하는 작업을 다중화라고 한다. 여기서 소켓은 유일한 식별자를 갖고, 각 세그먼트는 세그먼트가 전달될 적절한 소켓을 가리키는 특별한 필드를 갖는데, 이 필드들은 출발지 포트 번호 필드와 목적지 포트 번호 필드이다. 여기서 0~1023 까지의 포트 번호를 잘 알려진 포트 번호라고 하여 사용을 엄격하게 제한하고 있다. 예를 들면 HTTP는 포트 번호 80, FTP는 포트 번호 21 처럼 잘 알려진 애플리케이션 프로토콜에서 사용되도록 예약되어 있다. 세그먼트가 호스트에 도착하면, 트랜스포트 계층은 세그먼트 안의 목적지 포트 번호를 검사하고 상응하는 소켓으로 세그먼트를 보낸다. 그러면 세그먼트의 데이터는 소켓을 통해 해당되는 프로세스로 전달되고 이는 UDP의 기본 동작 방식이다. TCP의 경우에는 좀 더 많은 의미를 가진다.
UDP는 목적지 IP 주소와 목적지 포트 번호로 구성된 두 요소로 된 집합에 의해 식별된다. 즉 출발지의 IP 주소나 포트 번호가 다르더라도 목적지 IP 주소와 포트 번호가 같으면 같은 목적지 소켓을 통해 같은 프로세스로 향한다. 출발지 포트 번호는 회신 주소의 한 부분으로 사용된다.
TCP는 4개 요소의 집합(four-tuple), 즉 출발지 IP 주소, 출발지 포트 번호, 목적지 IP 주소, 목적지 포트 번호에 의해 식별된다. 네트워크로부터 호스트에 TCP 세그먼트가 도착하면, 호스트는 해당되는 소켓으로 세그먼트를 전달하기 위해 4개의 값을 모두 사용한다. 즉 다른 출발지 주소, 포트 번호를 가지고 도착하는 2개의 TCP 세그먼트는 2개의 다른 소켓으로 향하게 된다.
TCP 연결 설정
- TCP 서버 애플리케이션은 `환영(welcome) 소켓`을 가지고 있음. 이 소켓은 포트 번호 12000을 가진 TCP 클라이언트로부터 연결 설정 요청을 기다리고있음
- TCP 클라이언트는 소켓을 생성하고, 연결 설정 요청 세그먼트를 보냄
- 연결 설정 요청은 목적지 포트 번호 12000과 TCP 헤더에 설정된 특별한 연결 설정 비트를 가진 TCP 세그먼트를 통해 보내짐
- 이 세그먼트는 클라이언트가 선택한 번호인 출발지 포트 번호를 포함함
- 서버 프로세스로 동작하는 컴퓨터의 호스트 운영체제가 목적지 포트 12000을 포함하는 연결 요청 세그먼트를 수신하면, 이 세그먼트를 포트 번호 12000으로 연결 수락을 기다리는 서버 프로세스로 보냄
- 서버는 연결 요청 세그먼트의 출발지 IP주소와 포트, 목적지 IP주소와 포트의 4개 요소를 보고 전부 일치하면 그 세그먼트는 해당 소켓으로 역다중화됨
웹 서버와 TCP
서버는 각기 다른 클라이언트가 보낸 세그먼트를 출발지 IP 주소와 출발지 포트 번호로 구별한다. 같은 웹 서버 애플리케이션과 통신하기 위해 같은 목적지 포트 번호를 이용하는 두 클라이언트의 예시를 보자.
호스트 A, 호스트 C, 서버 B는 각자 유일한 IP 주소인 A, C, B를 각각 가지고 있다.
- 호스트 C는 2개의 출발지 포트 번호(26145, 7532)를 자신의 HTTP 연결에 할당함
- 호스트 A는 호스트 C와 독립적으로 출발지 포트 번호를 선택하므로, 이것 또한 HTTP 연결에 출발지 포트로 26145를 할당
- 이렇게 하더라도, 2개의 연결은 다른 출발지 IP 주소를 가지기 때문에 서버 B는 여전히 올바르게 역다중화함
웹 서버는 각각의 연결에 따라서 새로운 `프로세스`를 만든다. 이들 프로세스는 각자 `연결 소켓`을 가지며, 이 연결 소켓을 통해 HTTP 요청을 수신하고, HTTP 응답을 전송한다. 그러나 연결 소켓과 프로세스 사이에서 항상 일대일 대응이 이루어지는 것이 아니다. 오늘날의 많은 고성능 웹 서버는 하나의 프로세스만 사용하는데 각각의 새로운 클라이언트 연결을 위해 연결 소켓과 함께 새로운 `스레드(thread)`를 생성한다.
지속적인(persistent) HTTP
지속적인 연결의 종속 기간에 클라이언트와 서버는 같은 서버 소켓을 통해 HTTP 메시지를 교환함
비지속적인(non-persistent) HTTP
모든 요청/응답마다 새로운 TCP 연결이 생성되고 종료됨
3.3 비연결형 트랜스포트: UDP
즉, 트랜스포트 계층은 네트워크 계층과 해당하는 애플리케이션 레벨 프로세스 간의 데이터를 넘겨주기 위해 다중화와 역다중화 서비스를 제공해야 한다. UDP는 트랜스포트 계층 프로토콜이 할 수 있는 최소 기능으로 동작한다. 다중화/역다중화 기능과 간단한 오류 검사를 제외하면 IP에 아무것도 추가하지 않는다. 또한 핸드셰이크를 하지 않기 때문에 비연결형이라고 부른다. DNS가 전형적인 UDP를 사용하는 애플리케이션 계층 프로토콜의 예로, 만약 질의 호스트가 응답을 수신하지 못하면 질의를 다른 네임 서버로 송신하거나 애플리케이션에게 응답을 수신할 수 없음을 통보한다.
UDP가 더 적합한 이유/경우
무슨 데이터를 언제 보낼지에 대해 애플리케이션 레벨에서 더 정교한 제어
- 애플리케이션 프로세스가 데이터를 UDP에 전달하자마자 UDP는 데이터를 UDP 세그먼트로 만들고, 그 세그먼트를 즉시 네트워크 계층으로 전달한다.
- 이에 반해 TCP에서는 혼잡 제어 매커니즘을 갖고 있고 하나 이상의 링크가 과도하게 혼잡해지면 트랜스포트 계층 TCP 송신자를 제한한다. 또한 TCP는 신뢰적인 전달이 얼마나 오래 걸리는지에 관계없이 목적지가 세그먼트의 수신 여부를 확인응답할 때까지 데이터의 세그먼트 재전송을 계속한다.
- 실시간 애플리케이션은 지연을 원하지 않고, 조금의 데이터 손실은 허용할 수도 있는데 이때 UDP가 더 적합할 수 있고 UDP의 기본 세그먼트 전달 외에 필요한 어떤 추가 기능을 구현할 수 있다.
연결 설정이 없음
- 핸드셰이크를 하지 않아서 연결을 설정하기 위한 어떤 지연도 없다.
연결 상태가 없음
- TCP는 종단 시스템에서 연결 상태를 유지하고 수신 버퍼, 송신 버퍼, 혼잡 제어 파라미터, 순서 번호와 확인응답 번호 파라미터를 포함한다.
- 이에 반해 UDP는 연결 상태를 유지하지 않으며 이 파라미터 중 어떤 것도 기록하지 않는다.
- 따라서 특정 애플리케이션 전용 서버는 프로그램이 TCP보다 UDP에서 동작할 때 일반적으로 좀 더 많은 액티브 클라이언트를 수용할 수 있다.
작은 패킷 헤더 오버헤드
- TCP는 세그먼트마다 20바이트의 헤더 오버헤드를 갖지만 UDP는 단지 8바이트의 오버헤드를 갖는다.
email, 원격 터미널 접속, 웹 등은 TCP의 신뢰적인 데이터 전송 서비스가 필요한 반면 네트워크 관리에서는 네트워크가 혼잡한 상태에 있을 때 자주 동작해야 하므로 UDP가 바람직하다. 애플리케이션 자체에서 신뢰성을 제공한다면 UDP에서도 신뢰적인 데이터 전송을 할 수 있다.
UDP 세그먼트 구조
- 데이터 : UDP 데이터그램의 데이터 필드에 위치
- 포트 번호 : 정확한 프로세스에게 애플리케이션 데이터를 넘기게 해준다.
- 체크섬 : 세그먼트에 오류가 발생했는지 검사하기 위해 수신 호스트가 사용한다.
- 길이 필드 : 헤더를 포함하는 UDP 세그먼트의 길이를 바이트 단위로 나타낸다.
UDP 체크섬
세그먼트가 출발지로부터 목적지로 이동했을 때 UDP 세그먼트 안의 비트에 대한 변경사항이 있는지 검사한다. 왜 많은 링크 계층 프로토콜에서 오류 검사를 제공하는데 UDP가 체크섬을 제공할까? 출발지와 목적지 사이의 모든 링크가 오류 검사를 제공한다는 보장이 없기 때문이다. 따라서 세그먼트들이 정확하게 링크를 통해 전송되었을지라도, 세그먼트가 라우터의 메모리에 저장될 때 비트 오류가 발생할 수가 있다. UDP는 오류 검사를 제공하지만, 오류를 회복하기 위한 어떤 일도 하지 않는다. 손상된 세그먼트를 그냥 버리기도 하고, 경고와 함께 손상된 세그먼트를 애플리케이션에게 넘겨주기도 한다.(처리 방식은 구현에 따라서 다름)
주어진 링크 간의 신뢰성과 메모리의 오류 검사가 보장되지도 않고, 종단 간의 데이터 전송 서비스가 오류 검사를 제공해야 한다면
UDP는 종단 기반으로 트랜스포트 계층에서 오류 검사를 제공해야만 한다.
→ 이것은 `종단과 종단의 원칙(end-end principle)`의 한 예시이다.
3.4 신뢰적인 데이터 전송의 원리
네트워크 전체에서 가장 중요한 문제라고 할 수 있다.
신뢰적인 채널에서는 전송된 데이터가 손상되거나 손실되지 않고 모든 데이터는 전송된 순서 그대로 전달된다. 신뢰적인 데이터 전송 프로토콜을 점진적으로 개발해보자. 여기서는 보내진 패킷이 일부 손실될 수도 있지만 보내진 순서대로 전달되리라는 것을 가정한다.
신뢰적인 데이터 전송 프로토콜의 구축
완벽하게 신뢰적인 채널상에서의 신뢰적인 데이터 전송: rdt1.0
여기서는 데이터 단위와 패킷의 차이점이 없으며, 모든 패킷 흐름은 송신자로부터 수신자까지다. 완전히 신뢰적인 채널에서는 오류가 생길 수 없으므로 수신 측이 송신 측에게 어떤 피드백도 제공할 필요가 없다. 또한 수신자는 송신자가 데이터를 송신하자마자 데이터를 수신할 수 있다고 가정하여 수신자가 송신자에게 천천히 보내라고 요청할 필요가 없다.
비트 오류가 있는 채널상에서의 신뢰적 데이터 전송: rdt2.0
패킷 안의 비트들이 하위 채널에서 손상되는 모델이다. 이러한 비트 오류는 일반적으로 패킷이 전송 또는 전파되거나 버퍼링될 때 네트워크의 물리적인 구성요소에서 발생한다.
자동 재전송 요구(Automatic Repeat reQuest, ARQ) 프로토콜
- `긍정 확인응답(OK)`
- `부정 확인응답(다시 보내주세요)`
이러한 제어 메시지는 정확하게 수신되었는지 또는 잘못 수신되어 반복이 필요한지를 수신자가 송신자에게 알려줄 수 있게 한다. 비트 오류를 처리하기 위해 기본적으로 다음과 같은 세 가지 부가 프로토콜 기능이 ARQ 프로토콜에 요구된다.
- 오류 검출 : 비트 오류가 발생했을 때 수신자가 검출할 수 있는 기능이 필요하다(checksum). 수신자가 패킷 비트 오류를 검출하고 복구할 수 있게 해준다.
- 수신자 피드백 : 송신자가 수신자의 상태를 알기 위해 수신자가 송신자에게 정확하게 수신되었는지 아닌지 등의 피드백을 제공한다.
- 재전송 : 오류를 가지고 수신된 패킷은 송신자에 의해 재전송된다.
- 왼쪽 상태에서 송신 측 프로토콜은 상위 계층으로부터 데이터가 전달되기를 기다린다.
- `red_sent(data)` 이벤트가 발생하면 송신자는 패킷 체크섬과 함께 전송될 데이터를 포함하는 패킷(`sndpkt`)을 생성하고,
- 그 패킷을 `udt_send(sndpkt)` 동작을 통해 전송한다.
- 오른쪽 상태에서 송신자 프로토콜은 수신자로부터 ACK 또는 NAK 패킷을 기다린다.
- 만약 `ACK` 패킷을 수신하면 (`rdt_rcv(rcvpkt) && isACK(rcvpkt)`)
- 가장 최근에 전송된 패킷이 정확하게 수신되었음을 의미한다.
- 따라서 프로토콜은 상위 계층으로부터 데이터를 기다리는 상태로 돌아간다.
- `NAK` 패킷을 수신하면
- 프로토콜은 마지막 패킷을 재전송한다.
- 재전송된 데이터 패킷에 대한 응답으로 수신자에 의해 응답하는 ACK 또는 NAK를 기다린다.
- 만약 `ACK` 패킷을 수신하면 (`rdt_rcv(rcvpkt) && isACK(rcvpkt)`)
rdt2.0과 같은 프로토콜은 `전송 후 대기(stop-and-wait)` 프토토콜이다. 송신자가 ACK 또는 NAK를 기다리는 상태에 있을 때, 상위 계층으로부터 더 이상의 데이터를 전달받을 수 없다. 즉, `rdt_send()` 이벤트는 발생할 수 없으며, 이는 오직 송신자가 ACK를 수신하고 이 상태를 떠난 후에 발생한다. 따라서 송신자는 수신자가 현재의 패킷을 정확하게 수신했음을 확신하기 전까지 새로운 데이터를 전달하지 않는다.
패킷이 도착했을 때, 수신자는 수신된 패킷이 손상되었는지 아닌지에 따라 `ACK` 또는 `NAK`로 응답한다.
rdt2.0의 결함
여기서는 `ACK 또는 NAK 패킷이 손상될 수 있다`는 가능성을 고려하지 않았다. 응답 패킷이 손상되면 송신자는 수신자가 전송된 데이터의 마지막 부분을 올바르게 수신했는지 알 방법이 없다. 현존하는 데이터 프로토콜이 채택한 방법은 데이터 패킷에 새로운 필드를 추가하고 이 필드 안에 순서 번호(sequence number)를 삽입하는 방식으로 데이터 패킷에 번호를 붙이는 방식이다. 수신자는 수신된 패킷이 재전송되었는지 판단할 때 이 순서 번호만 확인하면 된다.
rdt2.0의 수정된 버전 rdt2.1
rdt2.1에서는 송신자가 모든 데이터 패킷에 순서 번호(0 또는 1)를 번갈아가며 붙인다. 이렇게 하면 수신자는 같은 패킷이 두 번 도착했는지 알 수 있다.
- 순서가 바뀐 패킷이 수신되면, 수신자는 이미 전에 수신한 패킷에 대한 긍정 확인응답 전송
- 손상된 패킷이 수신되면, 수신자는 부정 확인응답 전송
비트 오류를 갖는 채널을 위한 NAK없는 신뢰적인 데이터 전송 프로토콜 rdt2.2
rdt2.2는 NAK를 사용하지 않고도 오류 복구가 가능한 프로토콜이다. NAK를 송신하는 대신 가장 최근에 정확하게 수신된 패킷에 대해 ACK를 재송신함으로써 NAK를 송신한 것과 같은 효과를 얻는다. 같은 패킷에 대해 2개의 ACK를 수신한 송신자는 수신자가 2번 ACK한 패킷의 다음 패킷을 정확하게 수신하지 못했다는 것을 알게 된다.
- 수신자가 반드시 ACK 메시지에 의해 확인 응답되는 패킷의 순서 번호를 포함해야 한다. 이는 수신자 FSM의 make_pkt()에 ACK, 0 또는 ACK, 1인 인수를 넣어서 수행한다.
- 송신자는 수신된 ACK 메시지에 의해 확인응답된 패킷의 순서 번호를 반드시 검사해야만 한다. 이는송신자 FSM의 isACK()에 0 또는 1인 인수를 넣어서 수행한다.
비트 오류와 손실 있는 채널상에서의 신뢰적인 데이터 전송: rdt3.0
하위 채널이 패킷을 손실하는 경우도 생각해보자. 프로토콜은 어떻게 패킷 손실을 검출할지, 패킷 손실이 발생했을 때 어떤 행등을 해야할지를 결정해야 한다. 송신자가 데이터 패킷을 전송하고, 패킷 또는 수신자의 패킷에 대한 ACK를 손실했다고 가정하자. 이 때 송신자에게는 수신자로부터 어떠한 응답도 없다. 즉, 송신자는 데이터 패킷이 손실되었는지, ACK가 손실되었는지, 단순히 지연된건지를 알지 못한다. 이를 알기 위해 송신자가 패킷이 손실되었다고 확신할 수 있을 정도의 충분한 시간을 기다린 다음 데이터 패킷을 재전송한다.
그렇다면 얼마나 오랫동안 기다려야 할까? 송신자는 적어도 `왕복 시간 지연` + 수신 측에서 `패킷을 처리하는 데 필요한 시간`만큼을 기다려야 한다. 시간 기반의 재전송 매커니즘을 구현하기 위해서는 주어진 시간이 지난 후에 송신자를 중단할 수 있는 카운트다운 타이머가 필요하다.
파이프라이닝된 신뢰적인 데이터 전송 프로토콜
rdt3.0은 기능적으로는 정확하지만 전송 후 대기(stop-and-wait) 프로토콜이라는 점에서 문제가 생긴다. 송신자와 수신자 간의 거리가 먼 경우, 패킷이 이동하는 데 대부분의 시간이 소요되고 송신자가 실제로 송신하는 시간의 비율은 매우 낮다. 파이프라이닝은 이것의 해결책으로, 송신자가 확인응답을 기다리기 전에 송신을 전송하도록 허용한다. 파이프라이닝을 도입하기 위해,
- 순서 번호의 범위가 커져야 한다. 각각의 전송 중인 패킷은 유일한 순서 번호를 가져야한다.
- 프로토콜의 송신 측과 수신 측은 하나 이상의 패킷을 버퍼링해야 한다. 최소한 '송신자는 전송되었으나 확인응답되지 않은 패킷'을 버퍼링해야 한다.
- 필요한 순서 번호의 범위와 버퍼링 조건은 데이터 전송 프로토콜이 손실 패킷과 손상 패킷, 상당히 지연된 패킷들에 대해 응답하는 방식에 달려있다. 파이프라인 오류 회복의 두 가지 기번적인 접근 방법으로 GBN(Go-Back-N)과 SR(Selective Repeat)가 있다.
GBN
GBN에서 송신자는 한 번에 여러 패킷을 윈도우 크기만큼 보낸다. 수신자가 패킷에서 오류를 발견하면, 해당 패킷과 그 이후에 받은 모든 패킷을 버리고 송신자는 오류가 발생한 패킷부터 다시 모든 패킷을 재전송한다.
- [0, base-1] : 순서 번호는 이미 전송되고 확인응답이 된 패킷
- [base, nextseqNum-1] : 송신은 되었지만 아직 확인응답되지 않은 패킷
- [nextseqNum, base+N+1] : 상위 계층으로부터 데이터가 도착하면 바로 전송될 수 있는 패킷
- base+N 이상 : 파이프라인에서 확인응답이 안 된 패킷의 확인응답이 도착할 때까지 사용불가
여기서 N을 윈도우 크기라고 부르며, GBN 프로토콜은 슬라이딩 윈도우 프로토콜이라고 부른다.
위의 예시에서 윈도우 크기가 4로 제한되어있다. 송신자는 패킷 0부터 3까지 송신하고, 송신을 계속하기 전에 하나 이상의 패킷이 긍정 확인응답되는 것을 기다린다. 성공적인 ACK(ACK0, ACK1)이 수신되었을 때 윈도우는 앞으로 이동하고 송신자는 새로운 패킷(pkt3, pkt5)를 전송한다. 수신 측에서는 패킷 2가 손실되었으므로 패킷 3, 4, 5는 순서가 잘못된 패킷으로 발견되어 제거된다.
GBN은 다음과 같은 문제점들이 있다.
- 하나의 패킷에 오류가 발생하면 해당 패킷 이후의 모든 패킷을 재전송해야 한다.
- 수신 측에서는 순서대로만 패킷을 처리하기 때문에, 오류가 발생한 패킷 이후에 올바르게 수신된 패킷들도 폐기된다.
- 윈도우 크기가 크면 오류 발생시 더 많은 패킷을 재전송해야 한다.
SR
SR은 GBN의 비효율성을 해결하기 위해 개발되었다. SR은 수신자에서 오류가 발생한 패킷만을 선택적으로 재전송한다. 따라서 불필요한 재전송을 피하고, 올바르게 수신된 패킷마다 개별적인 확인응답을 필요로한다. 윈도우 크기 N은 아직 확인응답이 안 된 패킷 수를 제한하며 송신자와 수신자 모두 윈도우 크기 N을 가진다.
SR 수신자는 패킷의 순서와는 무관하게 손상 없이 수신된 패킷에 대한 확인응답을 한다. 각 패킷에는 고유한 순서 번호가 할당되고, 순서가 바뀐 패킷은 빠진 패킷이 수신될 때까지 버퍼에 저장하고 빠진 패킷이 수신된 시점에서 일련의 패킷을 순서대로 상위 계층에 전달한다.
처음에 pkt3, 4, 5를 버퍼에 저장하고, 마지막으로 pkt2가 수신되었을 때 pkt2와 함께 상위 계층에 전달한다.
송신자
- 상위 계층에서 데이터를 받으면 현재 윈도우 내에 있는 순서 번호를 할당한다.
- 패킷으로 만들어 전송하고 해당 패킷의 복사본을 버퍼에 저장한다.
- 각 패킷마다 개별 타이머를 시작한다.
- 윈도우가 가득 차면 상위 계층에서 더 이상 데이터를 받지 않는다.
- 특정 패킷에 대한 ACK를 받으면 해당 패킷의 타이머를 중지하고 확인 표시를 한다.
- 이 패킷이 윈도우의 첫 번째 패킷이면 윈도우를 다음 미확인 패킷까지 이동시키고 새롭게 윈도우에 들어온 패킷들을 전송한다.
- 특정 패킷의 타이머가 만료되면 해당 패킷만 재전송하고 타이머를 재시작한다. 다른 패킷들은 영향받지 않는다.
수신자
- 패킷이 도착하면 순서 번호를 확인한다.
- 패킷이 현재 수신 윈도우 내에 있고 오류가 없으면 ACK를 송신자에게 보내고 버퍼에 저장한다.
- 이 패킷이 rcv_base이면, 이 패킷과 연속된 다음 패킷들을 상위 계층에 전달하고 윈도우를 이동시킨다.
- 패킷이 이전 윈도우 내에 있으면 이미 처리했더라도 해당 패킷에 대한 ACK를 다시 보낸다.
여기서 이미 처리한 패킷의 ACK 재전송이 중요한데, 송신자와 수신자의 윈도우는 항상 동기화되어 있지 않을 수 있고, 만약 수신자가 이전에 처리한 패킷에 대한 ACK를 다시 보내지 않으면 송신자는 자신의 윈도우를 이동시키지 못하고 계속 같은 패킷을 재전송할 것이다.
윈도우 비동기화 문제
순서 번호는 0,1,2,3 윈도우 크기는 3이라고 가정해보자. 처음 송신자는 패킷 0,1,2를 보냈고 수신자는 이 패킷들을 정상적으로 처리하고 ACK를 응답했다. 수신자의 윈도우는 이제 [3,0,1]로 이동했다.
- 시나리오 1 : ACK가 손실된 경우
- 송신자가 보낸 패킷 0,1,2에 대한 ACK가 모두 손실되었다.
- 송신자는 ACK를 받지 못했으므로 시간이 지나 타임아웃이 발생한다.
- 송신자는 패킷 0,1,2를 다시 보내고 수신자는 패킷 0을 받는다. 이 패킷은 이전에 처리한 패킷이다.
- 시나리오 2 : ACK는 도착했지만 새 패킷이 손실된 경우
- 송신자가 보낸 패킷 0,1,2에 대한 ACK가 모두 잘 도착하고 송신자의 윈도우는 [3,0,1]로 이동한다.
- 송신자는 새로운 데이터를 담은 패킷 3,0,1을 보낸다.
- 패킷 3이 네트워크에서 손실되고 수신자는 패킷 0을 받는다. 이 패킷을 새로운 데이터를 담고 있다.
여기서 수신자는 이 패킷 0이 이전에 처리한 데이터의 복사본인지, 새로운 데이터를 담은 패킷인지 구별할 방법이 없다. 이 문제를 해결하기 위해 윈도우 크기를 순서 번호 공간의 절반 이하로 제한해야 한다.
패킷 순서 바뀜 현상
실제 네트워크에서는 패킷들이 다른 경로를 통해 이동하거나, 네트워크 장비에서 처리 시간이 다를 수 있기 때문에 패킷의 순서가 바뀔 수 있다. 한정된 번호를 사용하기 때문에 이전에 보낸 패킷 5가 네트워크에 남아있는데 새로운 데이터로 패킷 5를 다시 보내면 수신자가 혼란스러울 수 있다. 이를 해결하기 위해 네트워크에서 패킷이 살아있을 수 있는 최대 시간(e.g. 30초)을 가정하여 송신자는 패킷 5를 보낸 후 최소 30초가 지나야 다시 순서 번호 5를 전송할 수 있다. 이렇게 하면 이전 패킷 5가 네트워크에서 사라진 후에 새로운 패킷 5가 전송된다.
TCP에서는 32비트(약 43억)의 순서 번호 공간을 사용한다.
3.5 연결지향형 트랜스포트: TCP
TCP 연결
TCP는 애플리케이션 프로세스가 데이터를 다른 프로세스에게 보내기 전에, 두 프로세스가 먼저 핸드셰이크를 해야하므로 연결지향형이다.
- 3-way-handshake
- TCP 연결이 설정된 이후 : 핸드셰이크를 통해 연결이 설정되면 두 애플리케이션 프로세스는 서로 데이터를 보낼 수 있다.
- 최대 세그먼트 크기 (MSS) : 세그먼트로 모아 담을 수 있는 최대 데이터 양은 MSS로 제한딘다. MSS는 로컬 송신 호스트에 의해 전송될 수 있는 갖아 큰 링크 계층 프레임의 길이인 MTU에 의해 결정된다.
- TCP 세그먼트 : TCP 헤더 + 클라이언트 데이터로, 네트워크 계층에 전달되어 네트워크 계층 데이터그램 안에 캡슐화된다.
TCP 세그먼트 구조
- 출발지와 목적지 포트 번호
- 체크섬 필드
- 32비트 순서 번호 필드
- 32비트 확인응답 번호 필드
- 16비트 수신 윈도우 : 흐름 제어에 사용
- 4비트 헤더 길이 필드 : TCP 헤더의 길이
- 옵션 필드 : 선택적이고 가변적인 길이를 가짐
- 플래그 필드
순서 번호와 확인응답 번호
TCP는 전송하는 각 바이트에 번호를 부여한다. 세그먼트의 순서 번호는 해당 세그먼트에 포함된 첫 번째 바이트의 번호이다. 500,000 바이트 파일을 전송하고 MSS가 1000바이트라면
- 첫 번째 세그먼트: 순서 번호 0 (바이트 0-999)
- 두 번째 세그먼트: 순서 번호 1000 (바이트 1000-1999)
- 세 번째 세그먼트: 순서 번호 2000 (바이트 2000-2999)
- 각 순서 번호는 TCP 세그먼트 헤더의 순서 번호 필드에 포함
TCP는 양방향 통신을 지원하므로 호스트 A와 B는 동시에 데이터를 주고받을 수 있다. 호스트 A가 보내는 확인 응답 번호는 호스트 B로부터 다음에 받기를 기대하는 바이트의 번호이다. TCP는 누적 확인응답을 사용하여 특정 바이트까지 모든 데이터가 제대로 수신되었음을 확인한다.
- 호스트 A가 B로부터 바이트 0-535를 받았고, 바이트 900-1000도 받았지만 536-899는 아직 받지 못했다면
- 호스트 A는 확인응답 번호로 536을 보내고
- 이는 "536번 바이트부터 받기를 기대한다"는 의미이다.
데이터가 순서대로 도착하지 않은 경우, TCP 구현에 따라 순서가 바뀐 세그먼트를 즉시 버리는 즉시 폐기 방식과 임시 버퍼에 저장해두고 빠진 세그먼트가 도착하면 재조립하여 전달하는 버퍼링 방식이 있다. 대부분의 현대 TCP 구현은 후자를 사용한다.
왕복시간(RTT) 예측과 타임아웃
타임아웃은 세그먼트가 전송된 시간부터 ACK까지의 시간인 RTT보다 좀 더 커야 한다. 그렇다면 얼마나 커야할까? 그리고 RTT는 처음에 어떻게 측정할까?
- SampleRTT: 세그먼트가 전송된 시간부터 확인응답이 도착할 때까지의 시간 측정
- EstimatedRTT: 네트워크 상태 변화를 반영하기 위해 SampleRTT의 가중평균 사용
- DevRTT: RTT의 변동성 측정
RTT는 위와 같이 측정하고 타임아웃 값은 EstimatedRTT + 4×DevRTT로 설정한다.
신뢰적인 데이터 전송
TCP의 신뢰적인 데이터 전송은 프로세스가 자신의 수신 버퍼로부터 읽은 데이터 스트림이 손상되지 않았으며 손실이나 중복이 없다는 것과 순서가 유지된다는 것을 보장한다. 앞에서 각각의 세그먼트가 개별적인 타이머를 가진다고 설명했는데, 타이머 관리는 상당한 오버헤드가 요구되기 때문에 권장되는 TCP 타이머 관리 절차는 단일 재전송 타이머이다.
TCP 송신자의 주요 이벤트
- 애플리케이션으로부터 데이터 수신 : 데이터를 받고, 세그먼트로 캡슐화한 뒤 IP에게 이 세그먼트를 넘긴다.
- 타이머 타임아웃 발생 : 타임아웃을 일으킨 세그먼트를 재전송하여 응답하고 타이머를 재시작한다.
- ACK 수신 : 수신한 ACK값과 확인응답되지 않은 가장 오래된 바이트의 순서 번호를 비교하여, 이전에 확인응답되지 않은 세그먼트를 확인하고 윈도우를 이동시키거나 필요시 타이머를 다시 시작한다.
몇 가지 흥미로운 시나리오
호스트 A로부터의 세그먼트가 호스트 B 측에서 수신되었음에도 B로부터 A로의 ACK이 손실된다면
- 타임아웃이 발생한다.
- 호스트 A는 같은 세그먼트를 B에게 재전송한다.
- 호스트 B의 TCP는 재송신된 세그먼트의 바이트를 버린다.
호스트 A가 연속해서 두 세그먼트를 전송하는데 호스트 A에서 타임아웃 이전에어떠한 긍정 확인응답도 수신하지 못했다.
타임아웃 이벤트가 발생하면
- 호스트 A는 순서 번호 92로 첫 번째 세그먼트를 재전송한다.
- 타이머를 다시 시작한다.
- 새로운 타임아웃 이전에 두 번째 세그먼트에 대한 ACK가 도착하는 한, 두 번째 세그먼트는 재전송을 하지 않을 것이다.
호스트 A가 연속해서 두 세그먼트를 전송한다.
첫 번째 세그먼트의 긍정 확인응답이 네트워크에서 분실되었지만, 첫 번째 세그먼트의 타임아웃 전에 호스트 A가 긍정 응답번호 120의 긍정 확인응답을 수신하면
- 호스트 A는 호스트 B가 119바이트까지 모든 데이터를 수신했음을 알게 된다.
- 그러므로 호스트 A는 두 세그먼트 중 어느 것도 재전송하지 않는다.
타임아웃 주기의 두 배로 설정
TCP는 재전송 때마다 타임아웃 주기를 이전 값의 두 배로 설정한다. 이는 혼잡 제어를 제공하는데, 혼잡 제어는 밑에서 자세히 다룬다.
빠른 재전송
타임아웃을 통한 재전송은 타임아웃 주기가 길 수 있어 데이터 전송 지연이 발생하거나, 네트워크 자원이 낭비될 수 있다. TCP는 패킷 손실을 빠르게 감지하기 위해 송신자가 이전에 이미 받은 것과 동일한 확인응답을 다시 받는 중복 ACK 방식을 사용한다.
수신자가 예상하는 다음 순서 번호보다 더 큰 순서 번호를 가진 세그먼트를 받으면, 이는 중간에 세그먼트가 손실되었음을 의미한다. 이때 수신자는 마지막으로 올바르게 수신한 바이트에 대한 ACK를 다시 보낸다. 송신자는 같은 데이터에 대해 3개의 중복 ACK를 연속으로 받으면 세그먼트 손실이라고 간주하고 타이머가 만료되기를 기다리지 않고 즉시 해당 세그먼트를 재전송한다.
예를 들어, 송신자가 세그먼트 1, 2, 3, 4, 5를 순서대로 보냈는데 세그먼트 2가 손실된 경우:
- 수신자는 세그먼트 1에 대한 ACK를 보냄
- 세그먼트 3이 도착하면, 수신자는 여전히 세그먼트 2를 기다리고 있으므로 세그먼트 1에 대한 ACK를 다시 보냄(중복 ACK #1)
- 세그먼트 4가 도착하면, 또 다시 세그먼트 1에 대한 ACK를 보냄(중복 ACK #2).
- 세그먼트 5가 도착하면, 또 다시 세그먼트 1에 대한 ACK를 보냄(중복 ACK #3).
- 송신자는 3개의 중복 ACK를 받고 타임아웃을 기다리지 않고 즉시 세그먼트 2를 재전송
GBN인가 SR인가?
TCP는 두 가지 특성을 모두 가진다. GBN처럼 누적 확인응답 방식을 사용하며 확인응답 받지 않은 가장 작은 순서 번호와 다음에 전송할 바이트 번호를 관리한다. 그러나 GBN과 달리 순서가 바뀐 세그먼트라도 올바르게 수신되었다면 이를 버퍼링하고 패킷 손실이 발생했을 때 문제가 된 세그먼트만 선택적으로 재전송한다. TCP는 선택적 확인응답(SACK) 기능을 통해 마지막으로 올바르게 수신된 순서가 맞는 세그먼트에만 응답하는 대신, 순서가 틀린 세그먼트에도 선택적으로 확인응답할 수 있다. 이 SACK을 선택적 재전송과 결합하면 TCP는 SR 프로토콜과 매우 유사해진다.
흐름 제어
수신하는 애플리케이션이 다른 작업으로 바빠서 오랜 시간 동안 데이터를 읽지 않는다면, 송신자는 점점 더 많은 데이터를 빠르게 전송함으로써 수신 버퍼에 오버플로우를 발생시킬 것이다. 이를 방지하기 위해 TCP는 데이터를 읽는 속도와 보내는 속도를 일치시키는 흐름 제어를 제공한다. TCP는 수신 윈도우(rwnd)라는 변수를 사용하여 흐름 제어를 구현한다 :
- 수신 측은 데이터를 임시로 저장할 버퍼 공간을 마련한다.
- 수신 측은 얼마나 많은 데이터가 버퍼에 도착했는지, 그 중 얼마나 많은 데이터를 애플리케이션이 이미 처리했는지를 계속 추적한다.
- 버퍼에 얼마나 공간이 남아있는지(rwnd) 계산한다.
- 수신 측이 보내는 모든 메시지에 rwnd를 포함한다.
- 송신 측은 이 정보를 받고 ACK를 받지 않은 데이터 양이 여유 공간보다 작도록 유지한다.
그런데 수신 측의 버퍼가 가득 차서 여유 공간이 없다고 알렸는데, 그 동안 일부 데이터를 처리해서 공간이 생기면 이후 보낼 데이터가 없어서 송신측에게 여유 공간이 생겼다고 알릴 방법이 없는 문제가 생길 수 있다. TCP는 이런 상황에서 송신 측이 주기적으로 1바이트짜리 테스트 메시지를 보내도록 하여 해결했다.
TCP 연결 관리
클라이언트 애플리케이션 프로세스는 서버에 있는 프로세스와 연결 설정하기를 원한다는 것을 클라이언트 TCP에게 알리고, 클라이언트 TCP는 다은과 같은 방법으로 서버와 TCP 연결 설정을 시작한다.
연결의 설정 : 3-way handshake
- SYN (Synchronize): 클라이언트가 서버에 연결 요청을 보냄. 이때 클라이언트는 초기 시퀀스 번호(ISN)를 포함한 SYN 패킷을 전송
- SYN-ACK (Synchronize-Acknowledgment): 서버가 클라이언트의 SYN을 받고, 연결 요청을 수락했음을 알리는 SYN-ACK 패킷을 보냄. 이 패킷에는 서버의 초기 시퀀스 번호와 클라이언트의 시퀀스 번호에 1을 더한 확인응답 번호(ACK)가 포함됨
- ACK (Acknowledgment): 클라이언트가 서버로부터 SYN-ACK를 받고, 이에 대한 응답으로 ACK 패킷을 보냄. 이 패킷에는 서버의 시퀀스 번호에 1을 더한 확인응답 번호 포함
연결의 종료
- FIN (Finish): 연결을 종료하고자 하는 쪽(클라이언트나 서버)이 FIN 패킷을 상대방에게 보냄. 이는 "더 이상 보낼 데이터가 없음"을 의미함
- ACK: FIN을 받은 쪽은 ACK 패킷을 보내 FIN을 받았음을 확인함. 이 시점에서 FIN을 보낸 쪽에서 상대방으로의 연결은 끊겼지만, 상대방에서 FIN을 보낸 쪽으로의 연결은 아직 유지(반이중 연결, Half-Close 상태)
- FIN: 데이터 전송을 마친 후, ACK를 보낸 쪽도 FIN 패킷을 보내 연결 종료 요청
- ACK: 마지막으로, 두 번째 FIN을 받은 쪽이 ACK를 보내 연결 종료 확인. 이 ACK를 받은 후 일정 시간(TIME_WAIT)이 지나면 연결이 완전히 종료됨
SYN 플러드 공격
- 공격자는 핸드셰이크의 세 번째 단계를 완료하지 않은 상태에서 무수한 TCP SYN 세그먼트를 보낸다.
- 서버의 연결 자원이 반쪽 연결에 할당된다.
- 결국 서버의 연결 자원이 소진됨에 따라 합법적인 클라이언트들이 서비스 거부가 된다.
SYN 쿠키
이는 SYN 플러드 공격에 대한 방어책으로, 현재 대부분의 운영체제에 존재하고 있다. 서버가 SYN 패킷을 받았을 때 상태 정보를 저장하지 않고, 암호학적으로 생성된 시퀀스 번호(쿠키)를 SYN-ACK에 포함시켜 보낸다. 유효한 ACK가 돌아오면 그때 연결 상태를 생성한다.
3.6 혼잡 제어의 원리
앞에서 송신자가 수신자의 데이터 처리 능력을 초과하는 속도로 데이터를 전송해서 발생하는 문제를 해결하기 위한 흐름 제어에 대해서 다루었다. 혼잡 제어는 네트워크 전체의 데이터 과부하를 방지한다. 네트워크 혼잡은 너무 많은 출발지가 너무 높은 속도로 데이터를 보내려고 시도하기 때문에 발생하고, 이를 처리하기 위해 네트워크 혼잡을 일으키는 송신자들을 억제하는 매커니즘이 필요하다.
혼잡의 원인과 비용
2개의 송신자와 무한 버퍼를 갖는 하나의 라우터
이 시나리오는 두 호스트(A,B)가 무한한 버프를 가지는 하나의 라우터(R)를 공유하는 기본적이고 이상적인 상황을 보여준다. 호스트 A와 B가 동등하게 네트워크 자원을 공유할 때 각각 R/2의 처리량 이상을 시도한다면, 다른 호스트의 처리량이 줄어들거나 큐잉 지연이 발생한다. 각 컴퓨터가 R/2 미만의 속도로 데이터를 보내면 라우터가 모든 데이터를 바로 처리할 수 있어서 보낸 데이터가 목적지에 빠르게 도착한다. 만약 각 컴퓨터가 R/2에 가까운 속도로 데이터를 보내면, 라우터가 간신히 처리할 수 있는 상황이 되고 데이터가 가끔씩 버퍼에서 기다려야 한다. 하지만 아직 모든 데이터는 목적지에 도착할 수 있다. 그러나 각 R/2보다 빠른 속도로 데이터를 보내면 라우터가 처리할 수 있는 용량을 초과하여 데이터가 버퍼에 계속 쌓이게 된다. 대기실에 쌓인 데이터는 점점 더 오래 기다려야하고 각 컴퓨터는 결국 R/2 속도 이상으로는 데이터를 보낼 수 없게 된다.
2개의 송신자, 유한 버퍼를 가진 하나의 라우터
이 시나리오는 라우터 버퍼의 양이 유한하고, 각 연결이 신뢰적이라고 가정한다. 즉, 버퍼가 가득 차면 패킷은 버려지고 버려진 패킷은 재전송된다.
- a) 패킷 손실이 없는 경우
- 송신자가 버퍼에 여유가 있을 때만 데이터를 보냄
- 모든 패킷이 성공적으로 전달되고 재전송이 필요 없으므로 추가 부하 없음
- b) 패킷 손실 후 정확한 재전송
- 송신자가 패킷이 실제로 손실된 경우에만 재전송함
- 버퍼 오버플로우로 인해 일부 패킷이 손실되고, 손실된 패킷의 재전송이 추가 트래픽을 발생시킴
- 이 추가 트래픽은 다시 버퍼 오버플로우 확률을 증가시킴
- 제공된 부하가 R/2일 때 실제 처리량은 R/3으로 감소
- c) 불필요한 조기 재전송
- 이 경우는 타임아웃이 너무 짧게 설정되어 송신자가 불필요한 재전송을 함
- 패킷이 손실되지 않고 단지 큐에서 지연되고 있을 뿐인데 재전송하고 원본 패킷과 재전송된 패킷 모두 수신자에게 전달
- 제공된 부하가 R/2일 때 처리량은 R/4로 더욱 감소
이 시나리오들로 유한한 버퍼 크기는 패킷 손실과 재전송을 유발하고, 이는 네트워크에 추가 부하를 발생시킨다는 것을 알 수 있다. 또한 적절한 타임아웃 설정이 네트워크 효율성에 중요하고 패킷 손실을 감지하고 적절히 대응하는 효과적인 혼잡 제어 매커니즘이 필요하다는 것을 보여준다.
4개의 송신자와 유한 버퍼를 갖는 라우터 그리고 멀티홉 경로
이 시나리오에서는 A에서 C로 가는 트래픽과 B에서 D로 가는 트래픽이 라우터 R2에서 만난다. 이 때 두 트래픽 흐름이 R2의 버퍼 공간을 두고 경쟁하게 된다. B-D 트래픽이 증가할수록 A-C 트래픽의 처리량이 감소하고 극단적인 경우 A-C의 종단간 처리율이 0에 가까워진다. 또한 R2에서 패킷이 버려질 경우 R1까지 전달하는데 사용된 자원은 완전히 낭비된다. 즉, 위의 그래프처럼 초기에는 전송률이 증가할수록 처리량도 증가하지만, 특정 지점을 넘어서면 감소하고, 극단적인 상황에서는 처리량이 0에 가까워질 수 있다. 이 시나리오는 패킷이 경로의 후반부에서 버려질수록 더 많은 네트워크 자원이 낭비되고 긴 경로를 가진 트래픽일수록 자원 낭비 가능성이 높아짐을 보여준다. 또한 경로가 짧은 트래픽이 경로가 긴 트래픽보다 유리해져 네트워크 자원의 공정한 분배를 방해한다.
혼잡 제어에 대한 접근법
종단 간의 혼잡 제어
- 종단 시스템이 네트워크 동작을 관찰하여 혼잡 상태를 추측
- 패킷 손실과 지연 시간 증가가 주요 혼잡 신호로 사용되고 혼잡이 감지되면 윈도우 크기를 줄여 전송 속도를 감소시킴
- 네트워크 장비에 추가 기능이 필요 없음
- 혼잡 감지에 시간이 걸리고 정확한 혼잡 위치와 정도를 알기 어려움
네트워크 지원 혼잡 제어
- 라우터가 자신의 혼잡 상태를 직접 모니터링
- 라우터가 송진자에게 혼잡 상태를 알리는 패킷을 보내거나 통과하는 패킷에 혼잡 정보를 표시하고 수신자가 이를 송신자에게 전달
- 더 빠르고 정확한 혼잡 제어가 가능하고 네트워크 자원을 효율적으로 사용
- 라우터에 추가 기능 구현이 필요하고 더 복잡한 프로토콜이 요구됨
현재 인터넷은 주로 TCP의 종단 간 방식을 사용하지만 일부 매커니즘을 통해 네트워크 지원 요소도 도입되고 있다.
3.7 TCP 혼잡 제어
IP 계층은 네트워크 혼잡에 관해 종단 시스템에게 어떠한 직접적인 피드백도 제공하지 않는다.
전통적인 TCP의 혼잡 제어
TCP 송신자가 자신과 목적지 간의 경로에서 혼잡이 없음을 감지하면 송신율을 높이고, 혼잡을 감지하면 송신율을 줄인다. 여기서 세 가지 의문이 제기된다.
TCP 송신자는 자신의 연걸에 송신자 전송 트래픽 전송률을 어떻게 제한하는가?
추가적인 혼잡 윈도우(cwnd) 변수를 사용하여 네트워크로 트래픽을 전송할 수 있는 속도에 제약을 가한다. 송신하는 쪽에서 확인응답이 안 된 데이터의 양은 cwnd와 rwnd의 최소값을 초과하지 않는다. 따라서 cwnd의 값을 조절하여 송신자는 링크에 데이터를 전송하는 속도를 조절할 수 있다.
TCP 송신자는 자신과 목적자 사이 경로의 혼잡을 어떻게 감지하는가?
혼잡이 발생하면 경로에 있는 하나 이상의 라우터 버퍼들이 오버플로우되고 데이터그램이 버려진다. 이 버려진 데이터그램은 송신 측에서 손실 이벤트를 발생시킨다(e.g. 타임아웃 또는 3개의 중복된 ACK 수신). 이를 통해 혼잡이 발생했음을 알게 된다. 손실 이벤트가 발생하지 않은 경우는 확인응답이 늦은 속도로 도착한다면 혼잡 윈도우를 낮은 속도로 증가시키고, 확인응답이 빠른 속도로 도착한다면 혼잡 윈도우를 빠른 속도로 증가시킨다. 이를 확인응답을 혼잡 윈도우의 크기 증가를 유발하는 트리거(trigger)또는 클록(clock)으로 사용한다고 하여 자체 클로킹(self-clocking)이라고 한다.
TCP 송신자는 송신율을 변화시키기 위해 어떤 알고리즘을 사용해야 하는가?
TCP 송신자가 너무 빠르게 송신하면 혼잡 붕괴가 나타나고, 너무 천천히 송신하면 네트워크 내의 대역폭을 충분히 활용하지 못할 것이다. TCP는 다음의 원칙에 따라 자신이 송신할 속도를 결정한다.
- 세그먼트 손실 시 전송률 감소
- ACK 도착 시 전송률 증가
- 대역폭 탐색 매커니즘 : 혼잡 발생할 때까지 전송률을 꾸준히 증가시키고 혼잡 발생시 전송률을 감소시킨다. 이 주기적인 증가/감소를 통해 가용 대역폭을 지속적으로 탐색한다.
TCP 혼잡 제어 알고리즘
느린 시작(Slow Start)
TCP 연결이 처음 설정될 때 cwnd는 1 MSS로 초기화되고 매 세그먼트의 ACK를 수신할 때마다 cwnd를 1 MSS씩 증가시켜 1 RTT 동안 cwnd가 2배씩 지수적으로 증가한다. 이러한 지수적 증가는 가용 대역폭을 빠르게 탐색하는 데 효과적이다.
종료 조건
- 타임아웃 발생 시
- 심각한 네트워크 혼잡 신호로 해석
- cwnd를 1 MSS로 재설정하고 ssthresh를 타임아웃 발생 직전 cwnd 값의 절반으로 설정
- 새로운 슬로우 스타트 단계 시작
- cwnd가 ssthresh에 도달한 경우
- 이전 혼잡 이벤트로 설정된 임계값에 도달했다는 의미
- 더 이상 지수적 증가가 위험하다고 판단하여 슬로우 스타트를 종료하고 혼잡 회피 모두로 전환
- 3개의 중복 ACK 수신 시
- 경미한 혼잡 신호로 해석
- 빠른 재전송 수행 : 손실된 세그먼트 즉시 재전송
- 빠른 회복 상태로 전환
혼잡 회피(Congestion Avoidance)
`swnd >= ssthresh` 일 때(슬로우 스타트에서 임계값 도달 시) 혼잡 윈도우를 더 조심스럽게 증가시킨다. 혼잡 회피 상태로 들어가는 시점에서 cwnd의 값은 혼잡이 마지막으로 발견된 시점에서의 값의 반이 되고 RTT마다 cwnd를 선형적으로 1MSS씩 증가시킨다.
종료 조건
- 타임아웃 발생 시
- 심각한 네트워크 혼잡 신호로 해석
- cwnd를 1 MSS로 재설정하고 ssthresh를 타임아웃 발생 직전 cwnd 값의 절반으로 설정
- 새로운 슬로우 스타트 단계 시작
- 3개의 중복 ACK 수신 시
- 경미한 혼잡 신호로 해석
- cwnd를 현재 값의 절반으로 줄이고 ssthresh를 혼잡 발생 직전 cwnd 값의 절반으로 설정
- 빠른 회복 상태로 전환
빠른 회복(Fast Recovery)
빠른 회복 상태에서는 cwnd 값을 손실된 세그먼트에 대해 수신된 모든 ACK에 대해 1 MSS 만큼씩 증가시킨다.
- 손실된 세그먼트에 대한 ACK가 도착하면 TCP는 cwnd 혼잡 회피 상태로 들어간다.
- 만약 타임아웃 이벤트가 발생한다면 빠른 회복은 슬로우 스타트 및 혼잡 회피에서와 같은 동작을 수행한 후 슬로우 스타트로 전이한다.
- 즉, cwnd 값은 1 MSS로 하고 ssthresh 값은 손실 이벤트가 발생할 때의 cwnd 값의 반으로 한다.
TCP Tahoe(TCP 초기 버전)은 타임아웃으로 표시되거나 3개의 중복 ACK로 표시되는 손실이 발생하면 무조건 혼잡 윈도우를 1MSS로 줄이고 슬로우 스타트 단계로 들어간다. TCP Reno(새로운 TCP 버전)는 빠른 회복을 채택했다.
손실 이벤트가 발생했을 때
- TCP 리노
- 혼잡 윈도가 9•MSS로 설정되고
- 선형적으로 증가
- TCP 타호
- 혼잡 윈도는 1 MSS로 설정되고
- ssthreash에 도달할 때까지 지수적으로 증가하며
- 그 이후에는 선형적으로 증가
TCP의 혼잡 제어는 가법적 증가, 승법적 감소(additive-increase, multiplicative decrease, AIMD)의 혼잡 제어 형식이라고 불린다.
TCP CUBIC
패킷 손실이 발생한 혼잡한 링크의 상태가 많이 변경되지 않은 경우 전송 속도를 더 빠르게 높여 손실 전 전송 속도에 근접한 다음 대역폭을 신중하게 조사한다. 큐빅은 현재 대부분의 리눅스 운영체제에서 기번으로 사용되는 알고리즘이다.
- 혼잡 회복 방식 : 패킷 손실 후 선형적으로 증가하는 대신 세제곱 함수를 사용해 처음에 빠르게 증가하다가 이전 최대값(Wmax) 근처에서 빠르게 증가
- 대역폭 활용 전략 : 패킷 손실 지점까지 빠르게 복귀한 후, 그 근처에서 조심스럽게 탐색
- 더 빠른 회복 : 인터넷이 잠깐 끊긴 후 리노는 천천히 속도를 회복하지만 큐빅은 빠르게 이전 속도로 돌아옴
- 안정적인 최대 속도 유지 : 리노는 계속 속도를 올리다가 자주 끊기는 반면 큐빅은 최대 속도에서 조심스럽게 유지
- 네트워크 변화에 더 잘 적응 : 갑자기 더 빠른 네트워크로 바뀌면 큐빅은 이를 빠르게 감지하고 속도를 크게 높임
네트워크 지원 명시적 혼잡 알림과 지연 기반 혼잡 제어
명시적 혼잡 알림(ECN)
기존 방식은 패킷 손실이 발생한 후에야 혼잡을 감지한다. 라우터의 버퍼가 가득 차서 패킷을 버리면, 송신자는 이 패킷 손실을 통해 간접적으로 혼잡을 감지한다. ECN은 패킷 손실이 발생하기 전에 혼잡 상태를 알려주는 예방적 접근법이다.
- 사전 혼잡 감지: 라우터가 버퍼가 위험 수준에 도달했지만 아직 패킷을 버리기 전에 혼잡 감지
- 혼잡 표시: 라우터는 IP 패킷 헤더의 특정 비트(ECN 비트)를 설정하여 "이 경로가 혼잡해지고 있다"고 표시
- 수신측 처리: 수신 호스트가 이 표시된 패킷을 받으면, 송신자에게 보내는 ACK(확인응답) 패킷에 경로에 혼잡이 감지되었다는 ECE(ECN-Echo) 비트 설정
- 송신측 대응: 송신자는 이 ECE 비트가 설정된 ACK를 받으면:
- 혼잡 윈도우(한 번에 보낼 수 있는 데이터 양)를 절반으로 줄이고
- 다음 패킷에 CWR(Congestion Window Reduced) 비트를 설정하여 속도를 줄였다고 알림
지연 기반 혼잡 제어
패킷 지연 시간을 측정하여 혼잡을 감지하는 또 다른 예방적 접근법.
- RTT 측정: 송신자는 모든 패킷의 왕복 시간(RTT)을 지속적으로 측정
- 이상적 처리량 계산: "네트워크가 혼잡하지 않다면 이 정도 속도로 데이터를 보낼 수 있어야 한다"는 예상값
- 실제 처리량 측정: 실제로 얼마나 많은 데이터가 전송되고 있는지 측정
- 비교 및 조정:
- 실제 처리량이 이상적 처리량에 가까우면: 네트워크가 원활하게 동작 중 → 속도를 조금 더 높여도 됨
- 실제 처리량이 이상적 처리량보다 많이 낮으면: 네트워크 경로에 혼잡이 생기고 있음(패킷이 큐에서 지연됨) → 속도를 줄여야 함
두 매커니즘 모두 네트워크 혼잡을 조기에 감지하고 패킷 손실이 실제로 발생하기 전에 대응
공평성
공평성은 네트워크 자원을 여러 연결간에 어떻게 나누어 사용하는지에 관한 문제이다. 이상적인 공평성은 각 연결마다 대역폭을
- 초기 상태(A 지점):
- 두 연결이 모두 낮은 전송률로 시작함
- 두 연결의 총 대역폭 사용량이 전체 대역폭(R)보다 적음
- 패킷 손실이 없는 상태
- 윈도우 크기 증가:
- 두 연결은 "혼잡 회피 모드"에서 매 RTT(왕복 시간)마다 윈도우 크기를 조금씩(1 MSS) 늘림
- 마치 두 사람이 파이를 조금씩 더 가져가는 것과 비슷함
- 두 연결 모두 같은 속도로 증가하므로 45° 각도로 그래프가 진행됨
- 패킷 손실 발생(B 지점):
- 결국 두 연결이 사용하는 총 대역폭이 링크 용량(R)을 초과
- 패킷 버퍼가 넘쳐 패킷 손실 발생
- 이는 "파이를 너무 많이 가져갔다"는 신호
- 윈도우 크기 감소(C 지점):
- 두 연결 모두 윈도우 크기를 절반으로 줄임(TCP의 표준 대응)
- 결과적으로 처리율(throughput)도 감소
- C 지점은 B 지점에서 원점 방향으로 절반 지점
- 다시 증가(C→D):
- C 지점에서 다시 두 연결은 윈도우 크기를 증가시킴
- 다시 45° 방향으로 전송률 증가
- 반복되는 패턴:
- 이런 증가와 감소가 계속 반복됨
- 결국 두 연결의 처리율은 "동등한 대역폭 공유선" 주변에서 진동하며 수렴
현실에서의 공평성 문제
- RTT가 작은 연결은 더 빨리 피드백을 받아 더 빨리 윈도우를 키울 수 있음
- UDP는 혼잡 제어가 없으므로 혼잡하면 속도를 줄이는 TCP와 달리 UDP는 그대로 전송
- 한 애플리케이션이 여러 개의 TCP 연결을 동시에 사용하는 경우 더 많은 대역폭 확보