TCP 개요
이번주는 트랜스포트 계층의 대표 프로토콜인 TCP 프로토콜을 배워보겠다.
TCP 는 양방향 full duplx data 전송이 가능한 프로토콜이다.
tcp 연결을 통해 클라이언트, 서버가 양방향으로 데이터를 전송할 수 있다.
전송 시 단위는 MSS 로, 맥시멈 세그먼트 사이즈가 주어진다.
tcp 에서는 바이트로 연속적으로 전송하기는 하지만 물리적으로는 세그먼트이다.
그래서 tcp 에선 세그먼트라고 부른다.
참고로 최대 세그먼트 크기(MSS)는 이더넷 프레임 크기에 의해 그 크기가 결정된다.
TCP 는 연결을 만든다. 클라이언트 서버, 서버 클라이언트 사이에 연결을 만들고 데이터를 전송하게 된다
연결을 만드는 과정이 3-way handshake 이다.
3번의 메시지 교환으로 완성이 되기 때문에 이와 같은 이름이 붙었다.
tcp는 흐름제어기능을 제공하고 있다.
센더가 수신자의 버퍼를 버퍼오버플로 하지 않도록 전송량을 조절하는 방법이다.
tcp 연결은 1대1 연결이다. 그래서 여러 수신자가 동시에 받고자 한다 하더래도 연결은 개별적으로 만들어야한다.
tcp 에서 가장 중요한 기능 중 하나는 신뢰성 기능이다.
신뢰성 기능은
1. 오류가 없어야한다.(오류 복구기능)
2. 순서에 맞게 전송해야한다.
연결을 통해 전송하는 것은 바이트가 연속적으로 전송이 된다.
그리고 흐름제어를 통해 tcp 연결이 전송하는 이 메시지는 동시에 이 여러개가 전송이 될 수 있다.
파이프라이닝 기능을 통해 tcp 센더가 수신자로부터 ACK 을 받지 않고 동시에 여러개를 보낼 수 있다.
지난 시간에 go-back-N , Select 예제에서 파이프라이닝을 통한 성능 향상 예제를 살펴본 적있다. 그 기능이 tcp 에 있다고 생각하면 된다.
그리고 tcp 프로토콜은 송신자와 수신자의 버퍼를 가지고 있다. 지난번에 go-back-N 과 Selective 방식을 봤을 때 센더와 리시버의 버퍼 크기가 어떻게 변하는 지 살펴봤었다. tcp 는 센더와 리시버의 버퍼를 동시에 가지고 있다.
TCP 세그먼트 구조
TCP source 와 destination 포트 번호가 각각 1번쨰 2번째 16비트씩 찍혀있다. 그후 순서 번호와 상대편이 전송하는 ACK 번호가 각각 4바이트(32비트)로 있다.
ACK 을 보낼 때는 위 사진을 보면 플래그가 몇개 있는데 그 중 A라고 써있는 비트가 ACK 에 해당한다. 체크섬 기능은 오류 제어를 할 때 비트 오류가 있는지 확인할 때 사용한다(16비트)
그 아래 옵션은 여러가지가 가능하다. 디폴트로는 없이 전송할 수 있지만 옵션이 몇개 추가될 수 있다. 옵션을 뺀 나머지가 전체적으로 보면 20바이트가 되고, 옵션 그리고 어플리케이션 데이터가 전송이 된다.
또 헤더의 길이가 header length 라고 표시되어 있다.
그리고 아까 살펴본 것처럼 tcp 세그먼트 종류가 몇가지가 있는데 여기서 처럼 SYN 은 연결을 설정하는 메시지이고 연결을 종료하는 메시지는 FIN 이다. RST 는 오류가 있을때 재설정하는 필드이다. ( 위에 사진 참고)
윈도우는 버퍼에서 얼마나 남아있나를 표시한다. 16비트짜리 receive window 를 통해 흐름 제어 기능이 수행되고 있다.
그리고 urgent data field 라해서 긴급한 재설정을 할때는 긴급 메시지가 몇번 부터 있다고 알리는 포인트가 있다.
전송 과정
호스트 A 에서 B 에게 데이터 'C' 하나를 전송한다고 할 떄 첫 전송을 보면 순서번호가 적혀있고 ACK 번호가 있다. 여기서 말하는 ACK 은 상대편이 보낸 데이터에 대한 ACK 이다.
첫 전송에서 순서 번호가 42 이고 ACK 이 79이다. 이것은 "내가 보내는 건 42번이고 너가 보낸 거 잘받았으니 다음은 43번으로 보내!" 라는 의미이다. 그 후 두번째 화살표를 보면 순서번호 79이고 ACK 43이다. 이것은 "내가 보내는 건 79이고 잘받았으니 다음은 43번으로 보내!"라는 의미이다.
와이어 샤크로 TCP 데이터를 살펴보면 순서넘버가 1번 이렇게 찍혀있는데 이건 상대적인 것이고 실제 번호는 691694465이다.
또 길이가 439라고 찍혀있는데 저 위에 이더넷 프레임쪽을 보면 493바이트라고 나와있다. 이 차이는 왜 생기냐면 헤더의 값을 빼면 실제 tcp 세그먼트의 크기가 나오게 되는 것이다. 예를들어 이더넷헤더가 14바이트 ip헤더가 20바이트 tcp 헤더가 20바이트이므로 총 54바이트를 뺀 것이 439이다. (헤더가 제외된 len)
와이어샤크는 사용자가 보기 편하게 가공해서 보여준다.
아까 데이터를 전송하고 나면 응답이 오는데 ACK 이 적혀있는 것을 볼 수 있다 . 아까 보낼때 439였으므로 440이 ACK 넘버로 적혀있는 걸 확인할 수 있다.
와이어샤크는 위처럼 시각화 기능도 제공한다.
tcp 는 오류제어를 통해 오류가 있으면 복구해준다. 이를 위해선 재전송이 필요하다. 재전송을 하려면 내가 얼마만큼 기다려야할지 기다려야하고 그 값을 이용하여 타이머값을 설정해야한다. TCP 는 왕복시간을 항상 기록하고 있고 그값을 이용해서 타이머값을 세팅한다.
에러제어에서는 재전송이 사용되고 있고 재전송에는 타이머가 필수라 항상 측정이 필요하다.
timeout 값을 예를 들어 RTT 보다 긴값을 설정하면 굉장히 긴 값을 기다려야한다. 그 값만큼 아무 전송도 하지 않으면 성능이 떨어진다.
그렇다고 너무 짧게 잡으면 한창 전송될때 타이머 값이 종료되므로 재전송이 이루어질 수 있어서 불필요한 재전송이 발생할 수 있다. 너무 길지 않고 짧지 않은 타이머값 설정하는 게 굉장히 중요하다. 그 값을 어떻게 설정하려면 지속적으로 RTT 를 측정을 하고 적절한 값으로 정해야한다.
아래 값은 RTT 평균값을 예측하는 것이다.
평균 알파가 1/8이므로 위에 식에 따르면 샘플값을 1/8만큼 이전 RTT 값을 7/8 정도의 웨이트를 두게 된다.
RTT 값을 계산하고 난 후는 타이머값을 설정하는 것이다 . RTT 가 예를들어 1초걸렸다고 해서 타이머값을 1초를 설정해서는 안된다. RTT 보다 조금은 더 긴값으로 세팅을 한다. 예를 들면 2초 / 그 값을 시행착오로 겪어보니 아래와 같은 공식이 나왔다.
DevRTT 는 위에서 Moving RTT 식처럼 계산한다. 이전 DevRTT 값과 샘플값과 RTT 값을 사용한다. 베타는 1/4로 두고 DevRTT 값에 3/4 의 웨이트를 두고 Sample RTT, 추정 RTT 의 차이를 1/4 배 해서 더한 것이 RTT 편차 값이 된다.
TCP 는 기본적으로 신뢰성 있는 데이터 전송을 보장한다. TCP 밑에 IP 계층이 있다. IP 계층은 신뢰성이 없다. 그래서 IP 에서는 패킷을 라우터가 보내도 손실이 일어나면 재전송하는 기능이 없다. Tcp 는 우리가 쓰는 스마트폰, 서버, 기기 등 커널에 있어서 손실되면 재전송하기 때문에 신뢰성있는 전송을 보장할 수 있다.
TCP 는 타임아웃 이벤트나 ack 중복 이 두가지 경우에 재전송을 진행한다. 재전송을 하려면 항상 타이머값을 설정해야한다.
TCP 는 차례차례로 데이터를 받아서 세그먼트 단위로 만들고 순서번호를 붙인다. 예를들어 1기가 바이트 파일에서 1000바이트로 짤라서 세그먼트로 보낸다 하면 0~999, 1000~1999 이런식으로 번호를 쪼개서 보낸다 .재전송을 하려면 타이머값을 설정해야한다.
그리고 전송 시 Ack 이 와야하는데 ack 이 안오면 타임아웃 이벤트를 통해 재전송을 하게 된다. ack 이 정상적으로 도착하면 다음으로 넘어가게된다(업데이트) 아직 전송되지 않은 ack 이 오면 타이머값을 유지할 것이다
TCP를 재전송하는 시나리오들이다.
첫번째는 ack 을 손실한 시나리오이다. 이 과정은 데이터 손실과 동일하게 타이머값을 통해 손실을 탐지하고 재전송을 하게 된다.
오른쪽은 타이머값이 너무 작게 설정되어 있는 경우이다. 이 경우는 타이머값에 의해 손실되었다 판단하고 재전송을 하게 되는데 불필요한 전송이 일어난 것이다.
아래 시나리오는 b가 두개의 ack 을 보냈는데 나중의 것이 도착하더라도 첫번째게 손실된 상황에도 불구하고 아무 문제가 생기지 않는다. 이것은 두번째 ack 이 이전에 보낸 ack 을 포함하여 누적된 ack (120번이라는 ACK)이기 때문에 정상적으로 진행되는 것이다.
다음은 수신자에게 발생할 수 있는 이벤트 4개와 그에 따른 TCP 수신자의 액션을 표로 정리한 것이다.
첫번째는 예측대로 순서대로 세그먼트가 도착되는 상황이다. 근데 ACK 을 한 번 이미 보냈다는 조건이 있다. TCP 가 이런경우에는 수신자가 ACK 을 보내야하는데 delayed ACK 이라해서 보내는 것을 일단 기다려야한다. ACK 이 너무 많이 발생하는 것을 방지하고자 함이다. 한 개가 정상적으로 오더라도 최대 500ms 기다렸다가 보내야한다.
두번째는 순서대로 도착한 세그먼트에 대해 ACK 을 보내야하는 상황이다. 이때는 즉시 하나의 cumulative 한 ACK 을 보낸다.
세번째는 순서에 맞지 않게 도착한 세그먼트가 도착했을 때이다. 그러면 중간에 비어있는 갭을 탐지하게 된다. 그러면 앸을 알려줘야하는데 앸은 중간에 비어있는걸 알려줘야한다. 앸을 이전에 했던 1, 2 까지 잘 했는데 4번이 도착했다면 2번에 대한 앸을 다시 보내게 된다. 그건 바로 송신자 입장에선 중복된 앸이 된다. 나중에 재전송을 통해 앸이 정상적으로 보낼수 있는 상황이 오면 정상적인 앸을 보내게 된다.
아까전 재전송할 떄 손실을 탐지하려면 tcp 는
타임아웃과 중복된 ack 두가지 경우가 가능하다 했다.
중복 ack 을 통해 전송하는 기능이 Fast Retransmit 기능이다.
타이머를 이용하게 되면 재전송 시간이 굉장히 오래걸리게 된다. 기본적으로 RTT 보다 타이머값이 길기 때문에 결국은 성능이 떨어지게 된다. 이 타임아웃 시간이 길게 나타나는 것을 방지 하기 위해 중복된 ack 이 발생될때는 어떤 세그먼트가 손실됐음을 알 수 있다.
중복 ack 을 이용하면 타임아웃을 기다리지 않고 바로 재전송을 할 수 있다.
송신자가 3개 ack 을 동일하게 수신하면, 해당 세그먼트가 손실되었다고 판단한다.
아래 사진을 보면 ACK 100이 중복하여 4개가 발생했다. 이것은 송신자 A 입장에선 중복된 ACK 이 3개가 발생했으므로( 첫번째는 정상, 두번째부터 4번째까지는 3번 발생한 것이므로 ) 100번 시퀀스 넘버가 손실되었다고 판단할 수 있다.
그리고 이때 재전송을 할 수 있다.
재전송을 하면 타이머가 만료되는 것을 기다리지 않고도 재전송을 할 수 있다.
TCP 는 흐름제어 기능이 있다. 흐름 제어는 TCP 수신자의 버퍼 상태를 관찰하고 있다가 수신자의 버퍼가 오버플로가 발생하지 않도록 전송량을 제어하는 것이다. 수신자의 버퍼가 아래처럼 주어져 있을 때 송신자가 데이터를 전송하려고 수신을 잘 받아서 웹 브라우저가 열심히 읽고 있는 상황이다. 파란색은 비어있는 버퍼크기 이다. (Recieve Window) 그렇다면 이만큼 추가적으로 받을 수 있지 이것보다 많이 도착하게 되면 버퍼 오버플로가 발생할 수 있다. 그래서 송신자는 항상 수신자의 버퍼 크기를 계속적으로 관찰해야하고 그만큼만 최대로 전송해야한다. 그거보다 적게 보내면 성능 저하 많이 보내면 오버플로가 발생할 수 있다.
아까 우리가 헤더를 봤었는데 TCP 구조를 보면 receive window 로 수신자의 버퍼 크기를 알 수 있다.
receive window = (가장 마지막 바이트) - (읽은 바이트)
송신자는 이 receive window 보다 값을 작게 하여 보내야한다.
tcp 는 udp 와 다르게 연결을 하고 나서 데이터를 전송한다.
연결할 때 어떤 일을 하는 지 알아보자
우선 순서번호에 대한 약속이 필요하다.
같은 번호를 항상 쓰진 않고 랜덤하게 쓴다.
초기에 소켓을 만들어 tcp 연결을 만들어야하므로 흐름제어, 버퍼크기에 대한 기능을 수행해야한다.
파이썬, c , 자바 언어에서 보면 소켓을 초기화할때 보면 소켓이 바로 연결을 만들게 된다. 클라이언트에서 서버 상태로 데이터를 보내서 연결을 만들게 되는 단계가 3-way handshake 이다.
처음에 SYN 이라는 세그먼트가 전송되게 되고 여기서는 초기 시퀀스 번호가 있고 데이터는 없다. 두번째 서버에서 클라이언트로 전송할 때는 ACK 이 있다. SYN 에 대한 응답 ACK 이다. 그다음 클라이언트가 받은 SYNACK 을 통해 연결할 수 있다.
연결을 만들게 되면 종료도 실시해야한다. 마지막 타임드 웨이트가 있는데 종료하고 나서도 조금 기다려야하는 시간이다..
다음은 연결관리 상태도이다.
왼쪽그림이 클라이언트에서 SYN 보내고 SYNACK 도착하고 ACK 보내면 연결이 설정된 상태이고 데이터를 열심히 보내고 마지막에 FIN_WAIT 1,2 , TIME_WAIT 로 종료하게 된다.
서버는 listen() 상태에서 대기하다가 SYN 받고 SYNACK 보내고 SYN received 상태가되고 ACK 을 받으면 연결이 설정된다. 여기서 데이터를 받고 CLOSE_WAIT 를 거쳐서 종료하게 된다. CLOSE_WAIT 는 FIN 을 받고 송신하는 상태이다. CLOSE_WAIT 에서 종료를 할 경우에는 서버가 종료하고 나면 FIN 을 보내게 된다. 그래서 LAST_ACK 상태에 들어가는데 이 상태는 서버에서는 과도기 상태이다. 운용이 종료되는 시간을 주어주는 만약 이 상태에서 오류가 있으면 계속 펜딩된 프로세스가 발생할 수 있다.
- TCP 핵심 : 연결지향적, 신뢰성, 파이프라이닝, 일대일, 흐름제어, 양방향, 송신자수신자버퍼, 에러제어(타이머, 중복 ACK), 연결관리, 3-way handshake
'Network' 카테고리의 다른 글
[컴퓨터네트워크] 비동기 프로그래밍 (0) | 2023.10.15 |
---|---|
[컴퓨터네트워크] 소켓프로그래밍 (0) | 2023.10.12 |
[컴퓨터네트워크] FTP, SMTP 프로토콜 (0) | 2023.10.02 |
[컴퓨터네트워크] P2P 프로토콜 (1) | 2023.10.02 |
[컴퓨터네트워크] DNS (0) | 2023.09.25 |