TCP(1) - 개요

ksh98·2024년 6월 10일

네트워크

목록 보기
15/25

TCP의 특징

  • 연결 지향 프로토콜이다.
    • 연결을 위해 핸드쉐이킹을 한다.
    • 이 연결을 논리적인 연결이다.

실제로 직접 연결된 것이 아니고 가운데 많은 것들이 있으며 연결과 상태에 대해서 아는 것은 오직 양 끝단이다.

  • full duplex
    • 양쪽이 서로에게 데이터를 보낼 수 있다.
  • 흐름 조절
    • 받는 사람의 버퍼를 생각해서 보낸다.
  • 신뢰성있고 순서에 맞는 바이트 스트림이다.
    • 패킷을 보내면 실제로는 바이트로 쪼개서 보내진다.
    • 이는 흐름 조절을 위한 것이다.
    • mss란 최대 세그먼트 크기이다.
  • 파이프라이닝을 한다.
    • 즉 윈도우가 있다는 것이다.
    • 정체와 흐름을 통제하며 윈도우 크기를 조절한다.

세그먼트 구조

크게 두 부분으로 나누면 위의 헤더부와 아래 페이로드로 나눌 수 있다. 헤더에는 다음과 같은 것들이 있다.

소스, 목적지 포트 번호

시퀀스 번호

  • 세그먼트의 첫번째 바이트의 번호이다.

데이터를 mss 단위로 쪼개 하나의 세그먼트를 만들고 각 세그먼트의 첫번째 바이트가 시퀀스 번호가 된다.
이때 첫 세그먼트의 시퀀스 번호는 0부터 시작하지 않고 랜덤 번호로 시작한다. 만약 0부터 시작하면 켰다 껐다를 반복했을 때 0, 1, 2가 반복해서 가니 받는 사람이 햇갈릴 수도 있기 때문이다.

연결이 될 때 서로 어떤 번호로 시작했는지 알 수 있다.

애크 번호

  • 다음에 받길 기대하는 번호이다.

한 메세지 안에 시퀀스 번호와 애크 번호가 같이 있는 이유
tcp는 양쪽이 모두 메세지를 보낼 수 있고 받을 수 있다. 그래서 내가 메세지를 보내는 동시에 애크도 같이 보내기 위해서 그런 것이다.

SYN, FIN

각각 연결을 만들고 연결을 해제할 때 사용된다

receive window

흐름 조절과 관련이 있고 내가 얼마나 받을 수 있는지가 들어있다.

TCP 타임아웃

로스가 일어난 경우 복구를 해야하니 타임아웃을 걸고 재전송할 준비를 해야한다. 이때 타임아웃은 당연히 rtt보다 길게 해야한다. 그러나 rtt는 네트워크 상황에 따라 계속 변한다. 그래서 rtt를 예상해야 한다.

SampleRTT

  • 처음으로 보내진 한 세그먼트가 애크를 받는데 까지 걸리는 시간
  • 재전송된 세그먼트에 대해서는 SampleRTT를 측정하지 않는다.
  • RTT는 네트워크 상황에 따라 매우 다양할 것이다. 따라서 평균을 구해야 한다.

EstimatedRTT

  • 예상 rtt로 지금까지 측정해온 samplertt의 평균

  • estimatedrtt는 기존의 값이고 samplertt는 방금 측정된 값

  • ewma라는 평균을 사용한다.

    	EstimatedRTT = (1 - α) * EstimatedRTT + α * SampleRTT

과거 샘플의 정보가 담긴 이전의 EstimatedRTT는 앞의 계수가 지수적으로 늘어나면서 작아져 즉 과거 샘플의 영향은 줄어들고 현재 샘플의 영향은 α * SampleRTT가 항상 그냥 더해지니 변함이 없다.

DevRTT

  • EstimatedRTT는 타임아웃 시간으로 이용하기는 부적절하다.

  • SampleRTT의 변동성이 큰 경우 너무 빠른 타임아웃을 할 수 있기 때문이다.

  • 따라서 세이프티 마진인 DevRTT를 더해줘야 한다

    	DevRTT = (1 - β) * DevRTT + β * |SampleRTT - EstimatedRTT|

TimeoutInterval

  • 최종적인 타임아웃 간격

  • 이걸 사용하면 된다.

    	TimeoutInterval = EstimatedRTT + 4 * DevRTT

신뢰성 있는 전송

  • 받은 바이트 스트림과 보낸 바이트 스트림이 정확히 일치해야 한다.
  • 파이프라이닝, 캐크, 재전송, 타이머를 이용해 구현한다.
  • 재전송은 타임아웃중복 애크가 오면 발생한다.

중복 애크가 오는 경우 예를 들어
1 2 3 4 5를 보냈는데 로스가 나서 계속 1 2 2 2처럼 중복 애크가 오면 무엇이 잘못됐다고 생각할 수 있다. 따라서 중복 애크가 와도 재전송한다.

전송자의 유한 상태 머신

응용으로 부터 데이터를 받으면

  • mss로 나누어 세그먼트를 만들고
  • 세그먼트의 시퀀스 번호를 정하고
  • ip를 이용해 전송하고
  • 다음 시퀀스 번호를 계산하고
  • 가장 오래 응답을 못 받은 세그먼트에 타이머를 건다. 이때 타임아웃 간격은 앞에서 계산한 timeoutinterval이다.

타임아웃이 발생하면

  • 아직 응답을 받지 못한 세그먼트중 시퀀스 번호가 제일 낮은 녀석을 재전송하고
  • 타임 아웃 시간을 두배로 하고
  • 타이머를 시작한다.

이때 타임 아웃 시간을 두배로 하는 이유
tcp는 유선 환경을 가정하여 로스는 오직 큐 로스 밖에 없다고 생각한다. 따라서 큐 로스가 일어나 정체가 있다 생각하고 막 재전송을 하지 못하게 타임 아웃 시간을 두배로 한다.

애크를 받으면

  • 캐크와 sendbase를 비교한다 이때 sendbase는 아직 애크를 받지 못한 가장 번호의 바이트를 말한다.
  • 만약 sendbase의 값보다 캐크가 크다면 캐크 - 1의 값까지는 모두 정상적으로 잘 받은 것이니 sendbase = 캐크라 한다.
  • 그리고 남아있는 세그먼트가 있다면 타이머를 재설정하고 없다면 타이머를 끈다.

수신자의 캐크 생성 규칙

순서에 맞고 예상한 번호를 받고 모든 세그먼트가 응답을 받은 상태

  • 애크를 너무 주고 받는 것을 방지하기 위해 잠깐 기다렸다 더이상 세그먼트가 안 오면 그때 애크를 보낸다.
  • 이는 캐크이기 때문에 가능한 건데 한 이전 세그먼트에 대한 캐크를 안 보내도 최신의 캐크가 이전의 세그먼트를 잘 받았다는 것을 보장하기 때문에 가능한 것이다.

순서에 맞고 예상한 번호를 받았지만 응답을 기다리는 세그먼트가 있음

  • 그럼 즉시 하나의 캐크를 보내 두 세그먼트가 응답을 받게 한다.
  • 예를 들어 1번의 경우와 같이 한 세그먼트가 기다리고 있는데 또 다른 정상 세그먼트가 오면 캐크를 보내 두 세그먼트를 처리한다.
  • 그래서 보통 세그먼트 2개 보내면 캐크 1개 정도 온다.

순서에 안 맞고 예상한 것 보다 높은 번호가 오면

  • 갭이 있다는 것이니 즉시 하나의 캐크를 보낸다.

예를 들어
1 2 3 4 5를 보내는데 3이 없어지면 이상하다 생각하고 2 3 3 3 같이 캐크를 보낸다.

갭을 채우는 캐크가 오면

  • 즉시 캐크를 보낸다.

예를 들어
위의 경우 3이 오면 캐크6을 보내는 것이다.

빠른 재전송

  • 타임아웃은 보통 길다.
  • 따라서 로스가 있어도 타임아웃되길 기다려서 tcp가 느릴 수도 있다.
  • 그래서 중복 애크가 오면 세그먼트 로스가 났다고 생각한다.
  • tcp 에서는 3개의 중복 애크가 오면 로스가 났다고 생각하고 타임아웃을 기다리지 않고 즉시 재전송한다.

흐름 조절

  • 수신자가 세그먼트를 받으면 헤더 때고 하면서 세그먼트가 tcp에게 갈 것이다.
  • tcp는 이걸 tcp 소켓 수신자 버퍼에 넣는다.
  • 응용이 receive 함수를 부르기 전까지 여기에 넣어둔다. 오직 리시브 함수를 호출해야지만 버퍼가 비워진다.
  • 만약 리시브 함수를 많이 호출하지 않는다면 점점 차고 어느 순간 오버플로우가 날 것이다.
  • 이런 경우 때문에 흐름 조절이 중요하다.

이를 위해 수신자는 자신에게 얼마의 공간이 남았는지 알려준다. 수신자는 전송자에게 뭘 보낼 때 receive window 즉 rwnd 헤더 값을 채워서 보낸다.

전송자가 rwnd의 값을 받으면 이에 맞게 윈도우의 크기를 조절해서 수신자 측에서 오버플로우가 나지 않게 한다.

커넥션 관리

  • 연결하기 전에 핸드쉐이크를 한다.
  • 핸드쉐이크는 연결을 만드는 것과 초기 시퀀스 번호같은 파라미터 공유에 동의를 하는 과정이다.

3 웨이 핸드쉐이크

  1. 클라이언트가 연결하고 싶어서 syn 비트를 1로 해서 신 메세지를 보냄 이걸 보내면 클라이언트는 신을 보내고 응답을 기다리는 신 센트 상태가 된다.
  2. 서버는 신에 대한 애크를 보낸다. 이때 예시의 경우 신 애크의 애크 번호는 당연히 x+1 이걸 보내면 서버는 신 리시브 상태가 된다.
  3. 클라이언트는 서버에게 신애크를 받아서 서버가 잘 돌아가고 있다는 걸 알게 된다. 이걸 받으면 클라이언트는 연결 상태가 된다.
  4. 이제 신 애크에 대한 애크를 보내 서버에게 잘 받았다고 알려준다. 여기에는 애크와 함께 데이터를 싫어 보낼 수도 있다. 서버는 신애크에 대한 애크를 받으면 연결 상태가 된다.

유한 상태 머신

클라이언트

  1. 연결하고 싶어서 소켓을 만들고 신을 보내면 신 센트 상태가 된다.
  2. 신 센트 상태에서 알맞은 번호를 가진 신 애크를 받으면 알맞은 번호의 신애크에 대한 애크를 보내고 연결이 된다.

서버

  1. 서버는 소켓을 만들고 accept 함수를 호출해 리슨 상태가 된다.
  2. 신을 받으면 신애크를 보내고 클라이언트와 소통을 위한 소켓을 만든다. 이후 신 리시브 상태가 된다.
  3. 신애크에 대한 애크를 받으면 연결이 된다.

연결 닫기

3웨이 핸드쉐이크와 비슷하다

  1. 클라이언트가 연결을 끊고 싶어서 핀을 1로 해서 보내면 핀 웨이트 1이 된다.

클라이언트는 더이상 데이터를 보내지 못하고 받기만 할 수 있다. 이는 서버에서 데이터를 더 보내고 싶어할 수 있어서 그런 것이다. 다만 상대방이 보낸 애크에 대한 데이터는 보낼 수 있다.

  1. 서버는 핀을 받으면 클로스 웨이트 상태가 된다.

이는 내 의견과 상관없이 상대방이 끊고 싶어한다는 것을 의미한다. 핀에 대한 응답을 보내도 되고 안 보내도 된다. 응답을 보내도 계속 데이터를 보낼 수 있다.

  1. 클라이언트는 핀 애크를 받으면 핀 웨이트 2가 된다.

이는 서버가 내가 보낸 핀을 받은 걸 아는 상태이다. 서버가 끊고 싶다는 말은 안 했으니 계속 기다려야 한다.

  1. 어느 순간 서버가 연결을 끊고 싶어 핀을 보내고 라스트 애크 상태가 된다.

이 순간부터는 서버도 더이상 데이터를 보낼 수 없다.

  1. 클라이언트가 서버의 핀을 받으면 타임드 웨이트 상태가 된다. 둘 다 끊고 싶다고 말하니 클라이언트가 마지막 애크를 보낸다.

이 애크는 서버의 핀에 대한 애크이고 타임드 웨이트에서 클라이언트가 기다리는 것은 혹시 모를 마지막 애크의 재전송이다.

  1. 서버는 핀 애크를 받으면 즉시 연결을 종료한다.

서버는 보낼 데이터가 없다면 클라이언트의 핀을 받았을 때 핀 애크를 한번에 보낼 수도 있다.

profile

0개의 댓글