TCP

|

이번 포스트에서는 TCP/IP 프로토콜 중 TCP에 대해서 설명한다.TCP/IP 프로토콜의 계층 구조에서 TCP는 Transport 레이어에 해당한다.다른 프로토콜인 UDP와 달리 TCP는  스트림 기반의 프로토콜이다. 이는 다시 말하면 보내는 데이터의 경계가 없다는 말이다.예를 들어 APP가 생성한 데이터를 보낸다고 한다면, UDP는 전송되는 user datagram의 어디부터 어디까지가 어느 파일이다.라고 명확히 boundary가 있는 반면, TCP에서는 생산자가 만들어놓은 데이터와 보내는 데이터의 크기가 다를 수 있다. 즉 boundary가 없는 stream의 형태이기 떄문에 한 파일이 2개의 패킷으로 나누어져 갈 수도,아닐 수도 있다.즉 segment의 크기가 가변적이다.다시 비교해보자면 UDP는 생성자가 보낸 그대로 나가는 반면 TCP는 그것을 바이트 단위로 끊어서 재가공한다.즉 생성자가 만든 것과 boundary가 일치하지 않는다.  또한, TCP는 full-duplex(전이중) 서비스를 제공한다. 이 말은, A에서 B로 가는 동시에 B에서도 A로 데이터가 갈 수 있다는 뜻이다.

TCP는 전송하는 모든 데이터 바이트에 번호를 매긴다. 이 번호는 전송 - 수신간에 별도로 매겨진다.(TCP는 양방향 통신이므로 송수신 양쪽에 읽기쓰기 버퍼가 한쌍 씩 들어있다.)바이트 번호는 0부터 2^32-1 사이의 번호 중 임의의 값을 선택해 처음 바이트의 번호로 설정한다. 만일 임의의 값이 1023라고 보내는 데이터가 6000바이트라면 1023~7022 까지의 번호가 바이트 각각에 매겨지는것이다.
개요에서 설명한 것처럼 TCP는 파일이 순서대로 도착할 수 있게 보장해주어야 한다. 이를 위해 TCP 헤더에는 sequence Number라는 필드가 있다. 이 순서 번호는 TCP가 보내는 세그먼트의 첫 번째 바이트의 번호로 설정된다.


만일 5000 바이트의 파일을 전송한다고 가정하고, 첫번째 바이트는 10001의 번호를 갖고 있고 한 세그먼트는 1000바이트씩 전송한다면 각 세그먼트의 번호는 위와 같이 된다.한 세그먼트에서  다음 세그먼트로의 순서 번호 증가는 데이터 사이즈, 즉 바이트 번호만큼 증가하게 된다. 위에서는 1000바이트씩 전송하므로 순서 번호는 1000씩 증가하게 되는 것이다.

순서 번호와 더불어 TCP의 데이터 전송의 신뢰성을 보장하기 위해 사용하는 것이 바로 Acknowledgement Number다. 이 번호는 자신이 바이트를 수신했
다는 것을 확인해주기 위해 쓰는 것으로, 자신이 수신하기를 기대하는 다음 바이트의 번호를 나타낸다. 예를 들어 10000번 까지 받았다면, 송신 측에 보내는 ACK은 10001번이 되는 것이다. ACK는 누적(cumulative)이 되므로 앞의 ACK이 없어져도 뒤가 앞을 포함한다.예를 들어 seq Num이 2000 3000 4000 5000인 패킷이  전송되었다면 ACK은 각각 3000 4000 5000이 될 것이다.이 때 만일 3000 4000에 대한 ACK이 유실되어도 5000이 전송되었다면, 2000~4999까지는 확실히 받았다는 것이 보장된다.
만일, 세그먼트가 순서대로 도착하지 않았는 경우,예를들어 seq가 1000,2000,3000인 경우 1000->3000->2000 순으로 들어온 경우에, TCP에서는 어떻게 대응할지 규칙을 정해놓지는 않았으나 대부분 수신자가 순서가 틀린 데이터를 갖고 있다가 비어있는 데이터를 기다리는 형식으로 구현하고 있다.

 

 

앞선 정보를 바탕으로 TCP의 전송 단위인 Segment가 어떻게 구성되어 있는지 살펴보자.


(TCP 헤더는 일반적으로 20 바이트다.)


 

Transport Layer는 process to process를 책임져야 하므로 송신과 발신 측 프로세스의 포트 번호를 알고 있어야한다.또한 ,신뢰성을 위한 순서 번호와 ACK 번호도 들어가야 한다.순서 번호는 랜덤으로 시작하는 것에 유의하자.헤더와 데이터 사이에는 뚜렷한 경계가 없으므로 어디서부터 헤더고 어디부터 데이터인지 구별하려면 헤더의 길이를 알아야하는데, 그 정보를 알려주는 필드가 HLEN이다. HLEN은 4비트로 15까지 표현한데, 헤더의 길이는 20~60 바이트이므로 이 HLEN에 4를 곱한 것이 헤더의 길이가 되겠다. HLEN이 4면 최대 길이인 60,4면 최소인 20이 된다.
RESERVED는 차후에 쓰일지도 모르는 정보를 위해 잡아놓은 필드다. 다음으로 flow control을 위해 필요한 필드인 window size는 받는 쪽의 윈도우에서 남은 공간을 표시해 전송하는 용도로 쓰인다.이 크기는 rwnd(receiving window)라고 하고, 수신 측에 의해 정해진다.즉 ACK 용도로 보내지는 세그먼트에 포함되는 값이다.URG,ACK,PSH,RST,SYN,FIN이 나열된 필드(6bit로 하나의 비트가 하나의 역할을 나타냄.)는 제어 필드로써 헤더의 목적을 나타낸다.동시에 여러개의 bit가 1로 세팅될 수 있다.  Checksum은 패킷의 내용이 제대로 전송되었는지 확인할 때 사용하는 필드, Urgent Pointer는 세그먼트가 긴급 데이터를 포함하고 있을 때 사용된다.


 

이 헤더와 어플리케이션 레이어에서 온 데이터필드를 합친 크기를 흔히 MSS(Maximum Segment Size)라고 하고, 이 단위가 데이터필드의 크기를 제한한다.
일반적으로 TCP에서 큰 파일을 전송할 때, MSS의 크기로 파일을 쪼갠다.단, 텔넷같은 대화식 어플리케이션에서는 TCP 세그먼트 안의 데이터 필드가 1바이트만 가질 때도 있다.

 

 


Control Field의 역할은 위와 같다.

다음으로 TCP의 연결 설정을 보자.TCP에서의 연결 설정은 three-way handshaking이라고 한다. 이 절차는 서버에서부터 시작한다.

서버는 먼저 자신의 TCP에게 연결을 수락할 준비가 되었다는 것을 알린다.(Passice Open) 이는 연결이 가능하다는 것을 알리는 것이지 클라이언트와 연결이 되었다는 뜻은 아니다.(서버가 먼저 SYN을 보낼 수는 없음) 이때 클라이언트가 서버에게 연결을 요청한다.(Active Open).클라이언트는 자신의 TCP에게 특정한 서버와 연결을 설정할 것이라는 것을 알린다.
이 때 헤더의 제어 필드에서 SYN부분을 1로 할당하여 세그먼트를 보낸다.(클라이언트->서버) 이 세그먼트에는 바이트에 순서번호를 매기기 위해서 처음 순서 번호를 랜덤으로 선택해 보내게 된다.이 세그먼트에 데이터는 포함되지 않지만, 순서 번호를 한 개 소비하게 된다. 서버 측에서는 받은 SYN에 대해 확인응답  - ACK을 보내주어야 하는데, 이 세그먼트와 더불어 서버->클라이언트 연결을 만들기 위해서 SYN 세그먼트도 보내야 한다.이를 각각 따로 보내면 four-way가 되고, 같이 보내면 three-way가 된다.클라이언트가 보낸 SYN에서 순서 번호가 8000이었으므로 ACK은 그 다음 번호인 8001이며,
서버->클라이언트 방향에서 초기 순서 번호는 랜덤으로 선택된 15000이다.또한 서버 쪽 윈도우의 남은 공간(rwnd)는 5000이다. 이 SYN+ACK 패킷은 마찬가지로 데이터를 포함하지는 않지만 순서 번호를 한 개 소비한다.
클라이언트 측에서는 마찬가지로 서버 측에서 온 SYN에 대한 응답을 해야한다.ACK에 데이터가 같이 붙어 나가지 않는다면 ACK은 단독으로는 순서 번호를 소비하지 않으므로 순서 번호는 그대로 8000으로 나간다. SYN에 대한 응답으로는 15000까지는 받았으니 15001로 나가야 한다.(ACK에 대한 ACK은 없다.) 위 과정을 함수와 같이 정리하면 밑 그림과 같다.


위 그림과 관련된 설명은 나중에 코드와 함께...


위의 연결 과정이 끝나면 데이터 교환을 시작한다.(위 그림에서 Connection Establshment와 Connection Establishment의 위치가 바뀌었음)
클라이언트에서 서버로 데이터를 전송한다. 위의 그림과 이어서 ACK은 그대로 15001이고, 데이터 전송을 시작하므로 순서 번호는 8001부터 시작한다.
1000바이트를 연속드로 2번 보내므로 첫번째 데이터의 순서 번호는 8001, 두 번째는 9001이다. 서버측에서는 두 번의 데이터 모두 ACK을 보내도 되지만, 한 개의 ACK으로도 두 번의 확인응답을 할 수 있으므로(Cumulative) 확인 응답 번호 10001번으로 두 번 모두 처리가 가능하다.위에서는 서버에서 클라이언트로 ACK과 함께 2000바이트의 데이터를 보내고 있다. 서버로부터 받은 데이터에 대해 보내는 ACK은 17001이다.전에 받았던 세그먼트에 대한 ACK과 이번에 보낼 데이터를 같이보내는 것을 piggybacked되었다고 한다.

 

이제 연결의 종료를 알아보자.연결을 끊기 위해서는 FIN 필드를 1로 세팅한 세그먼트가 필요하다. SYN과 마찬가지로 FIN 세그먼트는 하나의 순서 번호를 소비한다.하지만 FIN에는 데이터가 같이 붙어서 나갈 수 있기도 하고, 단순히 제어 세그먼트일 수도 있다.클라이언트로부터 온 FIN 세그먼트(Active Close)를 수신한 서버 쪽에서는 이에 대한 응답으로 ACK을 전송해야 하고 더불어 서버->클라이언트의 연결도 종료해야 한다.(Passice Close)연결 시와 마찬가지로 three-way 방식으로 FIN과 ACK을 동시에 보낼 수 있다.이때 FIN에 대한 ACK을 받아야 해당 연결이 끊어지므로 FIN에 대한 ACK을 받은 클라이언트는 클라이언트->서버 방향의 연결을 종료시킨다.서버로부터 FIN을 받은 클라이언트는 이에 대한 ACK을 보내고, 이를 받은 서버는 서버->클라이언트 방향의 연결을 종료한다.

종료 과정을 함수와 더불어 설명하면 위 그림과 같다.

만일 닫기 과정 중, 한 쪽이 더 보낼 데이터가 있는 상황이라면,즉 보낼 데이터는 다 보냈고 받기만 해도 되는 상황에서는 Half Close라는 기법을 사용한다.
예를 들면 클라이언트가 서버에게 3 1 2라는 숫자 데이터를  보내고, 서버가 이를 번호 순서대로 정렬해서 다시 클라이언트로 보내야 하는 상황에서 서버가 숫자를 정렬하는데 시간이 걸리는 상황이라면. 클라이언트는 숫자를 모두 전송한 후, 출력 스트림을 닫아고 입력 스트림만 열어두어도 되는 것이다.

위의 그림에서는 클라이언트가 데이터를 모두 전송한 후 FIN 세그먼트로 연결을 종료하고, 서버에서는 FIN+ACK이 아닌 클라이언트의 FIN에 대한 ACK만 보내주었다. 즉 클라->서버 방향의 연결만 종료된 것이다. 이후에 서버가 클라이언트로 데이터를 보내고(클라이언트에서 서버로 데이터가 아닌 ACK은 갈 수 있다.)
전송이 끝나면 서버에서 FIN 세그먼트를 보내 서버->클라이언트 방향의 연결도 종료시킨다.(위 그림에서 클라->서버로 보내는 FIN+ACK에 y로 설정되어 있다. 이는 기대값이 y인데, 서버에서 보내는 ACK은 y-1이다. 이는 ACK에 아무 데이터도 붙이지 않았고 ACK은 순서번호를 소비하지 않기 때문이다. 밑 부분도 마찬가지다.)

 


위 그림은 연결 설정,전송,종료 기간 동안 발생하는 일들을 정리한 그림이다. / 앞 뒤로 앞은 받은 이벤트, 뒤는 전송할 반응이다.
먼저 클라이언트의 흐름(굵은 실선)을 보자면 먼저 Active Open을 위해 SYN 패킷을 전송하고 SYN-SENT 상태로 넘어간다.이후 서버로부터 SYN+ACK(three way handshake)를 받으면 이에 대한 ACK을 보내고 ESTABLISHED 상태로 들어가게 된다.
이제 서버의 흐름(점선)을 따라가 보자면, 서버는 능동적으로 연결을 만들 수 없으므로 Passive Open을 위해 Listen상태에 들어간다. 이 때 클라이언트로부터 SYN을 받게 되면 서버는 SYN+ACK을 전송하고 SYN-RCVD 상태로 들어간다. 이 때 클라이언트로 ACK을 받게 되면 ESTABLISHED 상태로 들어가게 된다.

연결 종료는 연결 설정과 흡사하다.서버쪽에서는 FIN을 받게 되면 ACK을 보내고(이 떄 FIN을 같이 보낼 수도 있다), Close-Wait 상태로 들어간다.이 상태에서 Close 함수를 호출해 FIN을 전송한다. 이 FIN에대해 클라이언트로부터 ACK을 받아야 비로소 연결이 종료된다.(버퍼의 해제라든가)
클라이언트 쪽에서는 조금 복잡하다. Close를 통해 FIN을 보내고 FIN-WAIT1 상태에 들어간다.이 때 서버로부터 ACK이 들어오면(FIN이 같이 들어올 수도 있고)
FIN이 들어올 때까지 기다린 후 서버로부터 FIN을 받았다면 그에 대한 ACK을 보내고 TIME-WAIT 상태에 들어간다. 서버는 ACK을 받자마자 끊어지는데 왜 클라이언트 쪽에서는 기다리는걸까? 2가지 이유가 있다. 첫 번째로 서버가 보낸 FIN에 대해 클라이언트가 보낸 ACK을 서버가 받지 못했을 경우, 다시 ACK을 전송해주어야 하기 때문이다. 만약 ACK이 유실되었는데 클라이언트가 먼저 연결을 끊게 된다면 서버는 클라이언트로 보낸 FIN이 유실된 것으로 생각하고 계속 FIN을 보낼 것이다. 두 번째로 같은 서버에 같은 클라이언트가 다시 접속하는 경우다. 서버는 클라이언트를 소켓 주소(IP+포트번호)로 구별하는데, 같은 클라이언트에 같은 프로세스가 접속한다면 소켓 주소가 같아진다. 이를 서버가 old connection과 같은 것으로 착각하고 new connection에 old connection의 정보(버퍼같은)를그대로 사용한다면 문제가 발생할 수 있게 된다.따라서 old connection에서 사용한 포트번호를 new connection이 사용하지 못하도록 잡아두기 위해서 서버 쪽에서 연결을 충분히 끝낼 수 있는 시간까지 대기하는 것이다.  

만일 클라이언트에서 먼저 FIN을 보내는 것이 아니라 서버 쪽에서 FIN을 보낸다면 어떻게 될까? 그렇다면 서버는 FIN에 대한 FIN+ACK을 받고,ACK을 전송한 후 똑같이 TIME-WAIT상태로 들어가게 된다. 이 때 서버를 다시 키게 된다면 이전의 서버가 해당 포트를 잡아두고 있으므로 bind error가 발생하게 된다.

위에서 사용된 상태에 대한 요약은 위와 같다.


위는 연결 설정부터 데이터 교환,Half Close 까지의 과정을 전체적으로 다시 그린 그림이다.


이제 송수신에서 사용되는 Window에 대해 알아보자. TCP는 데이터 전송을 위해 각 방향에 대해 양방향 통신의 경우 각각 2개의 윈도우, 즉 4개의 윈도우가 필요하다.여기서는 간단히 클라->서버로 단방향 통신이 이루어진다고 생각하고 설명하도록 한다.

송신 윈도우의 경우는

a.의 경우는 보내는 쪽 윈도우를 나타내고 있다. ~200 까지는 보낸 바이트 중 ACK을 받은 데이터, 즉 확인 응답을 받았으므로 재전송을 할 필요가 없는 데이터다.
201~260은 보냈지만 아직 ACK을 받지 못한 데이터, 즉 재전송을 할 수도 있는 데이터다.261~300은 보낼 수 있는 바이트, 즉 정해진 윈도우 안에서 더 보낼 수 있는 데이터다. 301~은 윈도우가 움직이기 전까지는 보낼 수 없는 데이터다.
b.에서는 201~260이 ACK을 받아 재전송할 필요가 없어졌으므로 윈도우의 좌측 벽이 왼쪽으로 움직인다. 이때 보내는 쪽에서는 받는 쪽의 ACK에서 rwnd정보를 읽어 윈도우 크기를 새로 설정해야 한다.하지만 rwnd값에 따라 윈도우의 오른쪽 벽이 왼쪽으로 줄어들 수도 있다.이런 경우를 윈도우가 shrink되었다고 한다.
이 문제는 뒤의 흐름 제어에서 다시 다루도록 한다.

받는 쪽의 윈도우는

~200까지는 이미 프로세스에 보내 프로세스로 재전송할 필요가 없는 데이터다. 201~260은 프로세스가 제대로 소비되어 보내는 쪽에 다 받았다고 ACK을 보내기 전의 데이터다.261~300은 비어있는 공간으로 앞으로 받을 수 있는 공간,즉 Receive window size(rwnd)를 말한다.이 크기는 버퍼의 크기에서 프로세스로부터 읽히기 전의 데이터 -  즉 201~260의 크기를 뺀 공간이다.301~의 공간은 할당된 버퍼 밖의 공간이다.
b.에서는 201~260의 데이터가 프로세스에게 전부 소비되었으므로 버퍼에 더이상 담을 필요가 없어졌으므로 윈도우의 좌측 벽이 움직인다.따라서 윈도우의 오른쪽 벽도 늘어난다. 수신 측에서는 벽이 shrink되지 않는다.

이제 흐름 제어로 넘어가보자. 이전 포스트에서 설명한 대로 흐름 제어는 생산자가 데이터를 만드는 속도와 소비자가 데이터를 사용하는 속도의 균형을 맞추어 버퍼에서 버려지는 데이터가 없게 하는 것이다. 보내는 쪽에서는 프로세스에서 버퍼로 보내는 양을 조절해줘야 하며, 받는 쪽에서는 받는 양을 보내는 쪽에 말해 조절할 필요가 있다. 그림을 통해 흐름제어를 보자.

클라이언트는 SYN을 통해 순서번호를 100부터 시작한다고 알려준다. 서버 쪽에서는 100의 순서번호를 가진 SYN을 받았으므로, 윈도우 크기는 101부터 시작하고 ACK번호는 101으로 전송한다. 서버가 택한 초기 순서 번호는 1000이다.수신 측(서버)의 윈도우 크기는 800이고, rwnd도 800이다.
이 정보를 받은 클라이언트는 rwnd를 바탕으로 윈도우 크기를 800으로 결정한다.서버가 보낸 SYN의 seq가 1000이었으므로 클라이언트가 보내는 ACK은 1001이다.클라이언트가 바로 다음에 보내는 데이터의 크기는 200바이트다.수신받은 서버는 800에서 200만큼을 뺀 600이 rwnd가 된다.서버는 클라이언트에게 101~300까지 받았다고 ACK을 보내면서 rwnd가 600이라고 알려준다.클라이언트에서는 101~300까지는 ACK을 받았으므로 윈도우의 왼쪽 벽을 오른쪽으로 밀면서, rwnd로 윈도우 사이즈를 새로 설정한다.보낼 데이터의 처음은 301부터이므로  301부터 600을 더한 900까지가 새 윈도우의 크기다. 여기서 클라이언트는 서버로 300 바이트의 데이터(301~600)를 보낸다. 서버에서는 101~200까지의 데이터를 프로세스가 가져갔으므로 버퍼의 왼쪽 벽을 오른쪽으로 밀고,301~600을 수신한다. 600의 크기에서 100이 프로세스가 소비해 버퍼에서 나가면 100만큼의 공간이 추가되므로 윈도우 사이즈는 700이 되지만, 300의 데이터를 받았으므로 rwnd는 400이 된다. 서버는 클라이언트로 rwnd를 400으로 설정하고 600까지 받았다고 ACK을 전송한다.클라이언트는 ACK을 받았으므로 600까지의 데이터는 버퍼에서 지우고 윈도우 크기를 400으로 다시 설정한다. 여기서 클라이언트가 데이터를 보내기도 전에 서버쪽에서 200 바이트 만큼의 데이터를 프로세서가 가져가 rwnd가 200 늘어난 600으로 ACK이 온다. 클라이언트 쪽에서는 윈도우의 크기를 다시 600으로 설정한다.   

이제 송신 측 윈도우의 크기가 Shrink되는 경우를 보자.


a.의 경우 윈도우 사이즈는 12,마지막으로 ACK으로 받은 숫자는 206이다. b.에서는 ACK이 216으로 왔지만 rwnd가 4로 와 오른쪽 벽이 왼쪽으로 줄어들었다.이런 상황의 예로는 서버 쪽 프로세스가 버퍼에 있는 데이터를 소비하는 속도가 받는 속도보다 느려서 윈도우 사이즈가 충분히 비지 못하는 상황이 있다.받는 쪽에서는 이런 경우가 생길 것 같으면 ACK을 지연 시켜서 윈도우가 줄어들기 전에(위의 그림에서는 윈도우 사이즈가 12일 때) 프로세스가 데이터를 소비해 rwnd가 늘어나길 기다려야 한다.(이 때 송신 측에서는 데이터를 아예 보내지 않는 것이 아니라 남은 rwnd만큼만 보낸다)

윈도우의 크기와 관련된 다른 이슈로는 Silly Window Syndrome이 있다. 이는 송,수신 양 쪽에서 발생하는데, 송신 쪽은 송신 측의 버퍼에 보낼 데이터가 너무 작아서, 이에 헤더를 붙이는 비용이 부담스러운 상황 - 즉 생산 속도보다 전송 속도가 더 빨라서 큰 상황이다.수신 측에서는 프로세스의 소비 속도가 수신 속도보다 느려서 rwnd가 작은 값이(예를 들어 1) 송신쪽에 보내져 송신측에서 계속 적은 양의 데이터만 보내 오버헤드가 커지는 현상이다.
송신 측에서 발생하는 문제를 해결하기 위한 방법으로는 Nagle 알고리즘이 있다.이 방법은 전송 시 처음은 버퍼에 얼마나 데이터가 있던 바로 보내고, 다음 데이터부터는 대기시켰다가 1.대기 중 처음 보낸 패킷의 ACK이 오거나(너무 전송이 늦어지면 프로세스의 처리에 영향이 발생할 수 있으므로) 2.충분히 세그먼트의 크기가 커지면 전송시키는 방법이다. 송신측에서 발생하는 문제는 보내는 버퍼에 너무 조금씩 데이터가 들어오기 때문에 발생하는 문제이므로 이렇게 오버헤드를 줄일 수 있다.
수신 측에서 발생하는 문제는 2가지 방법으로 해결할 수 있다. 하나는 Clark Solution으로,수신 측의 버퍼에 충분한 공간이 생길 때까지 송신 측에 보내는 ACK의 rwnd를 0으로 보내는 것이다. 이렇게 되면 송신측에서는 윈도우가 줄어드는 것이 아니라 아예 폐쇄되어 데이터를 더이상 보내지 않게 된다.
두 번째 방법은 ACK의 전송을 지연시키는 방법이다. 수신측에서는 수신 버퍼에 충분한 공간이 있을 때까지 ACK의 전송을 보류한다.즉 송신측에서는 세그먼트를 계속 보내지만 윈도우를 더 이상 진행시키지는 못하고 윈도우만큼의 데이터를 보내고 전송을 멈춘다.단 이 방법은 송신측에서 ACK을 받지 못한 세그먼트를 계속 재전송할 가능성이 있다. TCP는 위 두 방법의 균형을 맞추어 ACK을 0.5초 이상 지연되지 않도록 정의하고 있다.

다음은 TCP에서 발생하는 문제인 SYN Flooding을 다뤄보도록 하겠다.


서버 쪽에서 클라이언트를 처리하는 순서는 위와 같다. 일단 서버측에서 Listen 함수를 호출해 소켓을 서버 소켓으로 바꾸어 SYN을 요청한 클라이언트에게 SYN+ACK을 보내도록 한다. SYN을 보낸 클라이언트들은 연결 요청 대기 큐 서버에 SYN+ACK에 대한 ACK을 보내지 않은 클라이언트들을 넣어놓는다. 여기서 서버가 Accept 함수를 통해 클라이언트가 보낸 ACK을 받아야 새 소켓에 클라이언트 정보를 넣고 데이터 송수신을 진행하는 것이다.
여기서 만약에, 한 클라이언트가 소켓 주소를 계속 바꿔 저 큐를 다 채워버린다면 어떻게 될까? 큐가 꽉 차면 다른 클라이언트가 큐에 진입하지 못하게 되므로 서버가 정상적인 서비스를 수행하지 못한다. 이 방법이 DOS(Denial Of Service)공격 중 한 방법이다. 위의 문제를 해결하기 위해서는 큐에 ACK을 기다리는 클라이언트가 대기하는 시간을 정해놓고 시간을 초과하면 삭제하는 방법이 있겠다. 하지만 이 방법도 지우는 속도보다 들어오는 속도가 더 빠르다면 문제가 발생된다.


다음으로 오류 제어에 대해 설명한다. TCP에서는 Checksum이라는 필드가 있어서 세그먼트가 훼손되었는지를 검사한다. 만약 이 검사합을 통해 손상이 판명되면 세그먼트를 폐기하고 손실로 간주한다. 
앞서 세그먼트가 제대로 전달되었는지 확인하기 위해서는 ACK을 사용한다고 했다.이 확인 응답에도 종류가 있는데, selective ack (SACK)은 ACK을 대체하는 것이 아니라 순서에 어긋나게 들어온 데이터 블록과 중복 세그먼트 블록을 알려주는 부가 정보를 전달하는 용도로 쓰인다.이것은 나중에 TCP의 옵션을 다루면서 다시 이야기 하기로 하고, ACK의 전송에서 일반적으로 쓰이는 규칙을 알아보자.

Rule 1은(이 규칙은 TCP에서 정해놓은 것이 아닌 규칙에 번호를 매기기 위해 임의로 정한 이름이다) ACK을 전송할 때 헤더만 나가는 것이 아니라 같이 나갈 데이터가 있다면 데이터까지 같이 보내주는 것이다.(ACK+DATA)
Rule 2는 1에서 연장된 개념으로 ACK을 전송할 때 같이 나갈 데이터가 버퍼에 쓰일 때까지 일정시간(ACK-delaying timer)동안 기다려보는 것이다.
Rule 3는 2에서 연장된 개념으로 일정시간동안 기다리다가,시간 내에 반대 쪽에서 데이터 세그먼트가 2개 이상 들어오게 된다면 버퍼에 데이터가 쓰이는 것을 기다리지 않고 세그먼트 2개당 ACK을 무조건 1개씩은 보내는 규칙이다.
위 규칙 1 2 3 은 트래픽을 감소시키기 위해 ACK 개수를 줄이는 방안이다.

Rule 4는 수신 측에서 기대한 순서번호가 아닌 다른 순서번호가 도착하면(일단 버퍼에 넣지만 순서대로 빈칸은 넣어놓고) 즉시 자신이 수신하기를 기대하는 세그먼트의 순서번호(위에서는 701)을 보내는 것이다.이는 뒤에서 다룰 빠른 재전송을 위한 것이다.
Rule 5는 4에서 누락된 세그먼트가 도착하면 수신하고자 하는 다음 순서번호를 보낸다.위 그림에서는 801~900은 받았으므로 건너뛰고 바로 901을 보낸다.
TCP는 이런 순서번호가 지켜지지 않으면 프로세스에게 데이터를 보내지 않는다.
이제 세그먼트의 재전송에 대해 알아보자.

일단 좌측의 RTO(Retransmission Time-Out) 타이머에 대해 알아보자. 이 타이머가 시작되고 만료되기 전까지 ACK을 받지 못하면 TCP는 버퍼 가장 앞의 있는 세그먼트를 재전송하고 타이머를 재구동한다.이러한 TCP를 Tahoe라고 한다.여기서 RTO의 값은 가변적이며 RTT(Round Trip Time : 왕복 시간)를 기반으로 업데이트 된다.
위의 그림에서는 RTO에 의한 재전송이 아닌 3개의 중복 ACK에 대한 재전송(빠른 재전송)을 보여주고 있다.이 방식은 타임아웃을 기다리는 것보다 더 빨리 재전송 하는 것인데,만일 전송 측에서 3개의 중복된 ACK(총 4개의)을 받는다면 손실로 간주된 세그먼트를 재전송 하는 방식이다.이런 TCP를 Reno라고 한다. 근래의 대부분의 TCP는 이 방식을 따르고 있다.
그런데 왜 하필이면 중복이 3개까지 될 때까지 기다리는 것일까? 그 이유는 중복된 ACK은 다른 상황,예를 들면 201-300이 전송되고 301-400과 401-500이 순서가 바뀌어 수신된 경우에도 ACK 301이 두번 도착할 수 있기 때문이다.따라서 중복된 ACK이 3번 도착하면 100% 없어진 거은 아니지만 거의 그럴 가능성이 높다는 것으로 추측하여 재전송을 하는 것이다.  
  

위 그림은 만약 ACK이 손실 되었을 경우에, 뒤의 ACK이 앞의 ACK을 포함하는 것을 보여준다. ACK 701이 유실되었지만 901이 501~900까지의 데이터를 받았다는 것을 말해주고 있다.

위의 그림에서는 601~700에 해당하는 ACK이 손실되고 보내는 측에서는 RTO 타이머가 만료되어 501부터 다시 데이터를 보낸다. 하지만 수신 측에서는 제대로 받았으므로 중복된 세그먼트는 폐기하고 순서에 맞는 데이터를 받기 위해 ACK을 전송한다. 이것이 Rule 6다.


이제 혼잡 제어를 살펴보자. 혼잡 제어란 송-수신 사이에 있는 네트워크 상황을 고려하는 것이다.즉 송,수신 개체 이외에 그 사이에 있는 네트워크의 상황일 봐서 송신측에서 보내는 속도보다 네트워크가 느리게 처리한다면 네트워크는 송신측으로 하여금 데이터 발생 속도를 늦추도록 해야 한다. 이 때 적용되는 새로운 윈도우 개념이 바로 Congestion Window(cwnd)다. 송신측의 실제 윈도우 크기는 rwnd와 cwnd의 최소 값으로 정해진다.
이제 cwnd가 어떻게 정해지는지 살펴보자.

TCP에서 혼잡을 처리하는 원칙은 느린 시작(Slow Start),혼잡 회피(Congestion Avoidance),혼잡 감지(Congestion Detect)로 이루어진다.

 


느린 시작에서 윈도우의 크기는 1개의 최대 세그먼트 크기(MSS)부터 시작한다. 이 윈도우 크기는 하나의 ACK이 도착할 때마다 MSS 만큼 증가한다.
위 그림에서

위 그림에서는 1개의 cwnd로 시작했다가, ACK을 받은만큼 늘어나고 있다. 2개로 늘어났다면 2개를 보내 ACK을 2개 받았으므로 2개 더 늘어나고, 4개를 보내면 4개를 받아 4개가 더 늘어나는 식이다. 즉 느린 시작은 1로 시작하지만 지수적으로 증가한다.이렇게 느린 출발을 하는 이유는 네트워크가 얼마나 혼잡한지 알 수 없기 때문이다.이 지수적인 증가는 threshold(임계점)에 도달할 때까지 계속된다.
ssthresh(slow start threshold)에 도달하게 되면 느린 시작은 중지되고 다음 단계가 시작된다.
다음 단계는 가산 단계로써 전체 윈도우 크기 만큼의 세그먼트가 ACK을 받을 때마다 cwnd의 값이 1 증가한다.(즉 각 ACK에 대해서가 아니라 합쳐서 1이 증가)다시 말하자면 이 단계에서는 ACK의 수가 아닌 RTT를 기반으로 증가한다.



이제 혼잡을 감지했을 때의 경우를 보자. 혼잡이 발생했다는 것은 세그먼트를 재전송할 필요가 있는 경우다.즉 패킷이 버려져 ACK이 오지 않거나(RTO 만료) 3개의 중복 ACK이 도착한 경우를 혼잡 발생으로 본다. 이 때 모두 ssthresh는 절반으로 감소된다.
이후에는 2가지 케이스가 분기되는데, RTO 만료에서는 임계치를 현재 "윈도우 크기"의(임계치의 반이 아니다) 반으로 줄인 후 cwnd를 하나로 줄이고 느린 시작단계부터 시작한다.
반면 3개의 중복 ACK이 도착한 경우에는 위의 경우보다 혼잡이 일어날 가능성이 적다는 것으로 본다. 왜냐하면 3개의 ACK을 수신했기 때문에 하나의 세그먼트는 도달하지 못했지만,그 이후의 세그먼트는 안전하게 도착했다고 보고 위에서 설명한 빠른 재전송을 수행한다. 이경우에는 임계치를 현재 윈도우 크기의 반으로 줄이고 cwnd의 값을 임계치의 값으로 설정한다.이후에 가산 증가로 넘어간다.이를 요약하면



여기서 혼잡에 대해 조금 더 덧붙이자면, 만일 100Mbps의 네트워크에 100Mbps의 pc 2대가 두 지점으로 데이터를 보낸다면, 해당 네트워크에서 두 pc의 throughput은 어떨까? 만일 RTT가 같고 packet loss weight이 같다면 두 pc는 똑같은 throughput을 가지게 된다.즉 5:5로 가져가게 된다.(throughput은 1/RTT*루트(packet loss weight)) 하지만 조건이 다르다면, 즉 RTT나 Packet loss weight이 달라진다면 두 연결의 throughput은 달라지게 된다.하지만 한 pc에서 여러개의 connection을 만들게 된다면  조건이 똑같아도 연결이 많은 쪽의 throughput이 더 많아지게 된다. 이를 이용해 flashget이나 토렌트 같이 파일을 분할해 여러개의 connection을 만들어 전송하는 것이다.

신고

'ComputerEngineering > Network' 카테고리의 다른 글

[추가중] OVS command  (0) 2017.02.13
OVS - ODL Setup.  (5) 2017.02.13
BitTorrent  (0) 2016.12.16
TCP  (0) 2014.05.09
Transport Layer  (0) 2014.04.27
개요  (0) 2014.04.26
trackback 0 And comment 0