[컴퓨터 네트워크] TCP 심화

Pakxe·2023년 10월 10일
0

컴퓨터 네트워크

목록 보기
16/16
post-thumbnail

TCP

이제부터 TCP를 구성하는 주요 개념들을 알아볼 것이다.

point-to-point

하나의 sender와 하나의 receiver가 존재한다.

byte

TCP는 패킷이 아니라 바이트 단위를 사용한다. 시퀀스 번호가 바이트가 된다는 뜻이다. 500바이트 데이터를 보낸다고 하면 0~99바이트를 보내면서 시퀀스 넘버가 0이 되고, 100~199 바이트를 보내면 시퀀스 번호가 100이 되는 식으로 보내기 시작한 바이트로 시퀀스 넘버를 쓴다.

window

sender의 window과 receiver의 window가 존재한다. 그리고 서로 최대로 보낼 수 있는 MSS(maximum segment size)를 합의해야한다. 이는 TCP연결을 맺을 때 결정한다.

receiver의 윈도우 사이즈를 variable하게 해 ACK를 보낼 때마다 버퍼의 상황을 보고 적절한 윈도우 사이즈를 보낸다.
이때 receiver의 버퍼 크기는 MSS보단 크다. APP레이어가 데이터를 가져가는 속도보다 버퍼가 빨리 차오르면 오버플로가 발생하기 때문이다.

이렇게 서로의 윈도우 크기가 다를 수 있으므로, sender는 이 둘 중 작은 것을 기준으로 보내게 된다.

congestion control

네트워크 혼잡 상태에서 receiver가 보낸게 계속 로스되어 재전송했다고 해보자. 그럼 혼잡 상태니까 재전송 한 것도 또 로스되어 다시 재재전송한다. 이렇게 반복되면 혼잡 상태가 풀리긴 커녕 더 악화될 것이다. 따라서 원랜 윈도우 만큼 보냈던걸 윈도우를 가득 채우는 것이 아니라 윈도우보다 적게 보낸다. 이걸 얼마나 줄이느냐를 결정하는게 congestion window 이다. sender는 rcvW과 conW중 더 작은 값으로 윈도우를 설정해 보낸다.

TCP segment structure


뭐가 많지만 중요한 것만 읽자.
sequence number: 데이터 바이트 순서
ackownledgement number: ACK하는 넘버
A: Ack 필드를 무시하라는 의미
P: 모아서 올리지 말고 바로 올리라는 명령
R: 리셋
S: 커넥션 맺자고 요청하는 것. 맺어지면 0, 맺어지기전은 1이다.
F: 커넥션 끊을 때 사용한다. 난 이제 안보내겠다는 의미. 하지만 상대는 아직 보낼 수 있다. 끊으려할 때 1로 바꾼다.
receive window: 현재 나의 윈도우 버퍼 크기

TCP seq #, ACK


위 상황에서 Host A의 초기 시퀀스 넘버는 42이고 Host B의 초기 시퀀스 넘버는 79이다.

따라서 A가 세그먼트를 보낼 때 ACK를 79로하고 보내는 것이다.

그리고 B는 이에 응답하여 79 시퀀스를 보내는데 ACK가 43이다. 이는 기대하는 세그먼트가 43번이라고 이쪽에서 미리 더해서 주는 것이다.

그 후 A는 79를 잘 받았다고 ACK 80과 다음 시퀀스 넘버인 43으로 확인을 알리게 된다.

TCP 타이머의 타임아웃 주기

타임아웃되는 시간을 어떤 기준으로 정해야할까?

너무 짧으면 불필요한 재전송이 많이 발생할 것이다. 또 너무 길면, 세그먼트 로스에 늦게 대응하기 때문에 적절한 시간이 필요하다.

보통은 RTT보다 큰 값을 가진다.

그럼 RTT는 어떻게 계산할까?
sampleRTT를 사용해 세그먼트 전송 후 ACK가 올 때 까지의 시간을 측정한다. 이 값은 네트워크 상황에 따라 혼잡이 있을 수 있어 세그먼트마다 sampleRTT값이 다르다. 따라서 불규칙적인 값을 갖는다. 대체로 RTT를 추정하기 위해선 sampleRTT값의 평균값을 채캑하는데, 새로운 sampleRTT를 획득할 때마다 아래 공식으로 계산하게 된다.

보통 알파값은 0.125로 두고 계산한다. 즉 이전에 계산된 RTT의 가중치는 0.875이고, 새로 계산된 RTT는 0.125의 가중치로 새로운 평균 RTT를 계산한다는 의미이다.

그리고 현재 측정된 RTT가 기존의 평균 RTT를 얼마나 벗어나는지 파악하는 것도 중요하다. 이 변화율은 아래 공식으로 계산한다.

이렇게 계산된 두 값 EstimatedRTTDevRTT를 가지고 타임아웃 주기를 계산할 수 있다. 분명 앞에서 RTT보단 커야하지만 너무 크면 또 낭비하는 시간이 길어지기 때문에 적당히 커야한다. 따라서 얼마나 늘릴지를 DevRTT를 통해 정하게 된다. 결과적으로 타임아웃 주기는 아래 공식을 따라 계산한다.

TCP reliable data transfer

TCP는 go-back-N방식처럼 cumulative ACK를 사용한다. 따라서 하나의 타이머만 갖고 있다. 그리고 pipeline방식을 사용해 ACK없이도 여러 개의 세그먼트를 전송할 수 있어 효율적이다.

다만 go-back-N과 다른 점은 duplicated ACK에 대한 처리 방식이다.

예를 들어 ACK없이 50바이트를 보낼 수 있어, 10byte씩 세그먼트를 나눠보냈다고 해보자. 그럼 총 5개의 세그먼트가 시퀀스 넘버 0, 10, 20, 30, 40으로 전송되었을 것이다. 그런데 이때 시퀀스 넘버 10만 손실이 나고 모든 세그먼트가 잘 도착했다고하면 0, 20, 30, 40에 대한 ACK만 sender에게 도착할 것이다. 그러면 10을 다시보내야 하니까 ACK 10이 4개 도착한다. 그러면 중복되는 ACK가 4개인 것.

fast retransmission

이런 상황에서 TCP는 타임아웃될 때까지 이 중복 패킷들을 무시하는게 아니라 중복 패킷이 3개 이상 도착하면 타임아웃을 신경쓰지 않고 바로 이 패킷부터 보냈던 패킷까지 재전송한다. 이를 fast retransmission이라고 한다.

TCP FSM

위의 TCP FSM을 보면서 하나씩 과정을 이해해보자.

APP 에서 데이터가 내려오면 nextseqnum을 넣어 세그먼트를 만든다. 그리고 NET 계층으로 내려보낸다. 그 다음 nextsegnum += length(data)로 보냈던 데이터 만큼을 이동해 다음 nextsegnum으로 지정한다.(0번에서 10바이트를 보냈다면 0~9까지를 보낸게 되고, 다음 seqnum은 10이 되어 이어서 보내지는 것)

if(timer currently not running)은 현재 타이머가 돌고 있지 않다면 타이머를 시작한다는 뜻이다. 이게 왜 있냐면, 0번 세그먼트를 보내고나서 바로 이어서 1번 세그먼트를 보낼 땐 0번 세그먼트의 타이머를 덮어쓰지 않는다는 의미다. 따라서 타이머는 기본적으로 base 세그먼트의 타이머로만 동작하게 되는 것이다.

연달아 여러 개의 패킷을 보내고 타임아웃이 난다면, GBN방식에서 배운것과 똑같이 보낸 후 타이머를 다시 시작한다.

ACK를 받고, 그것의 시퀀스 넘버가 send_base보다 크다면 send_base를 이동시킨다. send_base는 send_base - 1까지는 무사히 잘 갔다는 의미이다.

만약 그렇게 이동했는데도 보냈는데 ACK안된 세그먼트가 남아있다면 타이머를 재시작한다. TCP의 타이머는 base 세그먼트의 타이머로서만 동작해야하기 때문이다.
안남아있다면 타이머를 멈춘다.

TCP 3-way handshake

TCP는 데이터를 보내기 전 연결을 설정해야한다.

이때 클라이언트는 S헤더의 값을 1로 설정해 "연결 하자" 라고 요청을 보낸다. 이때 함께 보내지는 시퀀스 넘버는 초기 시퀀스 넘버 x라고 하자. 그리고 Ack 필드는 아예 값을 안넣을 순 없으니 의미없는 값으로 채워져있을 텐데 이는 A필드의 값을 0으로해 ACK필드 값을 무시하라고 명령해서 해결한다.

그리고 서버는 마찬가지로 S = 1과 자신의 초기 시퀀스 넘버인 y를 보낸다. 그리고 A = 1로해 "너의 S를 잘 받았어" 라고 알린다, 그리고 Ack 필드의 값은 x + 1로해 "x를 잘 받았어"라고 알린다.

마지막 단계는 A = 1로하고, Ack 필드의 값을 y + 1로해 "너의 y를 잘 받았어"리고 알린다. 이제부턴 연결되었으니 S = 0으로 해 상호 연결 완료 상태인 것으로 표시한다.
이 세그먼트에는 데이터를 담을 수 있으므로, 보통의 3way handshake는 1RTT만 필요하다고 할 수 있다.

TCP closing connection

TCP 연결을 끊으려면 F헤더를 이용한다.

F를 1로하고 시퀀스 넘버를 x로 해 TCP 연결 해제를 요청한다.

서버는 이걸 받고 A를 1로 한 후(0이면 무시, 1이면 의미있음), ACK 필드에 x + 1로 "끝내고 싶다는 요청 잘 받았어"를 전달한다.

그후 클라이언트는 연결이 끊어진 상태가 된다. 하지만 아직 서버는 보낼 수 있으므로 서버 또한 클라이언트에게 동일한 과정으로 연결 해제 요청을 하고 완전히 서로에 대한 TCP 연결을 끊게 된다.

TCP congestion control

네트워크가 혼잡(congestion)상황이면 손실과 딜레이(timeout유발)가 많이 발생한다. 이런 상황에서 무작정 세그먼트들을 다시 다 보낸다고해서 혼잡은 더 가중될 뿐 해결되지 않기 때문에, TCP는 이런 혼잡 상황을 최대한 피하기 위해 세그먼트를 보내기 위해 congestion control을 수행한다.

세그먼트를 처음 보낼 때는 slow start전략을 사용한다. 일단 네트워크 상황이 어떨지 모르니까 조금씩 전송하는 것이다.

이때 congestion window MSS를 2배씩 증가하면서 네트워크 간을 보는데, congestion window는 혼잡을 피하기 위한 윈도우 크기라는 의미이다.

처음엔 1MSS를 보내고, 문제가 없었다면 1MSS x 2 = 2MSS를 보낸다. 또 문제가 없었다면 2MSS x 2 = 4MSS를 보낸다. 그렇게 8MSS까지 보냈는데, 로스또는 딜레이로 인해 아무것도 도착하지 않아 발생한 타임아웃이라면, 네트워크 혼잡이 발생했음을 인지한다.

아무것도 도착하지 않아 발생한 타임아웃은 네트워크가 심각하게 혼잡하다는 의미이므로 애매하게 줄이지 않고 다시 처음의 1MSS로 돌아가 slow start를 수행한다.

그렇게 하다가 다시 문제가 발생하는데 이땐 3 duplicated ACK가 발생했다고 해보자. 이땐 아까 아예 로스된 상황보다는 나은 상황이다. 따라서 보내던 n MSS 를 2로 나눈 값부터 다시 보내기 시작하는데 이땐 slow start가 아닌 congestion avoidance전략을 취한다. 이 전략은 기하급수로 증가하는게 아닌 선형적으로 증가한다는 뜻이다.

그렇게 선형증가를 반복하다가 다시 3 duplicated ACK가 발생했다면 그때의 MSS/2한 값부터 다시 congestion avoidance를 수행한다.

두 전략을 모두 통합해서 보자면 아래 이미지와 같다.

(출처: https://ddongwon.tistory.com/87)

profile
내가 꿈을 이루면 나는 또 누군가의 꿈이 된다.

0개의 댓글