CS/네트워크 수업 / / 2021. 4. 7. 15:47

컴퓨터 네트워크 6주차


TCP(Transmission Control Protocol) Overview

  • point-to-point: 하나의 sender, 하나의 recevier
  • 상위층에 reliable 서비스 제공
  • TCP는 piplining(go-back-n) & sliding window(selective repeat) 방식을 혼합해서 사용
  • segment packet 순서 맞춰서 상위층에 제공
  • in-order byte stream: 메시지를 바이트 단위로 본다. 즉 경계가 없다
  • full duplex data: 동일한 연결 상에서는 양방향 데이터 전송
  • MSS: Maximum Segment Size가 정해짐
  • connection-oriented 서비스: 사전 연결 설정(3-way-handshaking): 데이전 송수신 전에 sender와 receiver의 상태 정보 초기화
  • flow controlled: sender가 receiver가 처리할 수 있을 만큼만 데이터 전송 → sender가 receiver의 상황을 알아야 하므로 receiver는 sender에게 자신의 상황을 알린다.

  • sequence number 필드가 있으면 해당 프로토콜은, 신뢰성을 보장해준다고 생각해도 좋다.
  • TCP에서는 sequence number와 acknowledgement number를 byte 단위로 붙인다. 무슨 말이냐면 하단에 application data는 byte 집합일 것이다. 이 data의 byte마다 sequence number와 acknowledge number를 붙여서 sender에게 전송한다.
  • header length: 헤더의 총 길이(헤더의 옵션이 추가되면 길이가 가변적이어서 헤더의 길이를 알기 위해 추가)
  • not used: 현재는 사용되지 않는 필드지만, 추후에 추가될 정보가 있을 경우, 추가
  • U: Urgent data
  • A: ACK가 valid한지 invalid한지 (invalid하면 ack 사용 X)
  • P(Push): 다른 세그먼트가 올 때까지 기다리지 말고, 이 세그먼트 수신하면 바로 상위 계층으로 올려줘~
  • R(Reset), S(Synchronize), F(Finish): connection 설정할 때 사용
  • receive window: receiver가 받아들일 수 있는 byte size 설정(즉, receiver가 처리할 수 있는 양을 알려줌)
  • Urgent data pointer: Urgent data Field가 어디에 위치하는지 가리킴
  • application data(payload field): 크기가 가변적임
  • sequence number는 byte의 순서번호를 나타낸다. 그렇다면 세그먼트의 데이터에는 수많은 바이트가 있는데 그 중에 어떤 byte의 시퀀스 넘버를 적을까? → first byte의 시퀀스 번호를 적는다
  • 지금까지 배웠던 ack 번호는 마지막으로 올바르게 수신한 패킷의 시퀀스 넘버를 사용했다면 TCP는 잘 받은 바이트의 다음 시퀀스 넘버를 ack(즉, 다음에 받을 것으로 예상되는 바이트의 시퀀스 번호)로 사용한다.
  • cumulative ACK: go-back-n → 중간에 ack가 빠지더라도 그 이후의 ack가 제대로 도착한다면 중간에 빠진 ack도 받은 것으로 sender는 취급한다(인터넷 환경에서 유용)
  • 순서가 빠진 세그먼트가 receiver에서 수신이 되면 어떻게 처리를 해야할까? → TCP에서는 이런 상황에서 상세한 에러 처리 로직을 제공하지 않는다. 즉, 패킷의 순서를 맞추는 로직을 표준 프로토콜에서 제공하지 않으므로 사용자가 만들어야 한다.

  1. 호스트 A에서 'C'라고 타이핑을 치면 'C'가 호스트 B에게 전달될 것이다.
  1. 'C'에 대한 시퀀스 번호 = 42, ACK = 79(호스트 B도 데이터를 전송한 적이 있을 것이므로 예전에 B가 데이터 전송한 것에 대한 ACK이다. 이 때 ACK 79는 B로부터 다음에 수신할 것으로 기대되는 시퀀스 번호이다. 즉, 78 byte까지는 잘 받았고, 다음에 받을 바이트 넘버가 79)
  1. 호스트 B는 'C'를 잘 받았고, 데이터 필드에 'C'를 다시 담아서 A에게 재전송한다고 하자.
  1. seq=78, A는 78 바이트까지는 잘 받았으므로 다음 시퀀스 넘버는 79, ACK = 43 호스트 B가 다음에 A로부터 받을 시퀀스 넘버

RTT(Round Trip Time: 왕복 이동 시간)

ACK, Sequence nuber, timeout 이 세 개 기능을 제공하므로 TCP가 reliable transfer를 제공해주는 프로토콜이다.

그렇다면 timeout은 어떻게 설정할까?

  • RTT보다는 길게 설정해야 하지만, TCP는 인터넷 상에서 서비스가 되므로 단순히 회선 하나에서 서비스가 송수신 되는 거랑은 상황이 많이 다르다. 즉, 네트워크 상황에 따라서 RTT가 매우 상이하다. 그러면 RTT 값을 가장 길게 잡아야 하는 걸까?
    • timeout을 RTT보다 짧게 지정하면 조금만 더 기다리면 ACK를 받을 수 있는데도 timeout이 될 수도 있다 → 불필요한 재전송이 잦아질 수 있다.
    • timeout 값이 너무 길면? 충분히 RTT의 가변적인 상황을 커버할 수 있겠지만, 세그먼트가 손실됐을 경우에는 이런 처리를 빨리빨리 해줘야 하는데 처리 시간이 오래 걸릴 수 있다.
  • 따라서 RTT 값을 너무 길지도 짧지도 않게 설정해야 한다. 그렇다면 RTT 값을 어떻게 측정할 수 있을까?
    • SampleRTT: 세그먼트를 전송하고 ACK를 수신할 때까지 걸리는 시간(재전송 시간은 무시한다). SampleRTT 값은 세그먼트마다 다를 것이겠지만, 우리는 SampleRTT 값이 가파르게 변화하는 게 아닌 완만하게 차이가 나기를 바란다 → 최근의 SampleRTT 측정 값들을 평균 내서 timeout을 정한다!! → EstimatedRTT

  • timeout = estimatedRTT + safety margin(SampleRTT 값이 급격하게 변하므로 어느정도 여유값을 더해야 함)
  • EstimatedRTT로부터 SampleRTT의 분산값을 추정!! (어렵다...)

  • IP는unreliable 서비스이기 때문에 IP를 사용하는 TCP는 자체적으로 reliable system을 얻어서 상위 층에게 전달해야 한다. → pipelined segments(go-back-n, selective repeat), cumulative acks, single retransmission timer(전송을 한 지 가장 오래된 세그먼트에 대한 타이머)
  • 재전송이 일어나는 경우
    • 타임 아웃!
    • duplicate acks (pipelined 방식에서는 재전송을 타임 아웃이 발생했을 때만 사용했는데 TCP에서는 중보 ack가 sender에 도착을 했을 때도 재전송을 한다!!)

  • application 계층으로부터 데이터를 받으면, 받은 데이터를 중심으로 시퀀스 넘버를 보유한 세그먼트를 만들게 된다. 이 때, 이 세그먼트에 소속된 바이트 하나하나당 세그먼트 순서 번호를 갖게 되는데, 그 시퀀스 넘버들을 세그먼트 시퀀스 형식에 쓸 수 없으므로 데이터 필드에 있는 첫 번째 바이트의 시퀀스 넘버를 시퀀스 넘버 필드로 설정해서 전송한다.
  • 타이머: ack를 받지 않은 세그먼트에 대해서 가장 오래된 세그먼트에 대한 타이머
  • 타임아웃 이벤트가 발생하면, go-back-n 방식은 timeout이 발생한 세그먼트와 그 이후의 세그먼트를 전부 재전송하지만, TCP에서는 timeout을 유발한 세그먼트만 재전송한다. (timeout이 발생한 세그먼트만 재전송하는 방식은 selective repeat 방식이다. 이처럼 TCP는 go-back-n과 selective repeat 방식을 혼합해서 사용한다)
  • sender가 수신한 ack가 현재 기다리고 있는 ack가 아닌 그 이전에 받지 못한 ack면 어떤 ack를 받았는지 업데이트.
  • ack와 unack 세그먼트의 갭이 있다면 그 갭이 생긴(아직 ack를 못 받은) 세그먼트 중에 가장 오래된 타이머를 시작 (뭔소리지?)

  • lost ACK scenario
    1. 호스트 A가 호스트 B로 시퀀스 넘버 92(첫 번째 바이트의 시퀀스 넘버가 92)에 8바이트를 전송.
    1. 호스트 B는 ack = 시퀀스 넘버 92 + 바이트 개수로 ack를 전송하는데 중간에 데이터가 손실되면 타임 아웃이 발생해서 호스트 A는 재전송
    1. 호스트 B는 재전송된 세그먼트를 이미 받았으므로 세그먼트는 버리고 ack만 재전송한다.
  • premature timeout
    1. 시퀀스 넘버 92에 8바이트와 시퀀스 넘버 100에 20바이트를 sender가 전송
    1. ack 100가 120을 receiver가 sender 측으로 전송을 했으나 ack 100이 전송되기 전에 sender의 ack 100에 대한 세그먼트가 타임 아웃이 됐다면 (두 개의 세그먼트를 전부 재전송 하는 게 아닌, 타임 아웃이 발생한 세그먼트만 재전송) seq=92 + 8bytes 세그먼트를 재전송
    1. 재전송한 세그먼트에 대한 ack가 오기 전에 좀전에 receiver가 전송했던 ack 100과 ack120이 도착했다면 sender 측은 ack 100은 재전송한 세그먼트에 대한 ack, ack120은 처음에 보낸 세그먼트(seq=100, 20bytes)에 대한 ack라고 생각한다.
    1. sender가 재전송한 세그먼트(seq=92 + 8bytes ) receiver는 세그먼트를 이미 수신했으므로 해당 세그먼트는 버리고 ack 120을 전송한다. 그런데 100이 아닌 왜 120을 보낸 걸까? receiver는 ack 119까지는 이미 받았으므로 다음에 받을 것으로 예상되는 ack를 전송한다. sender 측에서도 이미 ack 120을 받았으므로 아무 문제 없이 다음 작업으로 넘어간다.

    • cumulative ACK
      1. 두 개의 세그먼트(seq=92, 8 bytes), (seq=100, 20 bytes)를 reciever에게 전송하고 receiver는 ack100과 ack120을 보낸다.
      1. ack100이 중간에 손실되고 ack120만 sender가 수신한 경우에, ack 100의 타임이 계속 가고 있을 것이다. 다행이도 ack120을 수신한 시점이 ack100에 대한 패킷 타임이 타임아웃이 되지 않은 상태라면, sender는 reciever가 119까지 잘 받았다고 인식하고 굳이 ack100을 기다리지 않는다. cumulative ack를 사용하면 중간에 ack가 몇 개 사라지더라도 재전송 빈도를 줄일 수 있다.

    TCP에서 ACK를 어떻게 사용할까?

    • (상황1) receiver가 기대하고 있는 시퀀스 넘버를 가진 세그먼트가 도착하고, 기대하고 있는 시퀀스 넘버 전까지의 ack는 이미 sender에게 발송 완료한 상황. → 하지만 TCP에서는 기다리던 세그먼트가 도착하더라도 ack를 바로 전송하지 않는다. 그럼 언제 날리느냐? TCP에서는 500ms 동안 그 다음 세그먼트가 올 때까지 기다리고, 만약 500ms 동안 ack가 도착하지 않으면 이전에 수신했던 세그먼트의ack를 날린다. (ack를 한꺼번에 cumulative하게 날리기 위해서 이런 방식 채택)
    • (상황2) 상황 2와 상황은 동일하되, 이번에는 500ms 안에 두 번째 세그먼트를 받았다. 500ms 안에 두 번째 세그먼트를 수신하게 되면 즉시, single cumulative ack를 전송한다. 즉, 즉 ack100과 ack120을 날려야 하는데, ack120만 날림으로써 sender는 ack120까지 무사히 받은 걸로 인식한다.
    • (상황3) 만약에 receiver가 기대하고 시퀀스 번호보다 더 높은 숫자의 세그먼트, 즉 순서에 맞지 않는 세그먼트가 도착(갭이 발생)하면? receiver는 다음 받을 것으로 예상되는 ack, 즉 좀전에 sender에게 송신한 ack(sender 입장에서는 duplicate ack)을 그 즉시 전송한다.
    • (상황4) 부분적으로 비어있는 세그먼트를 receiver가 수신하면 그 즉시 해당 세그먼트에 대한 ack를 전송한다. 여기서 주의해야 할 게, receiver 측은 받을 것으로 예상되는 시퀀스 번호를 가진 세그먼트를 대기하고 그에 대한 ack는 다음에 받을 것으로 기대되는 세그먼트가 시간 안에 도착하면 시퀀스 번호를 cumulative하게 보낸다(하지만 이것은 어디까지나 하위의 시퀀스 번호를 가진 세그먼트가 전부 도착했을 경우이다) 즉, 지금 받은 세그먼트보다 하위의 시퀀스 번호를 가진 세그먼트를 아직 못 받았고, 그 세그먼트를 수신하면 수신한 즉시, 해당 세그먼트에 대한 ack를 보낸다. sender 입장에서는 그 ack가 가장 상위의 ack가 될 것이다. 이미 더 상위의 시퀀스 번호를 가진 세그먼트를 보냈더라도 receiver 측에서는 수신만 했을 뿐 ack를 전송하지 않았다.

    어떻게 하면 빠르게 재전송할 수 있을까?

    • 잃어버린 패킷에 대해서 재전송을 하기 전까지 타이머가 길게 설정돼 있으면 긴 시간 동안 딜레이가 발생하므로, 좀 더 빨리 패킷을 중간에 잊어버렸다고 판단할 수 있을까? → 그 판단의 근거눈 duplicate ack이다. 즉, duplicate ack가 발생하면 해당 세그먼트가 loss가 된 걸로 여기자!! 보통 sender는 많은 세그먼트들을 연달아 연속적으로 송신한다. 만약 그 중에 한 세그먼트가 손실이 되면 그 이후의 세그먼트들은 receiver 측에서 duplicate ack를 송신하게 될 것이다(gap이 생겼으니까). 무슨 말이냐면, ack는 다음에 받기를 기대하는 squencenum을 보내는 건데, 해당 시퀀스 넘버보다 상위의 세그먼트를 받아도 ack는 다음에 받기를 기대하는 ack, 즉 갭이 생긴 ack를 sender에게 보내게 된다. sender 세그먼트를 계속 보내도 똑같은 ack만 오니 중간에 세그먼트가 손실됐구나라고 생각한다.
    • 그러나 sender 측에서는 duplicate ack를 받자마자 재전송을 하게 되면 네트워크 부하가 너무 심할 것이다. 그래서 TCP에서는 원래 받아야 할 ack 말고 그 외에 duplicate ack를 3개 받으면 receiver가 받기를 기대하는 세그먼트를 재전송한다!!(어렵다!!)

    만약 위 템플릿처럼 sender 측에서 duplicate ack를 인식하고 해당 세그먼트를 reciever에게 전송하면 receiver는 어떤 ack를 보내야 하나? ack 120을 보내야 하나? 아니면 이미 그보다 상회하는 시퀀스 넘버를 가진 패킷을 받았으니, 중간에 갭이 없는 상태에서 가장 높은 ack를 보내야 하나? → 정답은 4번 세그먼트에 대한 ack를 보낸다!!

    TCP Flow Control

    1. receiver에서는 application은 tcp 소켓 버퍼에서 데이터를 가지고 갈 것이다.
    1. application process에서 가져가는 데이터 양보다 수신받는 양이 더 많다면 buffer는 받을 수 있는 데이터 이외의 데이터는 버려야 한다. flow control이란 receiver가 sender를 제어함으로써, sender가 receiver의 buffer가 넘치지 않도록 receiver가 sender에게 일정량 이상의 데이터를 보내지 말라고 요청

    receiver는 어떻게 sender에게 전송 속도 조절을 요청할까? 위 템플릿을 보면 receiver의 buffered data와 free buffer space 두 구역을볼 수 있음을 알 수 있다.

    receiver는 free buffer space를 광고(?)를 한다. TCP 헤더에 보면 rwnd(receive window - 16bits) 필드가 있는데, 이 필드 값에 free buffer space 길이를 담아서 sender에게 보냄으로써 sender는 데이터를 송신할 때 window size 이상으로 보내지 않는다. 그렇다면 세그먼트를 보낼 때마다 윈도우 사이즈가 변하는 건가? 맞다. 세그먼트를 보낼 때마다 버퍼 사이즈는 다를 수 있으므로 free buffer space 길이는 그때그때 다르다. 결과적으로 sender가 보내는 데이터 양은 receive free buffer space 의 길이에 따라서 다르다.

    TCP는 패킷을 송수신 하기 전에 handshake라는 연결 설정 작업을 한다.

    • 상호 연결 설정할 의사가 있는지 → 연결 설정(연결 설정과 관련된 파라미터 값들도 설정한다) → 데이터 송수신 완료 후 연결 해제

    보통의 사람들은 2-way handshake를 하면 되지만, 두네트워크에 있는 두 호스트는 2-way handshake만으로 괜찮을까? → 결론적으로는 안 된다.

    (상황1 - half open) 호스트 X\와 서버가 request_connection과 accept_connection을 하고 데이터를 전송한다 → 데이터 전송을 다하고 서버와 클라이언트 둘 다 연결을 종료 → 하지만 클라이언트가 이전에 보낸 데이터가 재전송 돼서 서버가 이미 접속을 끊은 후에 서버에 도착하면, 서버는 이미 x에 대한 기록이 없는 상태이다. 그래서 서버는 x를 새로운 connection 요청이라고 생각하기 때문에, 서버는 클라이언트에게 access 신호를 전송할 것이다. 하지만 accept 요청을 받을 클라이언트는 이미 접속을 끊었으므로 아무도 서버가 보낸 accept 요청을 받을 수가 없다. 그렇다면 서버는 accept 요청을 보내는 동안, 서버는 데이터를 받을 준비(buffer space 준비 등)를 할 것이므로 그 자체로 자원이 낭비된다.

    (상황2 - ) req_conn(x) → req_conn(x) 를 한 후에 상호 간에 데이터를 주고 받고 데이터 전송 완료 호스트와 서버 둘 다 접속을 종료한다. 하지만 뒤늦게 재전송이 와서 서버는 새로운 클라이언트 요청으로 인식해서 ESTAB(Establish) 상태로 들어간다 →> ESTAB 상태에서 데이터를 수신했으므로 half open 상태가 되므로 이것 역시 자원이 낭비된다(?)

    1. 클라이언와 서버는 모두 listen 상태
    1. 클라이언트가 시퀀스 시작 번호를 임의로 설정해서 서버에게 전송
    1. SYNbit(세그먼트 필드의 SYBbit를 1로 설정하면 synchronize 메시지임)와 임의의 시퀀스 번호를 서버에게 전송. (클라이언트는 SYNsent 상태로 바뀌게 된다)
    1. 서버는 클라이언트가 보낸 세그먼트를 잘 받으면 SYNreceive 상태가 되고 다시 클라리언트에게 SYNbit=1과 서버가 send할 대 사용할 임의의 시퀀스번호 그리고 다음에 받을 시퀀스 넘버 + 1 = ack를 보낸다.
    1. 클라이언트가 SYNack(x)를 수신하면 서버의 connection accept를 확인하고, 서버와 다시한번 확인하기 위해서 ACKbit와 서버가 다음에 받길 원하는 ACKnum = Y+1을 보내고 SYNSENT에서 ESTAB 상태가 된다.
    1. 서버도 클라이언트가 보낸 ack를 받으면 클라이언트의 의사를 재확인 ESTAB 상태로 전환한다.

    ※ Piggy backing 방식: 데이터 세그먼트와 컨트롤 세그먼트를 합쳐서 보내는 방식

    1. 연결의 종료할 때는 어떻게 해야할까? 클라이언트와 서버 각자가 각각 접속을 종료할 수 있다. FIN(Finish) bit = 1로 설정해서 전송.

    2. Fin을 받으면 받은 receiver(sender와 receiver 상관 없음. 서버는 클라이언트든 받은 쪽이 receiver임)는 보낸 상대방에게 FIN=1 과 ack를 전송해서 확인했다고 알려준다.

    1. 클라이언트가 '나는 보낼 데이터가 이제 없어, 하지만 너가 보낸 데이터를 받을 수도 있어(재전송 등)' 의사 전달(FINbit = 1, receiver가 받을 것을 예상하는 시퀀스 넘버)
    1. 서버가 해당 메시지를 수신하면 ESTAB → CLOSE_WAIT 상태로 변경하고 '클라이언트의 접속 종료 의사'를 확인했다고 ack를 전송한다.
    1. 클라이너트는 서버가 보낸 ack를 받으면 FIN_WAIT1 → FIN_WAIT2 상태로 변경하고 서버는 아직 데이터 송신이 안 끝났을 수도 있으므로 서버의 데이터를 계속 수신한다.
    1. 서버 역시 데이터 송신이 모두 완료됐으면 클라이언트에게 FINbit=1와 시퀀스 넘버를 전송. CLOSE_WAIT → LAST_ACK 상태로 변경.
    1. 클라이언트가 서버가 보낸 FINBIT=1과 시퀀스 번호를 받으면 서버에게 서버의 접속 종료 의사를 잘 받았다는 ack를 보낸다.
    1. 클라이언트의 ack를 받으면 서버는 접속을 종료하지만 이미 이전에 클라이언트의 send는 종료되었고 이번에 자기의 send finish로 인해서 보낼 것도 없으므로 접속을 종료한다. (CLOSED)
    1. 하지만 클라이언트는 send data는 이미 이전에 종료되었지만, receive data는 방금 종료되었기 때문에 네트워크 딜레이로 인해서 아직 못 받은 데이터가 있을 수 있으므로 max segment lifetime * 2 시간 동안 대기하고 접속을 종료한다!!

'CS > 네트워크 수업' 카테고리의 다른 글

컴퓨터 네트워크 8주차  (0) 2021.04.23
컴퓨터 네트워크 7주차  (0) 2021.04.14
컴퓨터 네트워크 5주차  (1) 2021.03.29
컴퓨터 네트워크 3주차  (1) 2021.03.23
컴퓨터 네트워크 2주차  (1) 2021.03.23
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유