Computer Science/컴퓨터네트워크(ComNet)

[컴네/CN] TCP

gxxgsta 2023. 12. 4. 20:42
반응형
SMALL

TCP

1. TCP는 full duplex data 전송이 가능한 연결이다.

즉, 서버와 클라이언트가 연결하여 서로 소통이 가능한 양방향 소통으로

동일 연결에서 양방향으로 데이터를 전송한다.

 

MSS는 maximum segment size로 데이터 전송 시 사용되는 최대 segment의 크기를 말한다.

이 MSS는 이더넷의 프레임 크기에 의해 결정된다.

(TCP는 바이트 단위로 전송되지만 이러한 바이트를 모아 segment 단위로 전송한다.)

 

2. connection-oriented(연결 지향)적이다.

TCP는 연결 지향적이고,

연결을 생성하기 위해 3-way handshake 과정을 필요로 한다.

이때, 3번의 메세지 교환이 필요하므로 3-way handshake라고 불린다.

 

3. flow control

TCP는 흐름제어 기능을 사용한다.

수신자의 버퍼가 overflow 되지 않도록 전송량을 조절할 수 있다.

 

4. point-to-point

TCP 연결은 1 대 1 연결을 사용한다.

따라서 동일 서버에 여러 사용자가 사용을 하려면 각 사용자마다 TCP 연결을 생성해야 한다.

 

5. reliable, in-order byte steam

TCP 연결의 신뢰성을 보장하며, 이러한 기능은 TCP에서 중요한 기능 중 하나이다.

오류가 없이 전송되거나, 오류가 발생하면 이를 복구하는 것과 순서에 맞게 전송되게 하는 역할을 말한다.

 

6. pipelining

TCP 연결 시 바이트들은 연속적으로 흐른다. 즉, ACK를 미수신해도 계속해서 전송이 가능한데, 이러한 이유는 ARQ 중에서도 GBN이나 Selecte Repeat 프로토콜을 사용하기 때문이다.

 

 

7. send-receive buffer

TCP 프로토콜은 송신자와 수신자가 버퍼를 동시에 모두 가지고 있다.

 

TCP segment 구조

 

1. sourceport (16bits): 송신자의 포트번호

2. dest port (16bits): 수신자의 포트번호

 

3. sequence number (32bits): 순서번호

 

4. acknowledgement number (32bits): ACK 번호, TCP segment의 A에서 enable로 표시됨.

 

5. TCP segment (16bits)

   head len: 헤더의 길이를 나타냄

   C, E: 혼잡도를 나타냄(flag), 혼잡 감지 -> 수신자가 송신자에게 알림

   R(RST): 오류가 있을 때 연결을 재설정(Reset)

   S(SYN): 연결 설정 시 사용

   F(FIN): 연결 종료 시 사용

6. receive window (16bits): 흐름 제어 시 사용하는 필드로 수신자의 버퍼에 대해 얼마나 여유 공간이 있는지를 파악하기 위한 필드.

 

7. checksum (16bits): 오류 발생 여부를 판단하기 위한 필드.

8. urg data pointer (16bits): 긴급한 재설정 시 몇 번째 바이트에 긴급 메세지가 존재하는지 알려주는 필드.

 

9. options (32bits): 사용해도 되고, 사용하지 않아도 됨.

10. application data (20B): 실제 전송하고자 하는 데이터

 

TCP Seq.와 ACKS

TCP 헤더의 seq와 ack 필드를 통해 현재 전송하고 있는 데이터가 몇 번째인지 판단할 수 있는데,

TCP 연결은 양방향 연결이므로 오직 seq나 ack를 전송하기 위해 패킷을 전달하는 것은 낭비이다.

따라서 패킷의 헤더에 seq와 ack에 대한 정보를 함께 전송한다.

 

 

위 사진에서 Host A가 Host B에게 패킷을 전달할 때 seq를 통해 현재 패킷의 순서인 42를 전송하고, Host B로부터 78번째 패킷을 수신했다는 의미로 ack 필드에 79를 넣어 전송한다.

 

그럼 Host B는 Host A가 78번째 패킷을 잘 수신한 것으로 판단하고 seq 필드에 79를 넣어 79번째 패킷을 송신하여 방금 Host A로부터 42번째 패킷을 수신하였으니 해당 패킷을 잘 수신했다는 의미로 ack 필드에 43를 채워 보낸다.

 

이러한 방식으로 양방향 통신을 이용하여 데이터를 주고 받는다.

 

 

위 사진은 데이터 전송 시점에 와이어샤크를 통해 캡처된 패킷이다.

붉은 박스 이전 [SYN], [SYN, ACK], [ACK]는 3-way handshake부분으로 데이터를 전송하지 않는 상태이다.

 

붉은 박스 493 GET/index.html HTTP/1.1 부분에서 데이터를 전송하는데,

데이터의 길이가 493으로 나타나 있다.

이때, 14B는 이더넷 헤더, 20는 ip헤더, 20B는 tcp 헤더로 해당 길이를 모두 제거하면

439가 해당 세그먼트에서의 실제 데이터 크기가 된다.

 

이때 sequece 번호는 1로 표기되어 있지만 이는 상대적인 숫자로

실제로는 691,694,465임을 바로 아래에서 확인할 수 있다.

 

 

위 패킷에서 전송한 데이터를 수신할 때의 패킷을 캡처해보았다.

마찬가지로 [SYN], [SYN, ACK], [ACK]는 3-way handshake부분이다.

 

앞서 수신한 세그먼트의 길이가 439였다.

TCP 연결은 데이터가 바이트 단위로 전송되므로 수신자 측에서 전송할 ACK 번호는 현재 1인 ACK에 439을 더한 440이 된다. 이 또한 상대적인 숫자로 바로 아래의 691,694,904가 실제 ACK 번호이다. 이 숫자는 앞의 패킷 seq 번호인 691,694,465에 439를 더한 값과 같다.

 

 

와이어 샤크에는 TCP segment의 흐름을 시각화해주는 기능이 있다.

위 사진에서는 좌측이 client, 오른쪽이 server이다.

 

 Round Trip Time & Timeout

TCP는 오류를 탐지하고 해당 오류를 복구하는 기능을 제공한다.

이때 오류가 난 패킷에 대해서는 재전송을 진행해주는데, 이러한 재전송은 타이머가 필요하고 타이머의 시간이 timeout 되었을 때 송신자는 패킷이 손실되었다고 판단하고 패킷을 재전송한다.

 

이때, timeout 값은 RTT보다는 긴 값으로 설정해야 하지만, 너무 긴 값으로 설정하면 타이머가 종료될 때까지 송신자는 아무 것도 하지 않고 기다려야 하므로 낭비가 크다.

 

하지만 너무 짧은 값으로 설정하는 경우 ACK 신호가 도착하기 전에 timeout이 될 수 있으므로 불필요한 재전송이 발생한다.

 

따라서 타이머의 값은 RTT를 고려하여 적정한 값으로 지정해야 하는데, RTT는 어떻게 측정하는지 알아보자.

 

Estimated RTT

 

우리는 지속적으로 RTT를 업데이트 하며 예측한 값을 이용한다.

TCP segment의 전송으로부터 ack가 응답하는 시간을 측정하여 해당 시간의 평균값을 통해 RTT를 측정하는데, 이때 n으로 나누는 방식으로 평균을 구하는 것이 아닌, moving window average 값으로 측정한다.

위의 식에서 EstimatedRTT는 예측값으로 이전 예측값과 실제 측정값인 SampleRTT를 일부 반영하여 타이머를 설정하는 방식이다.

 

이때, SampleRTT는 변동이 크기 때문에 해당 값을 그대로 반영하기엔 부적절하다.

따라서 α값을 통해서 SampleRTT 값을 반영하는데, 이때 보통 α값은 1/8 즉, 0.125로 설정하여 이전 EstimatedRTT 값을 더 크게 반영하도록한다. 이는 SampleRTT에 대해 너무 민감하게 영향을 받지 않도록 하기 위함이다.

 

따라서 RTT는 위의 그래프를 따른다.

 

Timer 세팅

이제 RTT를 측정했으니 타이머(timeout)를 설정할 차례이다.

이때 timeout은 RTT에 'safety margin'을 추가하여 RTT보다 긴 시간으로 세팅한다.

 

위의 식에서 TimeoutInterval이 실제 timeout의 값이다.

방금 측정한 EstimatedRTT 값과 편차인 DevRTT값을 4배하여 더해준다.

 

DevRTT값은 이전에 측정된 DevRTT 값에 SampleRTT 값과 EstimatedRTT 값의 차이를 β만큼 더한 값이다.

이때, β값은 보통 1/4 즉, 0.25로 설정한다. 이렇게 구한 값을 4배하여 EstimatedRTT 값에 더한 결과를 timeout 값으로 지정한다.

 

신뢰성 있는 데이터 전송

TCP는 rdt(reliable data transport, 신뢰) 서비스를 제공한다.이때, ip단에서는 신뢰성을 제공하지 않아, 라우터에서는 패킷을 손실하여도 재전송하는 기능을 제공하지 않는다.

 

이때, TCP는 재전송을 통해 오류를 복구할 때 두 가지 이벤트로 복구할 수 있다.

 

1. timeout event   

timeout이 발생하면 해당 패킷을 재전송함. 반드시 timeout값이 지정되어야 한다.

2. duplicate acks   

중복된 ack가 발생하는 경우 해당 패킷을 재전송한다.

 

이때, TCP는 파이프라이닝을 통해 여러 개의 세그먼트를 ack를 미수신한 상태로 송수신 할 수 있으며 누적 ack 받는다. 이때, 누적 ack란 n번의 ack를 전송하면 그 이전의 패킷을 잘 수신했다는 의미인 것이다. (모든 패킷에 대해 ack를 전송하지 않음)

 

TCP Sender

데이터를 송신할 때에 TCP 커널에서 데이터를 수신하면 응용 계층에서 각 바이트를 세그먼트로 만들어 준 후 순서 번호를 붙인다.

 

예를 들어 1G인 파일을 전송할 때, 해당 파일을 1000B로 쪼개어 세그먼트로 만든 후 seq 번호를 메기는 것이다. 이때, seq 번호는 첫 번째 바이트 스트림의 번호이다. 

 

이때 타이머는 가장 오래된, ack 미수신인 세그먼트에 대해 실행되고, 해당 간격은 TimeOutInterval이다.

 

ACK가 미수신된 시나리오를 보자.

 

좌측의 경우는 ack 패킷이 손실되는 경우이다. 따라서 timeout 기간 동안 대기 후 ack를 미수신하자, 손실을 탐지하여 해당 패킷을 재전송하는 모습을 확인할 수 있다.

 

하지만, 우측의 경우 timeout 값이 짧아 ack가 도착하기 전에 timeout된 경우이다. 이 경우, 해당 패킷이 손실되었다고 판단하여 뒤늦게 ack 신호가 도착해도 마찬가지로 해당 패킷에 대해 재전송을 진행한다.

 

 

위 경우로 마찬가지로 ack 신호가 손실된 경우이다.

하지만 누적 ack로 인하여 100번의 ack가 손실되어도 120번의 ack를 송신자에서 수신하였기 때문에 송신자는 수신자가 이전의 모든 패킷에 대해 수신하였다고 판단한다.

 

TCP ACK 생성

RFC 1122, RFC 2581 표준에 명시된 TCP에서의 ack 생성에 대해 알아보자.

 

1. seq가 순서대로 도착한 경우 ack를 한 번 전송한 상태

ack를 지연하여 다음 세그먼트를 기다린다.(delayed ACK)

이는 너무 많은 ack 발생을 방지하기 위함으로 누적된 ack를 전송하는 방법이다.

 

2. seq가 순서대로 도착한 경우 보류 중인 ack가 있는 상태

즉시 누적된 ack를 전송한다.

 

3. 순서에 맞지 않는 segment를 수신한 경우

1, 2, 4의 순서로 segment를 수신한 경우 4번째 segment를 수신했을 때(out-of-order) ack 2를 전송하여 중복된 ack 값을 전송한다.

 

4. 이전에 순서대로 오지 않았던 segment를 재전송하여 수신한 경우

미수신하였던, 재전송된 segment를 수신하자마자 ack를 전송한다.

 

Fast Retransmit

재전송은 timeout 또는 중복된 ack을 수신한 경우에만 가능하다.

이때, 중복된 ack를 이용하여 재전송하는 방법이 Fast Retransmit(빠른 재전송)이다.

 

timeout이 발생하기까지의 대기 시간이 굉장히 길기 때문에 재전송을 위한 지연시간이 길어진다. 이는 성능이 낮아진다는 의미이다.

 

따라서 송신자는 중복된 ack를 통해 패킷이 손실됨을 탐지할 수 있는데, 중복된 ack를 수신하면 어떤 패킷이 손실됐는지 timeout이 발생하기 전에 탐지할 수 있다.

 

이때 송신자는 중복된 ack를 3개 이상 수신하면 해당 세그먼트가 손실되었다고 판단하여 해당 세그먼트를 재전송한다. 즉, Fast Retransmit는 타이머 종료 이전에 손실된 세그먼트에 대해 재전송을 실행하는 것이다.

 

 

 

Host A에서 seq가 92인 세그먼트를 전송한 후 Seq가 100인 세그먼트 전송하던 중 손실되었다.

하지만 파이프 라이닝을 통해 계속해서 데이터가 전송되고 있으므로 Host B는 연이어 오는 세그먼트를 계속해서 수신한다.

 

이때, Host B는 92번째 패킷을 잘 수신하였으므로 ack 100을 송신한다. 이후 세그먼트가 연이어서 들어오는데 기대하고 있는 seq가 100인 세그먼트가 아닌, 순서에 맞지 않는 세그먼트를 수신하고 있다.

 

따라서 Host B는 기대 중인 100을 수신하기 위해 ack 100을 계속해서 송신하고 있다. 이때 Host A는 ack 100은 정상 ack이므로 제대로 수신하지만, 이후 100번 ack를 연달아 3번 수신하게 된다.

 

이 경우에 Host A는 세그먼트 손실이 발생했다고 판단하여 타이머 종료까지 기다리지 않고 seq가 100인 세그먼트를 재전송할 수 있다.

 

TCP 흐름 제어(Flow Control)

TCP에서 송신자는 수신자의 버퍼 상태를 관찰하여 데이터를 너무 빨리, 너무 많이 전송하여 overflow가 발생하지 않도록 전송량을 제어한다. 송신자는 속도-수신자의 데이터 소비 속도를 매칭한다. 왜냐하면 응용 프로세스의 버퍼로부터 데이터를 읽는 속도가 느릴 수 있기 때문이다.

 

TCP 연결 시 수신자는 수신 버퍼를 유지한다.

 

 

위 그림은 수신자의 버퍼로 초록색 부분이 응용 프로세스로 향하는, TCP 데이터이고

파란색 부분(space room)이 수신 버퍼의 남은 부분으로 추가적으로 TCP 데이터를 받을 수 있는 공간이다.

 

수신자는 송신자에게 현재 receiver window에 얼마의 공간이 남았는지를 알려준다.

 

receiver window의 남은 공간을 계산하는 수식을 위와 같으며, 수신 버퍼의 전체 크기에서, 가장 최근에 받은 바이트에서 가장 최근에 읽은 바이트 값을 뺀 값을 또 빼준다. 즉, (수신 버퍼 전체 크기) - [(가장 최근에 받은 바이트) - (가장 최근에 읽은 바이트)]의 값을 계산하면 된다.

 

 

이렇게 계산된 값(여유공간)은 TCP 패킷의 receiver window 필드에 담겨 송신자로 전달된다.

이때, 송신자의 최대 전송 크기는 RcvWindow으로 이 경우에 수신자 버퍼 오버플로우가 발생하지 않는다.

 

TCP 연결 관리

3-way handshake: 초기 연결

 

TCP의 송수신자는 세그먼트를 교환하기 전 3-way handshake 방법으로 연결을 생성한다.

 

- 초기 TCP 변수

초기 TCP 변수에는 시작하는 seq의 번호를 지정한다.이 번호는 데이터 전달 시 해당 번호부터 시작하겠다는 의미로 랜덤으로 지정되어 항상 동일한 값은 아니다.

 

- 클라이언트클라이언트는 연결은 시작하는데, 연결 시작 명령어는 아래와 같다.

Socket clientSocket = new Socket("hostname", "port number");

 

- 서버

서버는 클라이언트로부터 연결 요청이 들어오기 전까지 대기한다.대기 명령어는 아래와 같다.

Socket connectionSocket = welcomeSocket.accept();

 

 

3-way handshake에서 연결 시 아래의 세 단계로 진행된다.

 

1. client -> server: TCP SYN segment

클라이언트는 초기 seq 번호를 지정하며, 해당 세그먼트에 전송할 데이터는 존재하지 않는다.

 

2. server -> client: SYNACK segment서버는 SYN에 대한 응답 ack과 자시 자신의 seq를 전송한다. (duplex, 양방향)이때, 서버는 버퍼를 할당하고, 서버에서의 초기 seq 번호를 지정한다.

 

3. client -> server: ACK segment클라이언트는 서버로부터 설정된 seq를 받고 해당 seq에 대한 ack 신호를 보냄으로써 연결이 설정된다.

 

 

3-way handshake: 연결 종료

 

 

TCP의 송수신자는 모든 세그먼트 공유 후 마찬가지로 3-way handshake 방법으로 연결을 종료한다.

 

- 클라이언트

클라이언트는 소켓 연결을 종료하는데, 종료 명령어는 아래와 같다.

clientSocket.close();

 

 

3-way handshake에서 연결 종료 시 아래의 네 단계로 진행된다.

 

1. 클라이언트 종료 시스템은 서버에게 FIN control segment를 전송한다.

 

2. 서버가 FIN을 수신하면 ACK으로 응답을 보낸 후 연결을 종료한 다음 똑같이 FIN을 전송한다.

 

3. 클라이언트도 마찬가지로 FIN을 수신하고 ACK로 응답한다.

이때, 클라이언트는 실제로 연결이 끊어질 때까지 대기하는 'timed wait' 상태로 들어간다.

 

4. 서버가 클라이언트로부터 ACK를 수신하면 연결이 끊긴다.

 

 

TCP 연결에서 클라이언트의 lifecycle은 위 사진과 같다.

 

 

TCP 연결에서 서버의 lifecycle은 위 사진과 같다.


참고 및 출처

https://kosaf04pyh.tistory.com/218

https://velog.io/@hsshin0602/%EC%BB%B4%ED%93%A8%ED%84%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-TCP-Timer

반응형
LIST