TCP (Transmission Control Protocol)

게임 서버 프로그래밍/네트워크 개념정리

2020. 2. 19. 12:40

 

특징

양쪽의 호스트 사이에 연결을 계속 유지한 채로 신뢰성 있게 데이터의 스트림을 주고받을 수 있다.

TCP는 UDP와 다르게 의도된 수신자에게 모든 데이터를 순서대로 전달하려 최선을 다한다.

이를 위해 UDP에 비해 큰 헤더가 필요하며, 연결된 호스트마다 간단치 않은 연결 상태 추적 메커니즘이 돌아간다.

덕분에 수신자는 발신자에게 ACK(Acknowledgement, 확인응답) 할 수 있으며, 발신자는 ACK가 없는 부분의 데이터를 다시 보낼 수 있다.

 

신뢰성

멀티플레이어 게임 프로그래밍 책에서 발췌

고유하게 식별할 수 있는 패킷을 발신 호스트가 수신 호스트에 보내놓고 확인응답, 즉 ACK를 기다리며, 수신 호스트는 ACK를 기재한 패킷으로 응답한다. 한참 동안 ACK가 오지 않으면, 송신 호스트는 원래 패킷을 다시 보내 본다. 모든 데이터를 보내고 ACK를 받을 때까지 이 절차를 반복한다.

 

TCP 상태변수

TCP가 신뢰성 전달을 하기 위해 채택한 구현 전략에선 시퀀스 번호 추적과 데이터 재전송이 필요하다.

따라서, 각 호스트는 열려 있는 모든 TCP 연결에 대해 상태 변수를 유지해야 한다.

 

변수 약자 정의
송신 다음 번호 SND.NXT 호스트가 보낼 다음 세그먼트의 시퀀스 번호
송신 미확인 번호 SND.UNA 아직 ACK를 받지 않은 데이터의 가장 앞 시퀀스 번호
송신 윈도 SND.WND 호스트가 보낼 수 있는 데이터의 현재 용량
수신 다음 번호 RCV.NXT 호스트가 받을 것으로 예측되는 시퀀스 번호
수신 윈도 RCV.WND 호스트가 받을 수 있는 데이터의 현재 용량

 

TCP 세그먼트

TCP는 긴 스트림 데이터를 보내는 용도로 설계되었으며, 이 스트림으로 이어진 여러 마디 중 하나를 잘라 하위 계층의 패킷으로 포장한 것이 세그먼트이다.

 

TCP 헤더 구조

발신지 포트 주소 , 목적지 포트 주소 (16 bit)

전송 계층의 포트번호

 

Sequence number (32 bit)

단조 증가하는 식별 번호이다. TCP로 보내는 각 바이트마다 시퀀스 번호가 부여된다.

발신자와 수신자는 답신을 위한 표식으로 이 번호를 사용한다.

대개 세그먼트의 시퀀스 번호는 세그먼트에 포함된 데이터 첫 바이트의 시퀀스 번호이다.

 

ACK Number (32 bit)

발신자가 응답받기를 기다리는 다음 시퀀스 번호이다.

TCP는 데이터를 모두 순서를 지켜 전송하므로 호스트가 매번 기다리는 시퀀스 번호는 그전에 받은 번호에 항상 1을 더한 값이 된다.

수신자가 ACK 번호를 보내면 그 ACK 번호에 해당하는 시퀀스뿐만 아니라 그 아래 모든 시퀀스를 받았다는 뜻이다.

 

HLEN (Header Length) (4 bit)

헤더의 길이를 32비트 워드로 지정한다.

 

제어 비트 (9 bit)

헤더의 메타 정보 플래그를 포함한다.

 

Window Size (16 bit)

발신자가 데이터 전송에 사용하는 버퍼 용량이 얼마나 남아있는지 알려주는 역할

흐름제어에 사용된다

 

Urgent Pointer (16 bit)

제어비트에 URG 플래그가 설정된 경우에만 유용하다.

세그먼트 데이터 시작 위치를 기준으로 긴급 데이터의 위치를 나타낸다.

 

3-Way HandShaking

 

절차

  • 클라이언트가 서버에 첫 번째 세그먼트를 보내 연결을 시작한다. 이 세그먼트에는 SYN 플래그가 설정되어 있고, 초기 시퀀스 번호를 J으로 골라 두었다. (시퀀스 번호는 발신하는 호스트에서 임의로 고르는 것)
  • 이제 서버는 클라이언트가 시퀀스 번호 J부터 시작하는 TCP 연결을 시작하면 좋겠다는 요청을 받은 것이다. 서버는 이에 따른 연결 상태 유지에 필요한 자원을 할당해야 한다.
  • 서버가 연결을 받아줄 수 있는 상황이면, SYN 플래그와 ACK 플래그 둘 다 켜진 패킷으로 응답한다. 이때 응답 패킷의 ACK 번호는 클라이언트가 처음 보내준 시퀀스 번호 + 1로 한다.
  • 클라이언트가 이 세그먼트를 서버로부터 받고나면 지금 당장은 보낼 데이터가 없으므로, 서버가 보내준 첫 번째 시퀀스 번호를 잘 받았다고 응답만 해주면 된다. 따라서 SYN플래그는 끄고 ACK만 켜서 서버가 보내준 시퀀스 번호 +1을 ACK 번호로 하여 응답한다.

신뢰성 확보를 위해서는 여러 상황을 가정하여 데이터를 보내고 응답을 받아야 한다.

시간이 초과 되어 클라이언트가 SYN-ACK 세그먼트를 받지 못한경우는 크게 2가지다.

  •  서버가 SYN 세그먼트를 못받은 경우
  • 서버가 응답은 보냈지만 클라이언트가 이를 못받은 경우

두 경우 모두 클라이언트는 서버에 처음 세그먼트를 다시 보낸다.

 

클라이언트와 서버의 시퀀스 번호는 완전히 다르다. 이는 서로 다른 스트림을 사용하고 있다는 것을 말하고 있다.

 

데이터 전송

멀티플레이 게임 프로그래밍 책에서 발췌

데이터를 전송하려면 호스트가 각 세그먼트를 보낼 때마다 페이로드를 실어야 한다.

시퀀스 번호는 각 세그먼트 데이터의 첫 바이트를 가리킨다. 바이트마다 연속적인 시퀀스 번호가 붙어 있다.

SEQ = 이전 세그먼트 번호 + 데이터길이
ACK = 마지막으로 받은 세그먼트 번호 + 데이터 길이

 

TCP 패킷 손실시 대처 방법은 세그먼트를 다시 보내는 것. 그러기 위해서는 원래 데이터의 사본을 들고 있어야 한다.

TCP 모듈은 데이터를 모두 보낸 것을 확인응답받을때까지 그 데이터를 한 바이트도 빠트리지 않고 들고 있다.

세그먼트를 잘 받았다는 ACK를 받은 후에야 이 데이터를 메모리에서 제거할 수 있다.

 

기다리던 시퀀스 번호와는 다른 패킷을 호스트가 받을경우 2가지 해결 방법

  • 순서가 안 맞는 패킷을 소각하는 방법
  • 일단 버퍼에 저장해 놓지만 ACK도 주지 않고, 응용 계층에 넘기지도 않고 그대로 갖고있는 것.
세그먼트를 보낼 때마다 ACK를 기다려야 하는가?

실제로는 그러한 제약이 없다. 이러한 제약이 있다면 대역폭은 심하게 제한 받을 것이다.

 

TCP는 연결 중에 ACK 없이도 여러 세그먼트를 한꺼번에 보낼 수 있다.

그렇다고 무제한으로 보낼 수 있는 것은 아니다. 호스트에 메모리가 아무리 많아도 버퍼의 크기는 고정된다.

따라서 느린 CPU에서 돌아가는 복잡한 프로세스가 있으면 데이터를 소비하는 속도가 도착하는 속도를 따라가지 못하는 경우가 발생한다.

 

흐름 제어

빠른 송신 호스트가 느린 수신 호스트를 압도하지 못하게 제어하는 기법이다.

흐름 제어가 계속되어, 호스트 B는 항상 데이터를 받을 수 있는 여유가 얼마나 있는지 호스트 A에 알려주어, 버퍼에 담을 수 있는 이상으로 과도한 데이터를 보내지 못하게 한다.

 

슬라이딩 윈도우 알고리즘

대역폭 한도 = 수신 윈도 / 왕복 시간

수신 윈도를 너무 작게 잡으면 TCP 송신에 병목 현상이 일어난다.

 

TCP의 지연 ACK

등장 배경

연달아 보내는 ACK는 헤더의 낭비를 불러 일으킨다. (마지막 ACK는 이전 ACK를 포함하기 때문이다.)

 

내용

TCP 세그먼트를 받는 호스트는 즉각 응답할 필요가 없으며 최대 500밀리초까지 기다려보고, 시간 내에 다음 세그먼트가 오지 않는 경우에만 ACK를 보내도 상관 없다.

 

혼잡제어

등장 배경

느린 네트워크나 라우터에 과부하가 걸리는 것은 막을 수 없다.

 

내용

TCP 모듈은 혼잡을 줄이기 위해 ACK 없이 보낼 수 있는 데이터의 한도를 자발적으로 제한한다.

흐름 제어와 비슷하지만 수신 측의 윈도 크기에 따르는 것이 아니라, 송신자 자체적으로 패킷의

확인응답률 및 누락율을 집계하여 한도를 정하는 것이 차이점이다.

 

알고리즘

  • 구체적인 알고리즘은 구현 내용에 따라 달라지지만, 대부분 합 증가/곱 감소에서 파생된 기법을 사용한다.
    • 처음 연결을 맺을 때 TCP 모듈의 혼잡 임계치를 최대 세그먼트 길이의 낮은 배수로 잡고, 이후 세그먼트의 ACK를 받을 때마다 임계치를 최대 세그먼트 길이 만큼 더해나간다.
  • 패킷의 크기를 최대 세그먼트 길이에 가능한 한 가깝게 맞추어 네트워크 혼잡을 줄이는 기법도 있다.
    • 패킷에 40바이트의 헤더가 붙어야 하므로, 작은 세그먼트를 여러 개 보내는 것보다 큰 세그먼트 하나로 합쳐 보내는 것이 효율적이다.
    • 네이글 알고리즘을 적용하여 세그먼트를 보내기 전 데이터를 쌓아두도록 규정한다.
    • 이미 전송중인 데이터가 있을 때 이후 보낼 예정인 데이터는 쌓아두는데, 쌓인 양이 한계치를 넘어서면 그때 세그먼트로 만들어 보낸다.
더보기

네이글 알고리즘은 TCP를 전송 계층 프로토콜로 하는 게임에 있어서는 달갑지 않은 존재이다.

대역폭은 절약해 주지만, 데이터를 보내는데 지연이 훨씬 커지기 때문이다.

이 때문에 대부분 TCP 모듈엔 혼잡 제어를 아예 끄도록 하는 옵션이 마련되어 있다.

연결 해제

 

4-Way HandShake로서 연결을 종료한다.

절차

  • 연결을 종료하기 원하는 호스트는 FIN 패킷을 보내 데이터 전송이 끝났다는 것을 알려준다.
  • 이후 송신용 버퍼에 있는 모든 데이터를 비롯해 FIN 패킷까지 정상적인 절차로 전송하고, ACK를 받을 때까지 재전송도 통상과 같이 수행한다.
  • 상대 호스트도 데이터를 다 보낸 뒤엔 마지막으로 FIN 패킷을 보낸다.
  • 상대 호스트의 FIN을 받으면 연결이 완전히 종료된다.

'게임 서버 프로그래밍 > 네트워크 개념정리' 카테고리의 다른 글

직렬화  (0) 2020.02.24
NAT과 P2P  (0) 2020.02.19
UDP (User Datagram Protocol)  (0) 2020.02.19
서브넷과 서브넷 마스크  (0) 2020.02.18
ARP (주소 결정 프로토콜)  (0) 2020.02.18