Chapter 3.5 connection-oriented transport: TCP

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

Point-to-Point

TCP는 연결지향(Connection-oriented) 프로토콜이다. 즉, 1대1 연결 상태를 유지하여 통신하는 것을 말한다.

이는 point-to-point 로써, 한명의 송신자에는 한명의 수신자만 있음을 의미한다.

1대 다수의 통신인 multicasting이 불가능하다.

 

reliable, in-order byte stream

tcp는 메시지의 경계(또는 구분)가 없다.

신뢰적인 순서 보장 스트림이 있다(reliable, in-order byte stream)

즉, Application에서 준 데이터는 순서대로 정확히 전달된다.

 

pipelined

TCP 혼잡(congestion)과 flow control(흐름 제어)에 따라 Window Size를 설정한다.

 

full duplex data

동일한 연결에서 양쪽에서 보내는 것과 받는 것이 동시에 가능하다.

-전이중 방식

MSS(Maximum Segment Size) : segment에 담길 수 있는 최대 data 양

Link layer의 가장 큰 frame에 의해 결정된다.

- Segment에서 layer data의 크기 제한

- 사이즈가 큰 파일을 전송할 때, MSS 크기로 파일을 자름.

 

Connection-oriented

3-way handshake를 수행하며, 상호 간에 통신을 초기화 하는데

Window Size, buffer 할당, 순서번호(seq#) 등이 결정된다.

양 side에 3개의 요소가 생긴다.

( buffer, variable, socket connection)

 

 

flow controlled

송신자는 수신자가 받을 수 있을 만큼만 보내고 receiver가 전송량을 통제한다.


TCP segment structure

source port, dest port : 출발지, 목적지 포트로 다중화/역다중화에 사용

---

sequence number : 순서번호 필드

---

acknowledgement number : 확인 응답번호 필드 ( 500 일 경우, 499까지는 받았다는 뜻)

---

URG : 일반적으로 사용하지 않는다(generally not used)

ACK

PSH : 일반적으로 사용하지 않는다(generally not used)

R(RST) : reset

S(SYN) : sync

F(FIN) : finish

receive window :수신측이 갖고 있는 buffer size. flow control 시 사용

---

checksum : 체크섬 오류 검출

Urg data pointer : 현재 사용하지 않음

---

otions

---

application data


TCP seq numbers, ACKs

seq number : Segment의 첫 번째 byte의 stream에서의 byte 순서 번호를 의미.

   예) 데이터 스트림이 500,000byte이고, MSS가 100일 경우 500개의 Segment가 생성.

        이 때, 첫 번째 Segment의 seq number은 0,

        두 번째 세그먼트의 순서번호는 1000

   

ACKs : 다음에 받을 것으로 예상되는 seq number

         -TCP는 오직 첫 missing byte만을 ACK 하기 때문에 Cumulative ACK 가 가능하다.

  예) 수신자가 0~535byte를 포함하는 Segment와 900~1000byte를 포함하는 Segment를 수신받고

       536~899byte는 수신 받지 못했을 경우, 536번째 byte를 수신하기 위해

      다은 세그먼트 확인 응답 번호 필드(Acknowledgement num)를 536으로 지정한다.

 

Q. 순서가 틀린 Segment를 수신한 경우는?

- 특별히 정해둔 규칙이 없음.(프로그래머의 마음대로)

- 즉시 버리거나 ,buffering 하는 방법이 존재

 

 

위 그림에서 호스트 A는 seq num가 42, 호스트 B의 seq num은 79이다.

호스트 A는 42번째 byte를 보내면서 79번째 byte를 호스트 B에게 요구한다.

호스트 B는 79번째 byte를 보내면서 다음으로 원하는 byte인 43번째 byte를 호스트 A에게 요구한다.

 

마지막에 data가 없는데 Seq num을 가지는 경우는 TCP header에는 seq number field를 채워야하기 때문에

ACK로 온 number을 Seq number에 넣어 보낸 것이다.

 


TCP round trip time, timeout

tcp의 timeout은 어떻게 설정할까?

- RTT(round trip time) 보다는 길어야 한다.

- 너무 짧을 경우 : 불필요한 재전송이 일어난다.

- 너무 큰 경우 : Segment 손실에 대한 대응이 늦어진다.

 

RTT(round trip time)의 측정 방법

- Sample RTT : segment가 보내진 후로부터 해당 segment의 ACK가 도착하기까지 걸린 RTT.

재전송된 Packet은 RTT를 측정하지 않는다. 

Estimated RTT는 매우 smooth하게 변경된다. 변경폭이 크지 않다.

Sample RTT를 통해 평균을 내어 Estimated RTT를 측정한다.

 

Exponential weighted moving average를 사용한다.

 

a(알파)값은 대략 0.125로 잡는다.

 

RTT 추청지 예제

timeout 설정

- Estimated RTT보다 약간의 여유값을 더한 값으로 설정

- Dev RTT - Sample RTT 와 Estimated RTT의 차에 의해 결정된다.

   : Sample RTT가 Estimated RTT로부터 얼마나 벗어나는지에 대한 예측

이때, b(베타)의 권장값은 0.25이다.

 

이제 이 Estimated RTT와 Dev RTT를 이용해 timeout interval을 계산한다.

 

실제 timeout 설정

 

Chapter 3.4 principles of reliable data transfer

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

Pipelined protocols

이전 포스트에서 rdt 방식은 stop-and-wait 으로 진행됨으로 굉장히 효율이 떨어진다는 것을 알았다.

효율을 높이기 위해서 파이프라이닝 방식을 사용한다. 

컴퓨터 구조를 공부한 적이 있는 사람이라면 이 그림이 익숙할 것이다.

 

파이프라이닝이란 무엇일까? 단순히 말해 여러 일을 동시에 처리하는 것이다.

4명의 사람이 자신의 옷을 세탁한다고 가정하자. 

 

세탁기에 옷을 넣고 세탁 -> 건조기에 옷을 말림 -> 옷을 갠다 -> 옷장에 보관

 

위 그림에서 이 하나의 과정은 한 번 하는데 2시간이 걸린다.

A, B, C, D 라는 사람이 이 일을 각각 하나씩 하는데 서로의 작업이 전부 끝날 때까지 기다렸다가

자신의 일을 한다고 가정하면 저녁 6시에 시작한 일이 무려 새벽 2시가 되어서 끝이 난다.

 

이와 반대로 앞 사람이 끝난 작업을 다음 사람이 바로 하게 되면 

모든 작업이 9시 30분에 끝나게 되니 얼마나 효율적인가?

 

이것이 바로 파이프라이닝이다. 

 

한번의 한 개씩 패킷이 목적지에 도착하면 목적지로부터 돌아오는 ACK를 기다리지 말고

여러 패킷을 전송할 수 있도록 하는 것이다.

 

파이프라이닝을 이용하여 이전보다 이용률이 무려 3배나 좋아진다.

 

하지만 파이프라이닝을 사용하려면 다음 사항을 고려해야 한다.

  • 순서 번호(sequence number) 범위의 증가 : 여러 패킷을 보낼 수 있게 됨으로써 0과 1 만으로는 부족
  • 버퍼(buffer)의 필요 : 송신측과 수신측은 여러 패킷을 담을 수 있는 버퍼가 필요
  • 파이프라인의 대한 오류 : 파이프라인에서 발생하는 패킷 손실 , 지연 패킷에 대한 처리 방식이 필요

이 고려 사항들은 어떻게 해결해야 할까?


GBN(Go-Back-N) : N에서부터 다시

GBN 프로토콜은 수신자 측에서 지금까지 성공적으로 받은 패킷 순서 번호에 대한 ACK를 전송하며,

순서에 맞지 않는 패킷은 성공적으로 수신한 것으로 간주하지 않는다.

따라서 송신자는 ACK를 받지 못한 가장 오래된 패킷부터 다시 재전송하게 된다.

 

왼쪽에서 오른쪽으로 쌓이는데 가장 왼쪽에 있는(=가장 오래된) 노란색 패킷부터 재전송한다.

이때 이 패킷들은 최대 허용수(N=window size) 보다 크지 않아야 한다.

 

  • 수신자는 누적된(cumulative) ACK만 전송 (수신된 패킷들의 순서번호 사이에 갭이 있으면 ACK는 응답하지 않음)
  • 수신자는 비순차(out of order) 패킷으로 버퍼링하지 않는다.
  • 송신자는 ACK받지 못한(문제가 생긴) 가장 오래된 패킷부터 모두 재전송 (타이머가 1개)

 

위 그림의 sender에게는 4개의 저장 공간이 있다.

처음에 sender는 패킷을 pkt0부터 pkt3 까지 총 4개를 보내고 ACK를 대기한다.

 

pkt2의 경우, 중간에 손실이 있어서 receiver는 이를 받지 못했기 때문에 ACK2를 보내지 않고

ACK1을 보내어 자신이 pkt2를 제대로 받지 못했음을 알린다. 

 

더보기

※이전까지 순서 번호가 0과 1만 있었는데 이제 2도 생겼음을 눈치챘는가?

앞에서 pkt0, pkt1의 긍정응답인 ACK0과 ACK1은 슬라이딩 되어 다음 패킷인 pkt4와 pkt5를 보낸다.

 

앞에서 pkt2를 받지 못했으니 이후의 패킷인 pkt3, pkt4, pkt5는 잘 도착하여도 이를 버리고(discard)

sender에게 ACK3 ACK4 ACK5 가 아니라 모두 ACK 1을 보낸다.

 

즉 pkt2가 다시 제대로 receiver에게 전달되어 ACK2가 sender에게 가야만 뒷 작업인

pkt3, pkt4, pkt5의 패킷을 받을 수 있는 것이다.

 

패킷 pkt3, pkt4, pkt5(후배님들)는 자신들보다 먼저 보내졌던 pkt2(선배님)을 받지 못하였다고

온전히 받아졌음에도 불구하고 버려졌다(discard).

 

굉장히 비효율적이지 않은가?

이것을 해결하기 위해 SR 이 등장했다.


SR(Selective Repeat) : 선택된 것만 다시

SR은 받는 쪽에도 저장 공간을 만드는 것이다.

따라서 불필요한 재전송 즉, 이미 온전히 받은 패킷의 재전송을 피할 수 있다.

 

(a)는 sender의 저장 공간, (b)는 receiver의 저장 공간이다.

receiver의 저장 공간을 보면 sender로부터 문제 없이 도착한 패킷은 자주색,

그렇지 못한 패킷은 회색으로 표시가 되어 있다.

 

동작이 어떻게 이루어지는지 살펴보자.

 

아까와 마찬가지로 sender는 패킷을 pkt0부터 pkt3 까지 총 4개를 보내고 ACK를 대기한다.

pkt2가 손실이 일어나 receiver는 이를 받지 못했지만 

pkt3를 받았을 때 순서 번호 3에 맞춰 ACK3를 보낸다. 

pkt4와 pkt5를 받은 receiver 또한 ACK4 ACK5를 보낸다.

pkt2의 긍정응답인 ACK2를 대기하고 있던 타이머가 초과(time out)되어 pkt2를 재전송한다.

 

이러한 SR에도 문제점이 있는데 아래를 살펴보자


이 그림에서 pkt0, pkt1,pkt2의 긍정응답인 ack0, ack1, ack2가 sender에게 가는 도중 손실이 되었다.

타임 아웃 이후에 pkt0을 재전송하게 된다면

이것이 순서번호가 0인 첫번째 패킷이 재전송된 것인지,

아니면 이동한 슬라이드 안의 순서번호가 0인 새로운 패킷이 온 것인지

알 방법이 없다.

 

이를 해결하기 위해선 위에서 나온 N(=window size)의 크기 조절이 중요하다.

N은 sender의 순서번호 공간 크기의 절반보다 작거나 같아야한다.

 

예를 들어 순서번호가 0,1,2,3 총 4개라면 N은 1~3의 크기를 가져야 한다.

 

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)은 다음 포스트에서 이어서 설명하겠다.

flutter의 BLoC 패턴

 

플러터에서도 Provider나 stream을 제공하는데 Notification Center은 Swift에서 비슷할 역활을 한다고 보면 된다. 아직 Design Pattern에 대해 수박 겉햟기 식으로만 보았는데 아마 이것들이 base일 것이다.

 


 

NotificationCenter은 등록된 Observer에게 동시에 Notification을 전달하는 클래스이다. 특정 객체가 NotificationCenter에 등록된 Event를 발생시키면(Post라고 한다.) NotificationCenter에 등록된 Observer들이 Event에 대한 행동을 처리하고 이를 대기한다. 여기서 전체 흐름은 동기적으로 흘러가는데, 이를 aysnc으로 사용하려면 NotificationQueue를 사용하면 된다.

 

 

사용 방법은 아래와 같다.

 

Post

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func postBtnTapped(_ sender: Any) {
        NotificationCenter.default.post(
            name: NSNotification.Name(rawValue: "PostButton"), 
            object: nil)
    }
}

코드가 굉장히 직관적이다.

버튼을 Tap하였을 경우, NotificationCenter에 "PostButton" 이라고 하는 Event를 등록하고 이를 발생시킨다는 뜻.

이때, object 파라미터를 통해 Event를 발생시킬 때 특정 Object를 같이 넘길 수 있다. 

 

 

Event...너를 지켜보고 있다

 

Observer

class ObserverOneViewController: UIViewController {

    @IBOutlet weak var hiddenLabel: UILabel!
    override func viewDidLoad() {
        super.viewDidLoad()
        
        NotificationCenter.default.addObserver(self, 
            selector: #selector(showLabel), 
            name: NSNotification.Name(rawValue: "PostButton"),
            object: nil)
    }
    
    @objc func showLabel(){
        hiddenLabel.layer.isHidden = false
    }
}

NotificationCenter에 Observer을 추가하는 것을 한눈에 찾아볼 수 있다.

여기서 이 객체(self)를 name(위에서 설정한 "PostButton"이라는 Event가 발생했을 경우,

selector에 지정되어 있는 함수(showLabel)를 동작시킨다.

 

추가의 방법에는 두가지의 방법이 존재한다. 위 예제에선 후자

func addObserver(forName name: NSNotification.Name?, 
                 object obj: Any?, 
                 queue: OperationQueue?, 
                 using block: @escaping (Notification) -> Void) -> NSObjectProtocol

NotificationQueue를 만들어서 이걸 사용하고 싶다면 필요할 경우 Main Thread에서 사용하도록 잘 처리해야 한다.

 

func addObserver(_ observer: Any, 
                 selector aSelector: Selector, 
                 name aName: NSNotification.Name?, 
                 object anObject: Any?)

 

 

중요한 것은 생성한 Observer는 추후에 제거를 해주어야 하는데,

이는 flutter에서는 생성한 스트림을 close해주는 것이 있었는데 비슷한 이치이다.

func removeObserver(_ observer: Any, 
                    name aName: NSNotification.Name?, 
                    object anObject: Any?)
                    
//NotificationCenter.default.removeObserver(self, name: .changeLabel, object: nil)

 

제대로 제거해 주지 않으면 Observer가 소속된 오브젝트가 해제 되었을 때, Notification 이 전달되면 문제가 생긴다.

Chapter 3.3 UDP

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

UDP : User Datagram Protocol [RFC768]

User Datagram Protocol의 축약어로 컴퓨터가 다른 컴퓨터와 데이터 통신을 하기 위한 규약(프로토콜)의 일종이다. 

UDP는 TCP와 달리 Transport-layer Protocol이 할 수 있는 최소 기능인 Multiplexing과 Demultiplexing, 오류 검사(checksum)만으로 동작한다. 

 

UDP는 Segment를 송신하기 전에 TCP 처럼 상대방과의 연결이 잘 이루어졌는지 확인(3-way handshake) 하지 않는다. 

 

더보기

* TCP 3-way Handshake 란?

TCP 3-way handshake의 과정

 

TCP는 장치들 사이에 논리적인 접속을 성립(establish)하기 위하여 3-way handshake를 사용한다.

TCP 3-way handshake는 TCP/IP프로토콜을 이용해서 통신을 하는 응용프로그램이 데이터를 전송하기 전에 

먼저 정확한 전송을 보장하기 위해 상대방 컴퓨터와 사전에 세션을 수립하는 과정을 의미한다.

 

이러한 이유로 비연결형(connectionless)이라고 한다.

connectionless:

no handshaking between UDP sender, receiver

each UDP segment handled independently of others

UDP의 사용

  • 도메인 이름 서비스(DNS)
  • IPTV
  • 음성 인터넷 프로토콜(VoIP)
  • TFTP, RTSP, RIP, OSPF
  • NTP(Network Time Protocol)
  • IP Tunnel
  • Online game(Client - Server)

UDP를 사용하는 애플리케이션 계층 프로토콜의 하나인 DNS의 경우, 브라우져가 서버와의 데이터 교환을 위해서는 DNS를 통해 서버 컴퓨터의 IP주소를 알아낸다.

 


UDP 특징

1. Finer application-level control over what data is sent, and when

(애플리케이션 레벨이 데이터 송신에 대해서 정교한 제어를 할 수 있다.)

UDP의 경우, 애플리케이션 프로세스가 데이터를 UDP에게 전달하자마자 UDP는 데이터를 Segment로 만들고 즉시 아래의 Network-layer로 전달한다. 

 

이에 반해 TCP는 혼잡제어(congestion control) 매커니즘을 가지고 있어서 이를 이용해 호스트들 사이의 링크가 혼잡해지면 TCP 송신자를 조절한다. 또한, 목적지로부터 Segment의 수신 여부 확인(ACK)응답을 받기 전까지 계속해서 Segment를 재전송하게 된다.

 

TCP의 이러한 특징은 실시간(Real-time) 어플리케이션(예: 라이브 방송)에서는 적합하지 않다. 영상의 픽셀에서 점(.)의 크기만한 픽셀을 받지 못하였다고 버퍼링에 걸리는 것은 매우 비효율적이기 때문이다. 최소한의 전송률만을 요구하고 지나지게 지연되는 전송은 적절하지 않다.

 

이러한 실시간 어플리케이션의 경우 UDP를 사용한다면 기본 세그먼트 전달 외에 필요한 추가 기능을 구현할 수 있다. 

 

2. No connection establishment(연결 설정이 없다)

위에서 설명하였 듯 UDP는 connectionless 이다. TCP처럼 데이터 전송을 시작하기 전에 3-way handshake를 사용하지 않는다.

 

3. No connection state(연결 상태가 없다)

TCP는 종단 시스템(End system)에서 연결 상태를 유지한다. 이 연결상태에는 수신버퍼, 송신버터, 혼잡제어 파라미터, sequence number, ACK number 가 포함된다. 허나 UDP는 연결 상태를 유지하는 기능의 프로토콜이 아니므로 이 파라미터 중 아무것도 기록하지 않는다. 따라서 일반적으로 특정 어플리케이션에 할당된 서버는 애플리케이션이 TCP보다 UDP가 더 많은 클라이언트를 수용할 수 있다.

 

4. Small packet header overhead(작은 패킷 헤더 오버헤드)

TCP가 Segment 마다 20byte의 헤더 오버헤드를 갖는 반면, UDP는 겨우 8byte의 오버헤드를 가진다.

 


UDP segment header

애플리케이션 데이터는 UDP 세그먼트의 data 필드에 위치한다. 즉, 실질적으로 보내려고 하는 data가 위치하는 곳이다.

그 위에 위치한 4가지의 필드들은 이 data를 주고 받기 위한 최소한의 형식적인 필드이다. (최소 기능으로만 동작한다는 것은 위에서도 언급하였음) 체크섬(checksum)은 Segment에 오류가 발생했는지 검사하기 위해 수신 호스트에 의해 사용된다.

 

TCP 헤더와 UDP 헤더의 차이

한눈에 봐도 UDP가 좀 더 가벼워(=만들기 간단, 빠름) 보인다.


UDP checksum

 

체크섬은 오류를 검출하기 위해 사용한다.

간단히 말해서 지금 받은 데이터가 출발지로부터 목적지까지 이상없이 잘 받아졌는가 확인할 때 사용한다는 것이다.

 

송신측

송신측에서 UDP는 세그먼트를 16byte로 나누어 서로 더하고

treat segment contents, including header fields,  as sequence of 16-bit integers

이에 대하여 1의 보수를 수행하며, 덧셈 과정에서 발생하는 overflow는 자리 올림을 한다.

checksum: addition (ones complement sum) of segment contents

이 결과는 UDP 세그먼트의 체크섬 필드에 삽입된다.

sender puts checksum value into UDP checksum field

 

수신측

수신측에서는 체크섬을 포함한 모든 16비트 워드를 더한다. 위 그림에서 만약 패킷에 어떤 오류도 존재하지 않는다면 소신자에서의 합은 1111 1111 1111 1111 이 될 것이다. 만약 비트 중 하나라도 0이 있다면 (가령 1011 1111 1111 1111) 패킷에 오류가 발생했음을 알 수 있다.

 

 

 

문제가 있지만 어떻게 할 수가 없네?

UDP는 오류가 있는지 없는지 검사를 하지만 오류가 있다고 해서 어떠한 일도 하지 않는다.

+ Recent posts