Chapter 3.4 principles of reliable data transfer

Computer Networking : A Top Down Approach 7th Edition, Global Edition Jim Kurose, Keith Ross

슬램 덩크

 

데이터를 주고 받는 과정에서는 상대방에게 잘 전달되었는지가 단연 중요하다고 할 수 있다.

 

만약 제대로 통신이 되지 않는 채널, 즉 비신뢰적인(unreliable) 채널에서 데이터를 전송한다면

전송 도중에 에러가 나거나 분실, 순서가 지켜지지 않는 등의 문제가 발생할 수도 있다. 

위의 문제를 해결한다면 '신뢰성 있다' 라고 할 수 있을 것이다.

 

데이터 전송에서는 신뢰적인 데이터 전송(reliable data transfer)이 중요하며,

Transport layer의 주된 역할이 바로 신뢰성이 있는 데이터의 전송이다.

 

 

 

 

위 그림 (a)에서처럼 데이터를 transport layer에 있는 reliable(신뢰성 있는) 채널을 통해 목적지 프로세스까지

별다른 문제없이 데이터를 전송할 수 있을 것이다.

 

허나, 실제로 이를 구현하는 측면에서 보았을 때, (b)와 같이 transport layer 아래 계층(그림에서는 따로 표시가 없지만 이것은 Network layer을 말함)에는 unreliable(신뢰하지 못하는) 채널이 있으므로

신뢰성이 있는 채널을 구현하기가 어려워진다.

 

그렇다면 신뢰성 있는 채널을 만들려면 어떻게 해아할까?


신뢰적인 데이터 전송 프로토콜을 만들기 위해서는 가장 간단한 상황부터 복잡해지는 상황까지의 경우를 생각해보자.

 

  • 완벽하게 신뢰적인 채널에서 신뢰적인 데이터 전송 => rdt1.0
  • 비트 오류가 있는 채널에서 신뢰적인 데이터 전송 => rdt2.0 , (rdt2.0의 수정된 버전 : rdt2.1 , rdt2.2)
  • 비트 오류 & 손실이 있는 채널에서 신뢰적인 데이터 전송 =>rdt3.0

rdt라는 단어가 계속해서 쓰이는데, 이는 reliable data transport 의 약자이다.

위 상황을 설명하기 위해 우선 단방향 전송이라고만 가정한다.

또한 송신자와 수신자의 동작을 유한 상태 머신(Finite State Machines : FSM)으로 표현할 것이다.


rdt1.0 : 오류 없는 경우

우선, 첫번째 경우인 rdt1.0을 보자.

 

송신자와 수신자가 분리된 유한 상태 머신을 가지고 있다.

 

데이터 전송에 에러가 발생하지 않는 가정 하의 전송이기 때문에 그저 간단한 과정만 거친다.

 

Sender

  1. 송신측(sender)의 rdt_send 함수가 상위 계층에서 오는 데이터를 받음.
  2. make_pkt 함수가 데이터를 패킷으로 만들고 packet 변수에 저장.
  3. udt_send 함수가 packet 변수에 담긴 패킷을 채널로 내려보내면서 전송.

Receiver

  1. rdt_rcv 함수가 하위 채널로부터 패킷을 수신한다.
  2. extract 함수로 패킷에 있는 데이터를 추출해낸다.
  3. deliver_data 함수로 추출한 데이터를 상위 계층으로 전달한다.

rdt1.0의 경우 하위 채널이 완전히 신뢰할 수 있기에 수신자 측에서 잘 받았다고 피드백을 할 필요가 딱히 없다.

 


rdt2.0 비트 오류가 있는 경우

rdt1.0 보다 복잡해졌기 때문에 (비트 오류가 있음) 2.0이라고 불린다. 

우선 rdt2.0을 설명하기 전에 현실세계에서 일어날 법한 전화 속 대화를 생각해보겠다.

 

소풍 가고 싶다... 코로나 ㅅㅂ

A : "오늘 오후 1시에 공원에서 만나는 거 알지?"
B : "알지! 뭐 챙겨야 하는 거 있어?"
A : "돗자리 들고와!"

            -잡음 발생-

B : "뭐 들고 오라고?"
A : "돗자리!"
B : "알았어! 돗자리 챙길께~"

위 대화에서 B는 A가 말한 내용을 정확하게 듣지 못하여(=수신에 오류가 남) 무엇을 들고

오라고 하는지 다시 한번 알려달라는 재전송을 요청했다.

여기서 "뭐 들고 오라고?" 는 A에게 NAKs(negative acknowledgements) 부정 확인 응답을 보낸 것과 같다.

 

NAKs를 받은 A는 "돗자리!" 라고 다시 말해주고 그제서야 자신이 돗자리를 챙겨야 한다는 것을 깨달은 B는 A에게

"알았어! 돗자리 챙길께~" 라는 ACKs(acknowledgements) 긍정 확인 응답을 보낸다.

 

통신에서 이렇게 재전송을 기반으로 하는 신뢰적인 프로토콜을 

자동 재전송 요구 프로토콜 ARQ(Automatic Repeat reQuest)라고 한다.

 

위 대화로 계속 설명을 이어나가겠다.

 

ARQ 프로토콜이 이루어지려면 우선, B가 제대로 듣지 못했다는 것(오류가 발생했다는 것)

스스로 파악할 수 있어야 한다.

 

예를 들어 돗자리 들고 오라는 A의 말을 잡음으로 인해 잘못 들었음에도

"대충 잠자리 채 들고 오란 말인가?" 라고 생각해(=오류 검출 능력이 없음) 돗자리가 아닌 잠자리 채를 들고 간다면

아마 도시락을 싸온 A에게 싸대기를 맞을 것이다.

 

통신에서는 이러한 오류가 발생했음을 검출해내기 위해 헤더에 설정한 체크섬(checksum)을 사용한다.

자신이 정확하게 듣지 못했음을(=오류가 발생했음을) 파악한 B는 A에게 다시 한번 말해달라고 요청한다.

이때, 수신자인 A는 B의 "뭐 들고 오라고?" 라는 피드백을 받을 수 있어야 한다.

그래야 A가 "제대로 못들었구나" 라고 생각해 다시 한번 말해 줄 수 있기 때문이다. (재전송)

 

rdt1.0과 달리 rdt2.0에서 데이터 송신 측인 sender에서 checksum이라는 단어가 보일 것이다.

 

Sender

  1. 송신측(sender)의 rdt_send 함수가 상위 계층에서 오는 데이터를 받음.
  2.  sndpkt 라는 변수에 make_pkt 함수를 이용해 체크섬이 포함된 패킷을 담음. (receiver가 오류를 파악할 수 있게)
  3. udt_send 함수가 sndpkt 변수에 담긴 패킷을 채널로 내려보내면서 전송.
  4. 이후 상태 변화가 일어나 receiver 측으로 부터 ACKs 나 NAKs가 올 때까지 대기한다. (stop-and-wait) 중요
  5. NAKs 를 받았을 경우 udt_send 함수로 다시 재전송을 하고 ACKs 받으면 1번 상태로 돌아간다. (둘 중 하나를 받기 전까지 4번에서 계속 대기하고 있음)

 

Receiver

  1. rdt_rcv 함수가 하위 채널로부터 패킷을 수신하는데 corrupt 함수로 오류를 확인해서 오류가 발생했을 시 sender에게 NAKs를 보낸다.
  2. 오류가 발생하지 않았을 경우에는 extract 함수로 패킷에 있는 데이터를 추출해낸다.
  3. deliver_data 함수로 추출한 데이터를 상위 계층으로 전달한다.
  4. sender에게 ACKs 를 보낸다. ("무사히 받아서 처리했다 ><")

어려운게 있는가? 


rdt2.1

그런데 만약 recevier가 sender에게 보낸 NAKs 나 ACKs가 손상이 나면 어떻게 할까?

NAKs도 ACKs도 오지 않는 상황이라면 sender는 이를 계속 기다리는 일도 발생할 것이다.

중복으로 송신하면 sender가 중복으로 수신하는 상황도 있을 것인데...

 

이를 해결하기 위해 rdt2.0의 수정된 버전인 rdt2.1에서는 데이터 패킷에 순서번호(sequence number)를 추가했다.

순서번호를 추가하여 뭐가 어떻게 달라지는지 유명한 애니메이션인 스폰지밥으로 예를 들어보자.

 

주문을 받는 징징이

징징이 : 게살버거 244개 주문 들어왔어
스폰지밥 : 주문 확인! 게살버거 244개 만들께!

-주방이 시끄러워서 데스크까지 전달이 안됨-

징징이 : 스폰지밥! 게살버거 244개 주문 들어왔어!!
스폰지밥 : (아까랑 똑같은 주문인데) 그럼 게살버거를 총 488개나 만드는거야? 와~우

sender인 징징이는 receiver에 해당하는 스폰지밥에게 데이터(게살버거 244개 주문)를 전달하였고

스폰지밥이 있는 주방에서 주문 확인을 받았다는 대답을 들으려 하였다.

하지만 가게가 너무 시끄러워서 주문 확인(ACKs)이라는 대답을 듣지 못한 징징이는 한번 더

똑같은 데이터(게살버거 244개)를 보냈고 이를 두 번이나 들은 스폰지밥은 개살버거를 손님이 주문한 양보다

더 많이 만드는 사고를 저지를 수 있다.

 

열심히 만드는 스폰지밥

이렇게 NAKs 나 ACKs를 받지 못한 sender는 계속 대기하지 않고 똑같은 데이터를 한번 더 

receiver에게 전달한다. 이를 받은 receiver은 중복으로 동일한 데이터를 받게 되는데 이 데이터가

재전송으로 인한 중복 패킷인지 새로운 패킷인지를 확인할 때 위에서 언급한 순서 번호가 사용된다.

 

순서 번호는 0과 1을 번갈아가면서 사용하는데, 만약 같은 순서 번호가 연속으로 수신 된다면 해당 데이터는

재전송으로 인한 패킷임을 확인할 수 있는 것이다!

 

              0-> 1-> 1-> 1-> 0->1 -> 0 -> 0->0 

 

 

rdt2.1 의 sender

  Sender

수신된 ACKs/NAKs의 오류 여부에 대한 상태가 추가되어 rdt2.0 보다 상태 수가 2배 증가하므로

총 4개의 상태를 가진다.

 

 

rdt2.1의 recevier

Receiver

들어온 패킷의 중복 여부를 확인하고 마지막으로 보낸 ACKs/NAKs를 sender가 잘 받았는지 확인한다.


위에서 설명한 rdt2.1과 기능은 동일하나 NAKs를 사용하지 않고 ACKs만 사용하는 모델이 바로

rdt2.2

NAKs를 보내는 대신, 가장 최근에 잘 받은 패킷에 대한 ACKs를 보냄으로써

NAKs를 보내는 것과 같은 기능을 수행할 수 있다.

rdt2.2

위 그림의 sender의 FSM에서 isACK(rcvpkt,1) 와 isACK(rcvpkt,0)에 빨간색으로 강조가 된 것을 볼 수 있다.

즉, ACKs가 제대로 와야 다음 상태로 갈 수 있는 것이다.

 

"ㅅㅂ 무슨 말이야?" 라고 생각하면 밑 그림을 보자.

 

친절한 그림 설명^^

어려운게 있는가? 


마지막으로 비트 오류 & 손실이 있는 채널에서의 신뢰적인 데이터 전송은 어떻게 해야 할까?

rdt3.0 : 비트 오류와 손실이 있는 경우

비트의 손실이 추가되었다. 비트의 손실은 어떻게 할까? 

 

별 것 없다. 마찬가지로 손실이 일어난 패킷을 또 다시 보내면 된다!

또 다시 보내는 것은 위에서 계속해서 기능을 추가했던 일이 아니였던가?

 

타임 아웃!

rdt3.0에서는 또 하나의 기능을 더 추가하는 데 그것은 바로 타임 아웃 타이머이다.

 

rdt 2.2에서 쉽게 설명한 그림 설명을 다시 보자.

비트 오류가 일어났다면 이를 감지하고 ACK0을 전달하여 다시 요청을 했다.

그런데 만약 ACK0이라는 데이터의 비트가 손실이 일어나면 어떻게 할까?

 

ACK loss

 

sender가 이를 무한정 기다리는 일이 생간다. 

그렇기에 일정 시간이 지나도 ACK 응답이 오지 않으면 그냥 새로 한번 더 보내는 것이다!

물론 처음에 sender가 보낸 hello 1 의 패킷이 receiver에게 가는 도중 손실이 일어나도

ACK 0을 보내지 못하는 것은 같으므로 시간 초과가 되면 어쨋거나 sender은 한번 더 똑같은

패킷인 hello 0을 보낼것이다.

 

그런데 만약 receiver가 보낸 패킷이 손실된 것이 아니라 어떤 이유로 시간 초과보다 늦게

도착하는 경우는 어떻게될까?

 

이미 패킷이 중복됬을 경우를 처리하기 위한 순서 번호(sequence number) 기능이 있으니 

문제 없이 작동한다!


rdt 3.0 까지 발전시켜나감으로써 신뢰성 있는 통신이 가능해졌다. 

이론적으로 보았을 때도 굉장히 잘 작동할 것 같다. 

뭐 잘 작동하는 건 좋다 이거야 그런데 뭔가 굉장히 느릴꺼 같지 않은가?

 

이유는 전송 후 대기(stop-and-wait) 하는 방식이기 때문이다.

 

예를 들어보자. 

 

광활한 미국의 대륙이다. 황단 시 4000~4500km의 거리를 가야한다.

미국 서부에서 동부로 통신을 하려고 한다.

  • 두 지점 사이의 RTT : 30msec
  • 1Gbps 전송률(R)을 가진 채널로 연결
  • 헤더와 데이터를 포함하여 패킷 당 1000byte(=8000비트) 의 패킷크기(L)를 가진 패킷을 전송

※RTT : Round Trip Time으로 패킷이 목적지로 도착하고, 목적지에서 보낸 ACK가 출발지까지 돌아오는데 걸리는 시간

 

위와 같은 상황에서 8000비트를 미국 대륙의 끝과 끝으로 보내는데 시간이 얼마나 걸릴까?

 

 

계산 결과 0.000008 초가 걸린다.

 

위에서 두 지점 사이의 RTT는 30msec(=0.03초) 라고 하였음으로 이 시간도 더하면

8000비트를 보낸 출발지가 목적지가 보낸 ACK 를 받으려면 

30.008msec(=0.030008초)가 걸림을 계산할 수 있다.

 

총 30.008msec의 시간 동안 T(=L/R) 0.008msec 의 데이터만 전송한 것이니 이용률을 계산하면

전체 시간의 0.00027이다. stop-and-wait 프로토콜의 문제점은 이처럼 효율이 좋지 않다는 것이다.

 

효율을 높히려면 어떻게 해야할까?

 

ACK가 도착할 때까지 대기하지 않고 여러 패킷을 전송하는 것이다!

 

sidongmen

파이프라인(Pipeline)은 다음 포스트에서 이어서 설명하겠다.

+ Recent posts