티스토리 뷰

공부

컴퓨터네트워크 Chapter 15

Bluesion 2022. 10. 11. 22:38

TCP란?

Transmission Control Protocol의 약자. Transport layer에서 일어나는 일들이기 때문에 segment 단위로 이루어진다. (Segment 대신 packet이라고도 부름)

 

Port

포트번호는 사람의 전화번호와도 같다고 생각하면 된다. 이미 200개의 Well-known port들이 존재한다.

 

TCP에서의 데이터 전송

TCP는 stream delivery 방식을 이용하기 때문에 UDP의 boundary delivery처럼 한 패킷에 원하는 데이터가 들어가 있는 것이 아니다. 바이트 스트림은 독립적이기 때문에 Sender가 패킷 하나로 데이터를 보내도, Receiver는 패킷 여러개로 받을 수 있다는 것이다.

 

Buffer

TCP는 양방향 소통을 하기 때문에 Sender와 Receiver가 동시에 될 수 있다. 그렇기 때문에 연결을 하면 Sending buffer와 Receiving buffer가 만들어진다. 이미 보낸 데이터도 sending buffer에 잠깐 남아있는데, 이는 packet loss 등의 이유로 상대방이 데이터를 받지 못했을 경우 다시 보내기 위함이다. Receiving buffer는 데이터가 제대로 들어오면 application으로 보낸다. 중간에 패킷이 빠져서 온전한 데이터가 아닌 경우, 패킷이 빠진 이전까지 제대로 들어온 데이터를 application으로 보낸다.

 

ACK

Acknowledgment의 약자이다. 정보를 받으면 그 정보를 잘 받았다고 상대에게 알려주는 것이 ACK인데, ACK에는 2종류가 존재한다.

 

Selective ACK - 101번 패킷을 받으면 101번 ACK을 보낸다. 즉, 받은 sequence number를 그대로 ack number로 활용한다.

Cumulative ACK - 현재 TCP에서 사용하는 ACK 방식이다. 101번 패킷을 받으면 201번 ACK을 보낸다. 즉, 다음에 받아야 하는 sequence number를 ack number로 활용하는 방식이다.

 

각 ACK은 장점과 단점을 가지고 있는데, 이는 다음과 같다.

ACK 장점 단점
Selective ACK 잘못된 ACK을 쉽게 판별할 수 있다. 모든 패킷에 대해 ACK을 보내야하기 때문에 overhead가 크다.
Cumulative ACK 2개 이상의 패킷에 대한 ACK을 한 번에 보낼 수 있다. 중간에 잘못된 패킷이 있다면 문제가 되는 패킷 이후의 패킷을 전부 다시 받는다. 재전송에 상당한 시간이 걸린다는 뜻이다.

 

TCP Header

TCP 헤더는 기본적으로 20바이트(= 160비트)이며, 옵션을 통해 80바이트까지 늘릴 수 있다.

헤더는 아래와 같이 생겼다.

Source port number (16bits) Destination port number (16bits)
Sequence number (32bits)
Ack number (32bits)
HLEN (4bits) Reserved (6bits) Controls (6bits) Window size (16bits)
Checksum (16bits) Urgent pointer (16bits)
Options

 

TCP 연결 과정

세한 코드는 echo_server2.c와 echo_client2.c 확인할 것: 외우기

 

<Server>

serv_sock = socket(PF_INET, SOCK_STREAM, 0);

bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr));

listen(serv_sock, 5);

clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr,&clnt_addr_size);

while((str_len=read(clnt_sock, message, BUF_SIZE))!=0)
    write(clnt_sock, message, str_len);

close(clnt_sock);

close(serv_sock);

 

<Client>

sock = socket(PF_INET, SOCK_STREAM, 0);

connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));

fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);

write(sock, message, strlen(message));
str_len=read(sock, message, BUF_SIZE-1);
message[str_len]=0;
printf("Message from server: %s", message);

close(sock);

 

---

 

<연결>

Client SYN -> Server SYN+ACK -> Client ACK

이때 서로 window size를 주고 받는다. (Window size = min(cwnd, rwnd))

 

<데이터 전송>

데이터를 전송할 때는 6개의 Rules가 존재한다.

 

Rule 1: 데이터를 받고 ACK을 보낼 때, 보내야 할 데이터가 있다면 같이 보낸다.

Rule 2: 보내야 할 데이터가 없을 때, ACK을 보내기 전에 50ms를 기다렸다가 보낸다.

Rule 3: 패킷 2개당 최소 1개 이상의 ACK을 보낸다. (2:1)

Rule 4: Packet loss 상황에서는 곧바로 ACK을 보낸다.

Rule 5: Loss packet을 받았다면 곧바로 ACK을 보낸다.

Rule 6: 중복 패킷을 3번 받으면 곧바로 ACK을 보낸다. (Fast retransmission)

 

모든 패킷에는 타이머가 존재하고, 정해진 시간까지 해당 패킷에 대한 ACK이 오지 않으면 패킷을 재전송한다.

상대방으로부터 ACK이 오지 않아도 Window size만큼 계속 데이터를 보낼 수 있다. (윈도우 사이즈는 계속 변한다)

 

데이터를 전송하다보면 Silly window syndrome에 빠질 수 있다. 이를 해결하기 위한 3가지 알고리즘이 존재한다.

Nagle algorithm 보내는 데이터의 크기가 너무 작을 때 사용하는 알고리즘이다. 처음 보낼 땐 무조건 보내고, 상대로부터 ACK이 들어올 때까지의 시간을 잰다. 이후부터는 MSS만큼의 데이터가 쌓일 때까지 기다렸다가 한 번에 보내고, 만약 첫번째에 측정했던 maximum waiting time이 지나면 MSS만큼의 데이터가 쌓이지 않아도 보낸다.
 Clark algorithm 수신자의 Receiving buffer에 빈 공간이 부족할 때 사용하는 알고리즘이다. Receiving buffer의 절반이나 MSS만큼의 공간이 비어 있을 때만 rwnd 값을 보내고, 그 외엔 rwnd 값을 0으로 보내 sender를 stop 시키는 알고리즘이다.
ACK delay 수신자의 Receiving buffer에 빈 공간이 부족할 때 사용하는 알고리즘이다. 데이터를 받고 ACK을 보내야하는데, ACK을 일부러 보내지 않고 지연시켜 sender를 stop 시키는 알고리즘이다. 이때 ACK을 보내지 않는다고 해서 sender가 곧바로 stop 되는 것은 아니다. Sender는 여전히 Window size만큼 ACK 없이 보낼 수 있다.

Clark algorithm으로 rwnd = 0을 보내고, sender가 stop 한 상황에서, 다시 receiver의 receiving buffer에 자리가 비면 rwnd 값을 정상으로 보낸다. 그런데 정상적인 rwnd가 적힌 ACK이 loss 된다면 sender와 receiver가 서로를 기다리는 교착상태에 빠지게 된다. 이런 경우를 막기 위해 Persistent timer (영속 타이머)가 존재한다. rwnd = 0을 받았을 때 타이머를 시작하고, 제한시간이 다 될 때까지 ACK이 오지 않으면 probe를 보낸다.

 

Keepalive timer도 존재하는데, 서버와 클라이언트가 아무런 세그먼트 전송 없이 2시간 이상을 연결하면 서버 측은 클라이언트가 살아있는지 확인하기 위한 probe를 보낸다.

 

SYN Flooding - Client 측에서 IP 주소를 임의로 조작하여 서버로 SYN을 여러개 보내면, 서버는 연결 요청 대기 큐에 넣고 Client의 ACK을 기다린다. 하지만 클라이언트가 IP 주소를 임의로 조작하였기 때문에 ACK은 오지 않아 연결 요청 대기 큐가 비워지지 않고 점점 꽉 차는 현상이다. 큐가 꽉 차는 경우 더 이상의 연결이 불가능하고, 이를 Denial of Service라고 한다.

 

<연결 종료>

Client FIN -> Server FIN+ACK -> Client ACK (3-way fin)

Client FIN -> Server ACK -> Server FIN -> Client ACK (4-way)

 

이때 Client 측은 서버와의 연결을 종료한 후, 2MSL 시간만큼 TIME-WAIT 상태로 바뀐다. 왜냐하면,

1. ACK에 대한 ACK은 없기 때문에 서버가 Client ACK을 인식하지 못했을 경우를 대비하기 위함

2. 연결이 끊어진 이후 곧바로 다시 연결했을 때, 같은 포트 번호가 할당되어 데이터를 보낼 수가 있다. 만약 서버의 데이터 처리 속도가 느리다면 서버의 receiving buffer에는 이전 데이터와 새로운 데이터가 혼합되어 들어가고, 이는 원치 않는 결과가 나올 수도 있다. 그렇기 때문에 새로운 포트 번호를 할당하기 위해 TIME-WAIT 상태로 바꾼다.

 

혼잡상태

Throughput은 우리가 흔히 알고 있는 bps이다. 초당 몇 비트를 받을 수 있느냐를 나타내는 것이 스루풋이다. Throughput은 RTT에 반비례한다. (10Mbps = 1초당 10만개 비트를 전송)

 

데이터를 많이 보내면 당연히 혼잡상태가 일어난다. 혼잡상태를 막기 위한 2가지 알고리즘이 존재한다.

Slow start 처음 cwnd를 1로 두고, threshold까지 cwnd를 2배로 증가시키며 세그먼트를 전송한다. 만약 Slow start 도중 time-out이 일어나면 cwnd는 1MSS, threshold는 반토막난다. 3 duplicated ACK 상황이 일어나면 cwnd를 반으로 줄이고 additive increase로 전환한다.
Additive increase Slow start에서 threshold까지 2배로 증가시켰다면, 그 이후로는 cwnd를 1씩 증가시키며 세그먼트를 전송한다. TIME-OUT이 일어나면 cwnd = 1, threshold를 반토막 내고 slow start부터 시작한다. 3 duplicated ACK 상황이 일어나면 cwnd를 반으로 줄이고 additive increase를 한다.

Throughput은 RTT에 반비례한다고 했는데, 그렇다면 RTT란 뭘까?

 

RTT

RTT는 Round Trip Time의 약자로, 왕복 시간을 뜻한다. 우리가 알고 싶은 건 RTO이다. RTO 공식은 다음과 같다.

 

RTO = RTTs + 4 * RTTd

RTTs = (1-a) * RTTs + a * RTTm (a는 주로 1/8)

RTTd = (1-b) * RTTd + b * (RTTs - RTTm) (b는 주로 1/4)

 

RTTm은 지금 RTT를 뜻한다.

RTTs의 초기값은 RTTm, RTTd의 초기값은 RTTm / 2이다.

 

TCP의 retransmission이 RTT를 구하는데 방해가 된다. 이를 방지하기 위해 Karn's algorithm이 등장한다.

Retransmission이 일어나면 (Packet loss가 일어나면) RTT 구하는 것을 중지한다. Loss된 패킷을 받았다는 ACK이 도착하면 그제서야 RTT를 다시 구한다.

 

이때 retransmission의 RTO는 기존 RTO의 2배를 한다. (RTO를 변경하는 것은 전체적인 흐름에 영향을 주지 않는다)

 

Options

TCP 헤더의 옵션에는 single-byte 옵션과 multiple-byte 옵션이 존재한다. Single-byte 옵션은 그냥 자리 채우기 용으로 봐도 무방하고, multiple-byte 옵션에는 MSS, Window scale factor, Timestamp, SACK-permitted, SACK이 존재한다.

 

<MSS>

MSS는 Nagle, Clark 알고리즘, cwnd에서 사용한다. MSS는 보통 536 바이트이며, 옵션으로 이 수치를 바꿀 수 있다. 그런데 연결 중간에는 못 바꾸고, 첫 연결시에만 설정이 가능하다.

 

<Window scale factor>

TCP 기본 헤더에 Window size는 16비트로 할당되어 있다. 2^16은 64K이고, 기본 헤더로만 데이터를 주고 받으면 1회당 0.5Mbps 밖에 주고 받지 못한다(RTT가 1초일 경우). 더 많은 데이터를 주고 받기 위해 Window scale factor를 사용하며, Window scale factor는 int 형식이다 (WSF가 n이면 실제로는 2^n만큼 늘어남). 마찬가지로 첫 연결시에만 설정이 가능하다.

 

<Timestamp>

Timestamp와 Timestamp echo가 존재하는데, Timestamp는 sender가 해당 패킷을 보낸 시간, Timestamp echo는 receiver가 ACK을 보낸 패킷의 timestamp를 적는다. Timestamp를 사용하면 RTT를 쉽게 계산할 수 있다. 패킷을 보내고 ACK이 온 그 시간이 RTT고, ACK Timestamp - Packet Timestamp가 RTT가 된다. Timestamp는 시퀀스 넘버가 같은 패킷이 생기는 경우, 해당 패킷들을 구분짓는 PAWS 용도로도 쓰일 수 있다.

 

<SACK>

Cumulative ACK 말고, Selective ACK을 쓸 수도 있다. SACK-permitted option을 통해 sender와 receiver가 모두 동의를 하면 SACK을 사용한다.

 

데이터가 순차적으로 들어오지 않았을 경우, 먼저 들어온 segment의 시작 번호와 끝 번호 + 1을 각각 block에 차례대로 저장한다.

 

데이터가 순차적으로 들어오지도 않았는데, 기존에 잘 들어온 데이터가 또 들어오면, 위 block에 중복된 기존 데이터의 시작 번호와 끝 번호 + 1을 적고, 아래 block에 순차적으로 들어오지 않은 segment의 시작 번호와 끝 번호 + 1을 적는다.

 

데이터가 순차적으로 들어오지도 않았는데, 순차적으로 들어오지 않은 데이터가 또 들어오면, 위 block에 중복된 데이터의 시작 번호와 끝 번호 + 1을 적고, 아래 block에 순차적으로 들어오지 않은 segment의 시작 번호와 끝 번호 + 1을 적는다.

'공부' 카테고리의 다른 글

시스템 프로그래밍 - Modular programming  (0) 2022.10.20
시스템 프로그래밍 - GCC & GDB 주요 정리  (0) 2022.10.20
Unix programming - 2  (0) 2022.10.18
Unix Programming - 1  (0) 2022.10.18
컴퓨터네트워크 Chapter 2  (0) 2022.10.11
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함