상세 컨텐츠

본문 제목

네트워크 프로그래밍 - Window, Flow Control

Computer Science/Network

by 2021. 10. 10. 17:42

본문

반응형

Time line for a common scenario

 서버와 클라이언트가 ESTABLISHED status인 상황에서 Data가 전송되어 read의 return값이 0이 아닌 값이 반환되다가 상대방이 FIN을 보내게 되면 read의 return 값이 0이된다. 서버측에서 FIN + ACK을 보내고 클라이언트가 그에 대한 ACK을 보냄과 동시에 TIME-WAIT 상태로 들어간다. 

 

Denying a connection

클라이언트가 SYN을 보냈을 때 서버가 연결을 거절하는 경우에는 control field의 RST을 보내서 연결 요청을 거절한다. 

Aboring a connection

FIN과 FIN + ACK이 오가는 과정없이 연결 중에 바로 CLOSED상태로 들어가는 방법도 있다.

클라이언트가 RST + ACK을 보내면 FIN, FIN + ACK 과정 없이 바로 CLOSED 상태로 들어간다. 

 

보통 FIN을 클라이언트가 먼저 보내 연결 종료를 요청한다. 또는 강제종료를 시키면 FIN을 보내고 WAIT상태로 들어간다. 

./hserver를 통해 서버를 열고 나서 서버를 닫고, 즉시 다시 서버를 열려고 하는 상황에서 서버의 포트번호가 이전에 열었던 포트번호와 같으면 보통 bind 에러가 난다. 포트번호를 반납하지 않았기 때문이다. 

 

Windows in TCP

Send window in TCP

위 그림은 send window에 대한 것인데 이것이 왜 필요한지부터 알아보자.

TCP에서 Data를 보내고 응답으로 ACK을 받음으로써 Data의 정상도착을 알리는 방식을 Stop & Wait 방식이라고 부른다. 데이터를 보내고 해당 데이터를 받았음을 확인하는 간단한 방식이지만 비효율적이다. 

한 데이터를 전송하고 도착하는 과정을 끝내야지 비로소 다음 데이터를 전송하는 방법이 비효율적인 것인데, 한번에 여러 개를 보내면 훨씬 효율적일 것이다. 한번에 여러개를 보내기 위한 크기의 기준을 window size라고 부른다. 

 첫 번째 데이터를 보내고나서 그 데이터에 대한 ACK을 받을 때 까지 다른 데이터를 얼마만큼 보낼 수 있는지 maximum 양이 window size이다.

 

window는 sender쪽의 window와 receive쪽의 window가 각각 존재하고, 양 방향에서 sender window와 receive window가 하나씩 총 2개의 window가 있다. 

Send window

데이터를 보내는 쪽이 sender, 받는 쪽이 receiver라고 하자. sender와 receiver는 둘 다 클라이언트도 서버도 될 수 있다.

Send window에서 Outstanding bytes는 이미 sender가 보냈지만 ACK이 오지 않은 데이터들이다. 

Bytes that can be sent 부분은 보낼 수 있지만 아직 보내지 않은 데이터들이다. 

send window는 위에서 언급한 stop & wait방식이 한 번의 전송후 ACK이 올 때까지 하나의 데이터만 보내고 기다리는 것이 비효율적이기에 window size만큼의 데이터를 ACK이 오기전에 보낼 수 있는 방식이다. 

window size의 크기만큼 얼마나 보냈고 얼마나 더 보낼 수 있는가를 pointer 변수로 관리한다.

Receive window

위 직선그림은 동그란 도넛 모양으로 표현했던 receiving buffer를 막대로 펼쳐놓은 그림으로 보면 된다. 색이 칠해져있는 공간들은 Bytes received 즉 받은 데이터들이지만 프로세스가 read하지 않은 것들이다. consume은 데이터가 프로세스 read에 의해 호출되어 어플리케이션으로 올라가는 것을 의미하는데 이 과정을 대기하고 있는 데이터들이다. 

그 오른쪽 공간은 sender로부터 데이터를 더 받을 수 있는 비어있는 공간이다. 

Flow Control

여기서는 stop & wait방식에 대해서 한 번에 많이 보내기 위해 필요한 기준에 대해서 말한다.

 

위 sliding window은 검은색 화살표 방향으로 움직인다. 이 그림을 이해하기 위해 데이터를 보내는 양을 어떻게 정하는지를 먼저 알아보자.

 

sender가 receiver에게 데이터를 보낼 때 receiver의 receive buffer가 K byte라고 하면 K byte 이상의 데이터를 보내봤자 받지 못하므로 소용이없다. 따라서 sender는 receiver와 ESTABLISHED 상태가 된 직후 K byte만큼 데이터를 보낸다. 그렇다면 receive buffer의 크기는 언제 알아내는가?

 

위 그림은 hand-shaking을 하는 과정인데, 여기에서 rwnd 라는 것이 보인다. 

 

위 header의 구조를 보면 Window size가 있다. 이 window size는 receiving buffer에 있는 빈 공간을 상대방에게 알려주는 역할을 하는데 사용된다. 아래의 버퍼 그림을 보자

 

buffer에서 색이 칠해져있지 않은 비어있는 공간에 대한 정보를 receiver에서 ACK을 보낼 때, header의 window size에 실어서 보내준다. 즉 ACK이 전달되면서 동시에 rwnd의 값이 같이 전달되는 것이다. 

 

hand-shaking을 통해서 연결을 시작하기 위해 SYN을 전달하고 해당 SYN에 대한 SYN + ACK을 보낼 때 buffer의 빈 공간값에 대한 정보 rwnd가 함께 전달되는 것이다. 위 그림의 경우 현재 아무것도 담기지 않은 상태의 server측 receiving buffer의 공간이 5000byte임을 알려주는 것이다. 다시 client에서 ACK을 보낼 때 rwnd의 의미는 client의 receiving buffer의 최초 크기가 10000byte임을 의미한다.

 

rwnd에 어떻게 변하는지에 대해서 알아보자.

데이터를 전송하기 위한 buffer에 데이터를 담거나 buffe의 데이터를 어플리케이션으로 올리기 위해 write와 read함수가 사용된다. read함수의 경우 block이 되어있다가 데이터를 받을 때 block이 풀리지만, write는 다르다. 어플리케이션에서 TCP의 sending buffer에 가져다 놓는 함수가 write함수이다. write함수는 더이상 TCP의 sending buffer에 데이터를 가져다 놓을 수 없는 상태가 되면 block된다. 

 

위 그림의 번호 순서대로 보자.

  1. 어플리케이션에서 write를 통해 데이터를 sending buffer로 내려받고
  2. Receiver가 받을 수 있는 만큼 보낸다.
  3. 데이터를 보내고 Receiver에서 데이터를 어플리케이션으로 가져가는 만큼 빈 공간이 생긴다. 
  4. 생긴 빈 공간에 대한 정보를 Sender에게 알려준다.
  5. Sender는 ACK을 받고나서 보낸 데이터들이 담긴 sending buffer를 지우고 지운 만큼의 정보를 어플리케이션에 전달한다. 

sending buffer의 빈공간은 receiver로부터 ACK이 와야 생긴다. ACK이와서 receiver가 받았다는 것을 확인하면 보냈던 데이터를 지우고 지운 만큼의 분량에 대한 정보가 어플리케이션에 도달한다. 

Flow control feedback은 각각 receiver에서 어플리케이션으로 데이터를 올린만큼 생긴 빈 공간에 대한 정보가 sender로 전달되는 것, sender가 receiver에게서 ACK을 받고 buffer를 비워내 생긴 빈 공간에 대한 정보를 어플리케이션으로 전달하는 것이다. 

 

위 그림에서 1~5번의 순환하는 속도는 buffer를 얼마나 빨리 비워내느냐에 달려있다. receiver가 read하는 속도에 달려있다고 볼 수 있는데, 단순히 read작업이후 write나 printf를 호출한다면 빠르지만, 다른 I/O장치로 전달하는 등의 작업이라면 buffer를 비우는 시간이 오래걸릴 수 있다. 

 

정리하자면, flow control은 window size에 따라서 보내는 양을 조절하는 것이다. window size는 receiver의 빈공간에 대한 정보로 정해진다. 만약 receiver buffer에서 read가 덜되어 빈 공간이 남지 않았다면 ACK의 rwnd 값으로 0을 전달하여, sender에서 데이터를 보내지 못할 것이다. 

 

An example of flow control

window size가 변하는 예시를 보자

처음 연결요청으로 클라이언트에서 seqNo가 100인 SYN을 보낸다. 서버는 SYN에 대한 SYN + ACK으로 seqNo가 1000 ackNo에 101을 담아 보낸다. 여기서 ACK을 보낼 때 rwnd가 같이 간다. rwnd에는 receiving buffer의 최초값이 800이 적혀있다. 

rwnd값을 받은 클라이언트는 해당 값으로 window size가 800인 send window를 set up한다. 즉 한번에 800 byte를 보낼 수 있다는 것이다.

어플리케이션에서 200byte만 sending buffer에 담아놓고 보낸다. seqNo 101 ~ 300까지 200byte의 데이터가 전송되고, receiver에서는 800byte중 200byte가 들어왔으므로 남은 빈공간의 크기 600byte가 rwnd에 들어가서 ACK이 전달된다. 101~300을 받았으므로 ackNo는 301을 전송한다. 

 sender는 받은 ACK의 ackNo와 rwnd를 통해 새로운 window를 생성한다. ackNo가 301이므로 그 이전의 데이터들은 상상대방이 정상적으로 받은 데이터이기 때문에 ACK을 받았을 때 지우게 된다. rwnd가 600이므로 window size를 600byte로 잡아 설정한다. 

 그 후 어플리케이션에서 300byte를 sending buffer에 가져다 놓는다. 최대 600byte를 보낼 수 있지만, 데이터가 300이므로 300byte의 데이터가 전송된다. 

 receiver에서는 기존에 빈 공간이 600byte였다가 300byte를 받았다면 300이 남지만, 그 사이에 read함수로 인해서 어플리케이션으로 100byte의 데이터를 가져갔다(consumed). 따라서 남은 빈 공간은 400byte이다. 301~600의 데이터를 받았으므로 ackNo는 601이고 rwnd는 400의 ACK을 전송한다. 

 sender는 ACK을 받았으므로 보냈던 301~600의 정보들을 buffer에서 지워버린다. 그리고 rwnd값에 따라 window size 400byte 만큼 공간을 잡는다. 301~600의 데이터를 지우면서 window의 왼쪽 벽이 밀리고, window size만큼 크기를 잡으면서 window의 오른쪽 벽이 밀린다고 보면 된다. 

 그 사이 receiver에서는 200byte를 read를 통해 어플리케이션으로 보냈으므로 빈 공간이 400에서 600byte가 된다. 사실 위 그림 8번의 경우 처럼 Data를 받지않고 ACK만을 보내는 경우는 특수 케이스이다. 어쨌든 ACK에 ackNo로 601을 rwnd로 빈 공간 600을 보낸다고 보자. 

 sender는 ACK을 받고 ackNo인 601번부터 rwnd 값 600만큼 window를 잡는다. 

 

반응형

관련글 더보기