[CS] 컴퓨터 네트워크 TCP(이론)

재오·2023년 4월 8일
4

CS

목록 보기
7/35
post-thumbnail

TCP

TCP는 Transport Layer에 존재한다.

원래 port넘버는 정해져 있다. 80번이 우리가 쓰는 웹서버이다. 0번부터 1023번까지 port넘버가 다 정해져 있기 때문에 우리가 보통 쓰는 어플리케이션은 1023번 이후이다.

Stream delivery

Stream delivery의 반대되는 개념이 Boundary delivery이다.

예를 들어 사장님이 별사탕 5개 알사탕 4개 막대사탕 3개 이렇게 나눠줬을 때 Boundary delivery는 별사탕 묶어서 한박스 알사탕 묶어서 한박스…이렇게 보낸다. 하지만 TCP는 이런 방식이 아니다. 하나하나 각각을 배열로 따져서 한 박스에 들어가는 만큼 순서대로 다 넣어서 보낸다. → 순서대로 사이즈에 맞게 끊어서 보낸다.

보내고 받는 것 위주로 그림을 보겠다. 전 그림과 다른 점은 Buffer가 들어가 있다. TCP는 보내기만 하는게 아니라 리시버와 양방향이다. 핑크색으로 된 것은 보냈더라도 재전송할 우려가 있기 때문에 가지고 있는 것이고 회색은 곧 나갈 것이다. 패켓은 536바이트고 칸을 원래 한 바이트씩 나눠야 하는데 위 그림은 대충 이해하기 쉽게 그려놓은 것 뿐이다. 오른쪽 그림은 받았을 때 핑크색이고 어플리케이션에서 가져가면 흰색으로 비워진다. 리시버에서 센드한테 뭐뭐 받았다고 응답을 해준다. 그러면 가질 필요가 없기 때문에 재전송할 우려가 사라져서 핑크색이 흰색으로 바뀐다.

TCP segments

나갈 때에는 바이트 덩어리로 나간다. 패켓에는 번호가 붙어있어야 뭐를 못받았고 뭐를 받았다고 응답을 할 수가 있다. 최초에 패켓 번호는 0번이 아니라 랜덤으로 정해서 보낸다.

예를 들어 10001번을 랜덤으로 선택했다. 5000바이트인데 1000바이트 단위로 패켓을 설정한다 싶으면 5개의 패켓을 보낸다는 것을 의미한다. 그 다음 패켓의 번호는 사이즈마다 정해준다. 1000바이트로 나눌 것이기 때문에 패켓 1번은 10001번, 패켓 2번은 11001번, 패켓 3번은 12001번이다.

패켓에 번호를 부여할 건데 이 번호를 sequence라고 한다. TCP는 각 패켓에 첫번째 넘버를 sequence라고 지정했다.

⭐️ TCP sement format

맨 위 핑크색이 TCP 헤더이다. 한 줄당 4바이트를 의미하고 헤더는 최소 20바이트이기 때문에 기본값으로 5줄을 가진다. ACK는 헤더 안에 정의된다. 상대방이 기대하는 next byte 넘버로 정의한다.

왼쪽 Selective ACK 방식은 직관적이다. 100바이트로 나눴다 생각하면 101~200번까지 보내면 101번 받았다고 보낸다. 그리고 201~ 받으면 201 받았다고 전달한다.

TCP는 오른쪽 방식(Cumulative ACK)을 사용한다. 101~200번까지 보내면 리시버는 101번을 받았다고 응답하는 것이 아니라 나 201번 받아야 돼 라는 의미를 가지고 있다. 이 의미는 200번까지 난 제대로 받았어를 내포한다. 만약 200번까지 받았고 201~에서 에러가 발생하게 된다면 301을 보내더라고 201을 보낸다. 401을 보내도 201을 보낸다.

위 그림을 이해하고 돌아온다면 segment에 정의되어 있는 ACK는 다음에 받을 기대하는 값을 보낸다. 이게 Cumulative 방식이다.

모든 값은 0과 1로 표현되어 있기 때문에 헤더와 데이터 경계를 구분짓기 위해서 필요한 것이 HLEN 이다. 옵션이 어디서 끝날지를 모르기 때문이다. HLEN 은 기본적으로 4비트를 가지기 때문에 표현 가능한 값은 0~15이다. 따라서 헤더의 길이는 HLEN 값 *4를 해줘야 한다. 헤더(20~60바이트)는 보통 4로 나누어 떨어지기 때문에 만약 32바이트면 4로 나눈 8을 보내고 받은 쪽에서는 그 값에 4를 곱해서 아 32가 헤드의 길이구나~를 파악한다. 여기서부터는 옵션이구나? 그 이후에는 데이터구나 하고 데이터를 보내준다.

Checksum 은 헤더의 오류를 체크하는 기능을 한다. 오류가 발생하면 버린다. 송신자와 수신자가 16비트씩 잘라서 그 값을 다 더하는데 그 두개의 값이 같으면 에러가 발생하지 않는다. IP header는 IP관련해서만 Checksum을 하고 TCP header는 TCP 뿐만 아니라 데이터 관련해서도 Checksum을 한다.

TCP는 헤더와 데이터를 각각 16비트로 나누어서 Checksum을 진행한다. IP헤더에서 Checksum을 하고 IP헤더에 있는 정보를 가지고 TCP 헤더는 한번더 Checksum을 진행한다. 그때 IP헤더로부터 pseudoheader라는 정보를 받는다. 이 데이터는 전송되지 않고 좀 더 정확하게 Checksum을 하기 위한 용도이다. TCP헤더와 데이터를 가지고 Checksum을 진행한다.

각각이 1비트씩이다.

ACK

ACK에 있는 값이 유효하다. → ACK가 1로 간다면 ACK에 있는 값이 신경써야하는 값이구나 하고 꼭 확인할 수 있게끔 유도한다.

SYN

연결 요청 패켓을 의미한다.

FIN

연결 종료 요청을 의미한다.

⭐️ Three-way handshake

circuit switching은 중앙제어 방식이고 연결 지향적이다. 반면 packet switching은 분산제어 방식이고 비연결 지향적이다. TCP도 연결 지향적이지만 UDP는 비연결 지향적이다. 왜 TCP는 packet switching이랑 다를까? packet은 물리적인 경로 연결을 의미하고 TCP는 상대적 실시간으로 있어야 연결이 유지가 되어야 한다. 그런 개념에서 연결 지향적이다. UDP는 엽서와 같이 실시간이 굳이 필요하지 않다. TCP의 연결 개념은 상대적인 UDP가 있어야 비교가 가능한 개념이다.

TCP는 SYN을 보내고(연결 요청) (seq는 랜덤) → 응답을 8001번으로 해준다. 서버가 S를 보면 아 연결 요청이구나 하면서 잘 받았다는 의미에서 ACK을 보내준다. 앞에서 말한 것과 같이 TCP는 양방향이기 때문에 SYN을 보내고 SYN(랜덤으로 15000번)과 ACK를 같이 받고 ACK(15001)를 보낸다. 위 그림과는 다르게 ACK만 갈 때에는 ACK만 보면 된다. seq는 볼 필요가 없다.

three-way handshake: SYN이 가면 SYN + ACK이 오고 SYN + ACK이 가면 ACK이 온다

TCP는 상호작용을 한다 했으니까 위에 그림과 같이 send buffer와 receiver buffer가 같이 있다. 센드와 리시브를 하게 된다면 버프를 만드는 것이다. 만명의 클라이언트가 10000개의 서버에 요청을 시도하면 10000개의 buffer가 생긴다.

뒤에서 윈도우 사이즈 관련해서 나오지만 rwnd는 데이터를 받을 때 5000만큼이 max이니까 이거 초과해서 보내지마! 라는 의미이다. 액을 보낼때 windowSize만큼 같이 보낸다.

  • SYN = 연결 요청할 때 쓰이는 패켓 번호

  • sequence = 패켓에 부여되는 번호

Half-Close 상태

상대방이 더 보낼 내용이 있을 수도 있다. 클라이언트에서 FIN을 보내면 연결 종료를 요청한 상태이다. 하지만 서버는 종료를 요청하지 않은 상태를 Half-Close 상태라고 한다. 위 그림에서 검정색 라인 위에 파트를 뜻한다.

TCP 상태

SYN이 오기까지의 구간을 LISTEN 이라고 한다. SYN을 보내고 SYN + ACK을 받는 상태를 SYN-SENT 구간이라고 한다. SYN + ACK을 보내고 ACK을 받는 상태를 SYN-Received 구간이라고 한다. ACK을 보내고 FIN까지의 과정을 데이터가 이동 가능한 구간이라고 해서 ESTABLISHED 구간이라고 한다.

첫 FIN이 보내진 상태를 FIN-WAIT-1 상태라고 하며 ACK을 기다리고 있다. 첫 FIN을 받고 ACK을 보낸 상태를 CLOSE-WAIT 상태라고 하며 어플리케이션의 종료를 기다리고 있다. 첫 FIN에 대한 ACK을 받은 상태를 FIN-WAIT-2 라고 하며 TIME-WAIT 은 2번째 FIN을 받고 ACK을 보낸 상태를 의미한다. 이 구간은 동일 포트와 주소에 커넥션이 생성되지 않도록 기다리는 상태이다. LAST-ACK 은 2번째 FIN을 보내고 ACK을 기다리는 상태를 뜻한다.

전에 나왔던 그림을 더 보기 쉽게 나타낸 것이 위 그림이다. 선 색깔은 무시하자.

FIN-WAIT, TIME-WAIT, CLOSE-WAIT 등을 전에 나왔던 그림을 통해 이해하자.

CLOSED 상태에 들어간다는 것은 아예 종료가 된 상태이고, TIME-WAIT은 대기 상태를 의미한다.

소켓(Socket)

클라이언트1과 클라이언트2는 IP주소는 같으나 폴트 넘버가 다르다. 클라이언트3는 아예 다르다. 서버는 새로운 소켓을 만들고 이 소켓을 통해서 클라이언트1과 대화를 한다. 클라이언트는 모른다. SYN SYN+ANK ACK 주고받고 accept이 리턴될 때 새로운 소켓을 만들고 이는 클라이언트2와 연결하는 용도이다. 클라이언트는 새롭게 소캣을 만들었든 신경을 안쓰고 포트번호 80번만 보고 보낸다. 서버의 입장에서는 클라이언트3가 소통하고 싶으면 서버가 할당한 소켓을 연결해주면 된다. 약간 소켓은 매니저 역할(?)이다.

클라이언트 입장에서는 종료 요청하고 응답하고, 할 일은 다 했다. 하지만 마지막에 ACK가 없어질 수도 있다. RTT는 시시각각 변한다. 유튜버 갔다오는 시간, 네이버 갔다 오는 시간이 다르듯! 정해진 시간 안에 응답이 안오면 패킷이 없어진 걸로 알고 TCP 서버는 재전송한다. 마지막 ACK는 FIN에 대한 ACK이다. TIME WAIT없이 FIN을 받아버리면 안되기 때문이다. 따라서 TIME WAIT이 필요하다. ACK가 없어지는 것을 막기 위해서이다.

⭐️ TIME-WAIT 상태로 진입하는 이유

1) Port 중복 할당을 방지하기 위해서
2) 보낸 ACK 분실되면 재전송하기 위해서

Window

STOP&WAIT 방식은 신뢰성을 보장하기 위해 선택된 비효율적인 방식이다. 예를 들어 서울 부산을 왕복해오는 것을 확인하기 위한 수단이 한대가 부산에 갔다가 서울에 와야 아 왔구나 하고 신뢰성을 확보하는 방식이다. 그래서 비효율적이다. 따라서 이를 해결하기 위해 여러 대를 보낼건데 여러 개의 패킷을 묶어서 window라고 하고 이 안에 있는 총 바이트 수를 window size라고 한다.

201부터 300까지가 윈도우이다. 리시버로 부터 윈도우 사이즈 정보가 들어올 것이다. 하지만 엄밀히 말하면 Time Delay가 발생하기 때문에 이미 보낸거까지 윈도우 안에 집어넣어야 한다. 따라서 보낸 것도 윈도우 사이즈에 들어가야 한다. 8개를 보내달라고 오더라도 내가 이미 보낸 2개가 도착할 예정일 수도 있기 때문에… 갔다 오는 시간이 엄밀하게는 존재하기 때문이다. 따라서 OutStanding bytes라고 해서 내가 이미 보낸 바이트가 있다. 그거까지 고려를 하는 것이다. 그리고 흰색처럼 이제 보낼 예정인 것. 이렇게 나뉜다.

261부터 300까지가 윈도우 사이즈(빈 공간)을 의미하고 앞에 주황색 부분을 받은 곳이라고 한다. 중간 중간 빈공간이 발생할 때마다 알려줘야 한다.

16페이지에 window size는 액을 보낼때 리시버의 빈공간 관련 정보를 항상 같이 담아서 보낸다. rwnd에 그 정보를 담아서 보내는 것이다.

Sliding Window라고 해서 Sender 입장에서는 일정하지 않다. 위에서도 설명한 것 처럼 이미 받은 양, 보낸 양에 따라 Sliding Window 크기가 변한다.

⭐️ Flow Control

TCP의 수신측에서 수용할 수 있는 버퍼의 용량을 알려주어 overflow를 막는 것을 의미한다. 한마디로 Receive window size 를 sender에게 알려주는 것을 의미한다. Sender는 tcp header에 receive window 필드를 확인하고 overflow가 일어나지 않을 만큼의 데이터를 전송한다.

일정한 크기로 보내는게 아니라 줄었다 늘었다 하게 데이터를 보낸다. 리시버가 데이터를 어떤 속도로 받느냐에 따라 결정된다. = read()함수를 얼마나 부르냐 = 얼마나 많이 가져가느냐에 따라 결정되는 것이다.

어플리케이션에서 TCP에 데이터를 어떻게 가져다 놓느냐? write함수를 통해서 한다. 엄밀히 살펴보자면 write함수도 block이다. read()에 따라 보여지는 것이다. write는 엄밀히 말하자면 빈공간에 쓰는 것이다.

3번이 정말 중요하다. 3번에서 빨리빨리 가져간다면 그렇다면 비어지는 정보가 빨리빨리 가기 때문에 보내는 속도(쓰여지는 속도)가 엄청 빨라진다. 따라서 2번 스피드와 3번 스피드는 딱 맞아 떨어진다. 1번은 빈공간이 생겨야만 쓸 수 있는 것이다. buffer가 똑같다면 1,2,3번 스피드가 거의 비슷하다. 5번과 같은 피드백은 일어나지 않는다.

버퍼가 800바이트를 서버가 잡아뒀다. 그래서 rwnd 800을 보내준다. 상대방 SYN이 100번을 보냈기 때문에 101번부터 비워둔다. 위에 내가 정리해둔 방식을 토대로 한번 위 그림을 흐름대로 공부해보자.

Stop&Wait 방식은 \

………………………../

…………………………\ 방식이다.

윈도우 사이즈를 쓰면 →

……………………………→ 방식이다.

⭐️ Fast Retransmit

Fast retransmit 은 타이머의 타임 아웃 기간이 상대적으로 너무 길어 타이머가 종료되기 전이라도 중복된 ACK을 3번 받으면 바로 재전송을 하는 기능이다. 손실 된 패킷을 재전송하기 전 발생하는 긴 지연시간을 줄여준다.

☘️ ClientServer 사이에 data 송수신 중 Client가 보낸 packet이 중간에 누락되어 이에 대해 Receiver가 이에 대한 중복된 ACK을 3번 보냈을 경우, packet이 누락되었음을 detect하고 재전송해주는 방식은 Fast Retransmission이라고 한다. TIME-WAIT 이전에 detect하여 누락된 packet을 빠르게 재전송해줄 수 있다는 장점이 있다.

301번부터 받지 못해서 서버는 계속 ACK으로 301을 보낸다 윈도우 사이즈는 계속 허용 되기 때문에 클라이언트는 101, 201, 301, 401, 501 … 계속 보낸다. TIMEOUT 안에 오지 않으면 301을 보내는 것이다. 하지만 TIMEOUT이 가기까지 이상한 일들이 일어난다. TCP는 첫번째 301 요청은 정상으로 인식한다. 이후의 세개의 중복된 ACK이 들어온다면(총 4개) TIME-OUT까지 가지 않고 빨리 재전송해준다. 만약 중간에 501도 안들어오게 된다면 3번 지난 후 301이 들어오묜 501을 요청하게 된다.

⭐️ Lost acknowledgement

901을 ACK으로 보낸다는 것은 그 전에 것은 다 받았다는 것을 의미한다. 서버에서 보내는 ACK 하나 없어도 큰 의미가 있지는 않다.

Lost acknowledgement(TIME-OUT)

패킷 - 패킷 - ACK 이러한 방식으로 데이터가 오고간다. 위 예시처럼 501 ~ 700번까지 보냈는데 ACK이 손실되었다고 한다. 그러면 타임아웃 이 일어나고, 클라이언트는 동일한 세그먼트를 서버에 재전송한다. 서버는 세그먼트가 포함된 SequenceNumber을 통해 이미 수신된 데이터인 것을 확인하면 재송신 된 바이트는 버리고 ACK을 다시 보낸다.

⭐️ deadlock 상태(probe 패킷)

만약 receiver buffer가 가득 찼다가 공간이 생길 경우 언제 세그먼트를 보내야 할까? rwnd 가 0으로 왔기때문에 send buffer는 더 이상 세그먼트를 보내지 않을 것이다. 하지만 공간이 생겼는데 send buffer가 비어 있다면 deadlock 상태에 빠지게 된다. 이러한 상황을 해결하기 위해 만약 rwnd 사이즈가 0이라고 보내왔으면 주기적으로 작은 세그먼트를 보내보는 방법이 있다. 실제 보내고자 하는 데이터에서 한 바이트만 가져다가 세그먼트에 넣어 (헤더 40 + 데이터 1)probe 패킷을 만들어 보내는 것이다. 만약 공간이 생길 경우 유의미한 ack을 받을 수 있다.

⭐️ 혼잡제어(Congestion Control)

혼잡은 왜 발생할까? → 링크의 용량이 100Mbps라고 가정을 하자. TCP1과 TCP2가 100Mbps를 각각 보낸다고 가정을 하면 맥시멈이 100이기 때문에 200이 지나갈 수가 없다. 지속적으로 저렇게 보낸다면 라우터의 용량이 한계이기 때문에 그 이후에 들어온 패킷을 다 버린다. 그래서 패킷이 사라지는 99%가 혼잡때문이다. 중앙제어 방식은 이미 경로가 다 정해져 있기 때문에 혼잡이 발생할 일이 없지만 우리가 쓰는 인터넷은 packet switching이기 때문에 아무나 다 들어갈 수가 있다.

Packet delay

딜레이는 네트워크에서 혼잡해서 생기는 지연을 의미하고 Capacity에 가까워질 수록 딜레이는 쭈욱 늘어나게 된다.

Throughput

파일 하나가 100Mb라면 1초당 100Mbps 2초면 50Mbps이다. receiving rate, 즉 100을 2개로 나누면 50Mbps씩 받게 되는 것을 변하지 않는다. 하지만 60Mbps라면 10을 버리고 이후에 다시 들어가게 되는 것이다. 시간만 오래걸릴 뿐이다.

Congestion avoidence(Additive increase)

cwnd는 sender가 관리하는 것이고 rwnd는 sender가 보내는 것이다. 두개는 서로 전혀 상관이 없다. 밑에 나오는 sending 그래프 관련해서는 rwnd와 cwnd 중에서 작은 값으로 계산을 하는데 보통 cwnd가 더 작다.

cwnd를 계속 늘렸는데도 packet loss가 일어나지 않으면 아 아직 빈 공간이 많구나 하면 더 보내본다. 위의 그림처럼 4개를 보내고 4개가 다 오면 하나를 늘려서 5개를 보내고 5개가 다 오면 6개를 보내보고…를 해본다. packet loss가 일어나면 줄인다.

Slow start(Exponential increase)

하나씩 증가시키는 건 너무 보수적이고 너무 오래걸릴 것 같아서 1,2,4,8…두배씩 뛸 수도 있다. 앞에 Additive increase와는 속도가 너무 차이가 난다. 슬로우 스타트 알고리즘threshold를 만날 때까지 증가한다.

cwnd는 최솟값이 1이다. 1부터 시작한다.

처음에 0초에 1개, 1초에 2개, 2개를 다 갔다오면 2초에 4개를 보낸다…4초에 16개가 된다. 그림에서는 threshold가 16개로 되어있다. 이는 시스템의 default 값이다. 그 다음부터는 보수적을 1개씩만 보낸다. 17개 갔다오면 18개 보내고… 뒤에를 보면 packet loss→ 3ACK으로 인해 혼잡이 발생한다. 따라서 그때부터는 반으로 줄인다. 그때부터다시 보수적으로 하나씩 증가시킨다.

⭐️ TCP congestion policy summary

위 그림은 전에 있던 그림을 좀 더 구체적으로 표현한 표이다. 16을 만나면 밑에 노란색 칸으로 내려온다. 이때는 RTT (왕복 시간)마다 하나씩 증가한다. 3 duplicate ACK으로 인해 혼잡이 발생하게 되면 cwnd 가 반으로 쭉 줄어드는 것이다. 하지만 3 duplicate ACK 말고도 TIME-OUT으로 인해 혼잡이 발생할 수도 있다. 이러한 경우에는 현재 윈도우 사이즈의 반cwnd threshold로 잡아주고 cwnd(윈도우사이즈)를 1로 해버리고 다시 시작한다. 그것이 전 그림에서 두번쨰-세번쨰 그래프이다. 중요한 것은 TIME-OUT이 발생했을 때의 cwnd를 기준으로 반으로 threshold를 잡는 것이다. slow start 알고리즘에서도 3duplicate ACK이 발생할 수도 있고 TIME-OUT이 발생해서 혼잡이 발생할 수도 있다.

profile
블로그 이전했습니다

5개의 댓글

comment-user-thumbnail
2023년 4월 8일

좋은 정보 감사합니다 ~

1개의 답글
comment-user-thumbnail
2023년 4월 10일

이크에크~

답글 달기
comment-user-thumbnail
2023년 4월 10일

잘읽었습니당 소캣 -> 소켓 오타 있어용

1개의 답글