아롱이 탐험대

Transmission Control Protocol (TCP) (1) 본문

CS/Computer network

Transmission Control Protocol (TCP) (1)

ys_cs17 2022. 3. 22. 14:25
반응형

[그림 1] OSI 7계층 프로토콜

OSI 7 계층의 프로토콜은 [그림 1]과 같다. 프로토콜은 이전 포스트의 내용과 같이 일종의 통신 규약을 의미한다.

이번 포스트에서는 OSI 7 계층 중 Transport layer에 속하는 TCP를 중점적으로 알아보자.

TCP는 현재 transport layer 뿐만 아니라 전체적인 네트워크 통신에서도 핵심적인 역할을 하고 있는  프로토콜이다.

[그림 2] 잘 알려진 port numbers

컴퓨터에서 네트워크를 사용하는 모든 process들은 port number를 부여받아야 한다. port number가 없으면 통신이 불가능하다.

예를 들며 설명하자면 IP는 회사 대표 번호, port는 내선 번호로 생각하면 된다.

클라이언트와 웹서버와 통신할 때 클라이언트는 0부터 1023까지 port number가 지정되어 있어 자동으로 할당해준다. 이때 웹사이트는 80번이고 port number를 몰라도 된다. 하지만 웹 서버에서는 클라이언트의 port number를 알고 있어야 한다. 또한 웹 서버와 같이 전용 port number가 있는 서버가 아닌 일반적인 다른 서버들은 port number를 알아야 한다. 하지만 대부분의 프로그램에서는 port를 자동으로 할당한다.

[그림 3] Stream delivery

 

TCP를 설명할 때, 중요한 개념 중 하나가 바로 Stream delivery이다. Stream delivery를 통해 stream of bytes로 연속적으로 데이터가 전송되는 것처럼 보인다. 하지만 이는 거짓된 정보이다.

이를 설명하기 위해 UDP의 동작 방법을 알 필요가 있다. UDP는 boundary가 있는 delivery라고 이야기 한다. boundary가 있다는 예를 들어 5개의 사과와 5개의 바나나를 택배를 보낸다고 가정할 때 5개의 사과와 5개의 바나나를 다른 박스에 보낸다는 것과 같다. 이렇게 하나의 박스를 boundary라고 부르고, 이를 보내는 것이 boundary delivery라고 말할 수 있다. 다시 말해, Application이 만든 message가 boundary를 가지고 전달되는 이러한 형태를 boundary delivery라고 한다. UDP는 이를 통해 message 전달을 유지할 수 있다.

UDP와 다르게 TCP는 다르게 동작한다. TCP의 경우 만약 과일 7개를 담을 수 있는 박스가 있다면 사과 5개 + 바나나 2개를 한꺼번에 보낸 것과 동일하게 동작한다. 이때 받는 사람인 reciever는 무엇이 오는지 모르게 되고, reciever는 message가 쭉 보내지는 것처럼 느낀다.

[그림 4] TCP 버퍼의 동작 (1)

실제 TCP의 네트워크 통신은 양방향 통신이 이루어지지만, 위 [그림 4]는 단순 이해를 위해 request 과정만 보여준다. sender 쪽 (왼쪽)에 있는 buffer를 sending buffer, 받는 쪽에 있는 buffer (오른쪽)를 receiving buffer라고 하는데, 한 프로세스에는 sending, receiving buffer 모두 존재하지만, 이번 예시에서는 생략한다.

packet은 다음과 같은 과정을 통해 전달 된다.

Application -> sending buffer -> receiving buffer -> Application

수신받은 Application은 packet의 내용을 확인한 후 request에 대한 응답을 위 flow와 같이 receiving application에 전달한다.

위 [그림 4]에서 buffer를 보면 크게 3가지 영역으로 나누어진다. (Sent, Not sent, Empty)

TCP에서 buffer가 하는 역할은 상대방이 데이터를 받지 못했을 때 재전송하는 역할을 한다. Sent 영역은 재전송이 필요할 때, Application으로부터 해당 데이터를 요청할 수 없기 때문에 sent 영역으로부터 데이터를 가져와 재전송하기 위한 영역이다. (일종의 백업용 영역이라고 생각하면 된다.) Not sent 영역은 미전송 된 데이터 영역이고, Empty 영역은 말 그대로 빈 공간이다.

TCP 영역은 kernel 영역으로 Application은 control하지 못한다. Network programming이라고 하면 Application 영역에서 프로그램을 작성하는 것을 의미한다.

[그림 5] TCP 버퍼의 동작 (2)

Application 입장에서 보게 된다면 message를 3개 보냈는데, network를 거치면서 2개로 re packaging 되어 받는 입장에서는 2개의 packet으로 나누어 받게 되고, 해당 packet을 unpack 하게 된다면 3개의 message가 있는 그러한 구조라고 생각할 수 있다. (위에서 말한 TCP는 boundary가 없기 때문에 생기는 문제이다.) 여기서 발생하는 송신부와 수신부의 송수신 data의 개수 차이에 민감한 application이 있을 수 있음을 인지해야 한다.

Application 영역에서는  data가 어떻게 보내지는지 모른다. 앞에서는 stream of byte라고 설명했지만 실제로는 [그림 5]와 같이 packet 형태로 만들어져서 보낸다. 이 packet의 이름을 transport layer에서는 segment 혹은 user-datagram이라고 하는데, TCP에서는 segment라고 부른다.

receiving buffer에서는 packet들이 network를 통해 들어오면, 먼저 buffer에 저장한다. receiving buffer에서 Not read 부분은 Application에 아직 전달되지 않은 부분이고, packet이 들어오면 Not read에 push back 하여 들어간다. 여기서 Application이 read 하게 되면 제대로 된 data만 전달한다. 만약 packet이 전송되지 않았다면 해당 부분 전까지의 데이터만 Application에 전달하게 된다.

이 buffer들은 프로세스 1개당 별도로 만들어지고, 데이터를 보내기 전에 만들어진다.

 

위 내용을 정리하자면 TCP의 buffer는 data에 대한 누락을 책임지고, 재전송을 통해 보완한다. 여기서 말하는 데이터는 세그먼트를 의미하고, 이 세그먼트는 TCP에 의해 숫자가 붙여진 각 connection을 통해 전달이 된다. 여기서 사용되는 각 connection에도 판별을 위해 번호가 필요한데 이는 랜덤적으로 결정된다. 

 

위 설명과 같이 segment에 부여하는 번호를 sequence number라고 하는데, 이는 임의의 랜덤 한 시작 번호에서 packet size 단위로 더해진 값이 된다.

만약 sequence number가 10,001부터 시작하고 packet size가 1,000 byte라면 2번째 segment의 시작 번호는 11,001이 된다. 3번째 segment는 12,001이 된다.

segment에서 정의된 sequence number field의 값은 그 segment가 가지고 있는 첫 번째 data byte로 할당된다.

위 예시에서 첫 번째 segment의 sequence number field에 있는 값은 10,001이다.

 

TCP 통신에는 ack (acknowledgement)라는 것이 존재한다. 한국어로는 응답 문자이다.

ack 번호는 receiver가 다음번에 받고 싶은 번호로 정의된다. 이러한 방식을 cumulative 방식이라고 한다.

TCP가 Cumulative ack를 선택했다고 해서 다른 방식인 selective ack에 비해 더 좋은 것이 아닌 서로의 장단점이 존재한다.

 

Selective ACK

Selective ACK에서는 송신자가 101번을 보낸다고 하면 수신자는 101번을 받았다는 의미로 ACK 101번을 보낸다. 송신자가 201번을 보내면 수신자는 똑같이 201번을 송신자에게 보낸다.

여기서 101, 201번은 packet의 첫 번째 data byte인 sequence number이다.

 

Cumulative ACK

Cumulatice ACK는 송신자가 101번에 해당하는 packet을 보내면, 수신자는 다음번에 받고 싶은 byte 번호를 응답으로 보낸다. 만약 delivery가 되는 byte가 100이면 수신자는 송신자에게 다음 받고 싶은 sequence number인 201번을 보낸다. 그다음에는 송신자가 

201번을 보내면 수신자는 송신자에게 301번을 보낸다.

여기서 수신자가 송신자가 보내는 번호의 의미는 바로 전 (예를 들어 200까지 받았으면 201번을 보내는 것)까지 데이터를 잘 받았다는 의미를 내포하고 있다.

따라서 301번을 받은 송신자는 301~400번까지의 packet을 만들어서 보내주게 된다.

 

TCP segment format

 

[그림 6] TCP segment format

TCP의 segment는 Header와 Data로 이루어져 있다. data는 application에서 buffer로 write 된 데이터이고, header는 [그림 6] 아래와 같이 구성된다. header에 있는 32bit의 Sequence number는 앞서 설명한 값이 할당된다. 초기 segment는 랜덤 값이 배정되고, 그 다음 데이터는 다음 segment의 첫 번째 값이 할당 된다.

또한 header에는 32bit의 ack number field도 존재한다. 이는 앞서 설명한 cumulative 방식의 ack number가 할당 된다.

header는 기본 20byte로 구성된다. Data 부분은 payload라고 부른다. payload는 packet, segment, user-diagram, frame 뭐든 간 payload라고 부른다. 각 layer마다 보는 payload의 부분은 조금씩 다르다.

TCP는 process to process communication을 하는 양방향 통신이기 때문에, header에 송수신 port number (Source port address, Destination port address)를 필요로 하고, 앞서 설명한 sequence number, ack도 필요하다.

다음으로는 HLEN인데 위 설명과 같이 header의 길이는 20~60 byte로 유동적인데, header의 길이를 알려주고자 HLEN이 필요하다. 만약 HLEN이 없으면 header field와 data field를 구분하지 못하게 된다. HLEN은 4bit로 0부터 15까지 값을 표현할 수 있다. Header가 최대 60byte이기 때문에 이를 위해서는 원래라면 HLEN을 6bit까지 사용해야 하지만, bit 수를 아끼기 위해 4bit만 할당한다. 만약 header의 길이가 60byte라면 HLEN은 60에서 나누기 4를 한 15이고, 이를 이진법으로 전환하면 $15=1111_{(2)}$이 된다. 실제로 데이터가 수신되면 HLEN에 곱하기 4를 해서 header의 길이를 파악한다.

 

TCP control field

[그림 7] TCP control field

다음은 TCP control field이다. [그림 6]에서 reserved 오른쪽에 있는 부분이다. 6개의 비트 중 가장 많이 쓰이는 ACK, SYN, FIN에 대해 살펴보자.

ACK: Acknowlegement is vaild라는 신호로 데이터를 보낼 때, header의 ack number를 꼭 확인하라는 의미로 ack bit를 1로 설정한다. 만약 ack bit가 0이라면 ack number field 값은 garbage 값으로 본다.

SYC: 연결 요청 packet

FIN: 연결 종료 packet

이 2개는 뒤에서 자세히 다룰 것이다.

 

CheckSUM

다음은 [그림 6]에 있는 16bit로 이루어진 checkSUM이다. 설명하기 전 HLEN과 Control field 사이의 reserved 영역은 나중을 위해 예비로 남겨둔 영역으로만 알고 넘어가면 된다.

CheckSum은 packet들이 전송되다가 물리적으로 어떤 bit가 0에서 1로, 1에서 0으로 바뀌는 등 전송 중 오류가 발생할 수 있기 때문에 이를 check 하기 위해 필요한 것이 checkSUM이다. 일종의 데이터 변종 보완 장치라고 생각하면 된다.

TCP는 header에 pseudoheader라는 것을 붙인다. pseudoheader에는 송수신 IP 주소 (32bit) 프로토콜, TCP length가 들어간다. 이러한 정보들은 사실 IP 헤더에 들어가는 정보들인데, 더 확실하게 check 하기 위해 pseudoheader를 붙여 checksum을 구하고 보낼 때는 pseudoheader를 제외하고 TCP Header만 보낸다.

TCP에서 CheckSUM을 구하는 방식은 IP에서 CheckSUM을 구하는 개념과 같다. 먼저 IP에서 CheckSUM을 구하는 방식은 데이터를 16bit씩 끊어 읽어온다. 그다음 모든 값을 더해고 이 더한 값들을 보수를 취한 값이 CheckSUM이 된다. 이렇게 구한 값을 CheckSUM field에 담아 packet을 보내게 된다.

TCP에서 CheckSUM을 구할 때는 pseudoheader, header, data 모두를 16bit씩 읽어 CheckSUM을 구한다. 수신자가 CheckSUM를 확인할 때는 CheckSUM field를 제외한 부분을 다시 16 bit씩 읽어 sum을 구한 뒤 보수 값을 취한 값이 checksum과 같다면 전송 중에 에러 없이 데이터를 수신한 것이고, 다르다면 에러가 발생한 것으로 인지한다.

checkSUM을 통해 어떤 bit가 잘못되었는지는 모르지만, 오류의 여부는 알 수 있다. 만약 오류가 생겼으면 해당 packet 전체를 drop 한다.

TCP에서 CheckSUM은 필수적인 요소이지만, UDP의 CheckSUM는 optional 하다.

 

이제부터는 중간중간 캡슐화라는 단어가 많이 사용되는데 이는 Application layer의 데이터가 TCP Segment의 data 영역으로 들어가는 것과 같이 Segment, Datagram, Frame으로 감싸지는 것을 말한다. 위에서 언급한 바와 같이 각 data 부분은 payload라고 부른다.

 

Connection establishment using three-way handshake

[그림 8] Three-way handshake

TCP는 양방향 통신을 위해 SYN->ACK->SYN->ACK로 진행해도 되지만, server to client 통신을 SYN과 ACK를 나누어서 할 필요가 없기 때문에 한꺼번에 SYN, ACK를 보낸다. 이렇게 SYN에 대해서 packet이 정상적으로 전달되었는지 확인하기 위해 서버도 sequence number를 부여한다. SYN packet을 보낼 때는 ACK를 받기 위해 sequence number를 부여해야 하는데, 이 값은 이전에 임의의 값으로 초기화된다고 앞에서 설명했다. 그림에서 client가 선택한 번호가 8000이고, server에서 선택한 번호가 15000이라는 의미이다. SYN seq: 8000에 대해 server에서는 SYN, ACK, ack: 8001, seq 15000으로 응답하고, 이에 대해 client에서 ack로 seq: 15000에 다음 번호인 ack: 15001로 응답한다.

맨 마지막 client에서 보내는 seq: 8000 값은 의미가 없다. 이를 다시 말하면 ACK 만 보낼 때 seq는 필요 없다.

ack 번호에 대해 seq의 다음 값을 보내는 것은 데이터를 확인했다는 것을 상대에게 알려줬다는 의미이다.

이를 다시 정리하자면 아래와 같다.

1. SYN을 통해 연결을 요청함. seq number를 통해 추후 연결 요청이 되었는지 확인

2. ack number를 받은 seq number에 다음 번호로 할당하고 ack을 통해 연결을 수락한다. 이때 서버 쪽도 클라이언트에게 연결을 요청한다. 이때도 seq number를 보내는데 그 이유는 연결 요청을 확인하기 위해서다.

3. ack을 통해 응답을 하고, 요청이 온 것을 확인했다는 의미로 ack과 ack number를 보낸다. 이때 seq number는 무시해도 된다.

 

여기서 주의할 점은 SYN과 seq number는 정말 다르다는 것이다. SYN은 연결 요청 패킷을 의미하고, seq는 packet에 붙는 번호이다.

 

Data Transfer

[그림 9] TCP data transfer

이전 [그림 8]과 이어지는 과정이다. 앞서 Selectice ACK와 Cumlative ACK을 볼 때 sender의 request 하나에 대해 ACK response가 하나씩 오는 것으로 설명했는데, 실제로는 여러 request에 대해 1개의 ACK response가 발생한다. 이 과정은 조금 있다가 살펴보고 지금은 pack 전송에 대해 ack number가 어떻게 바뀌는지 확인해보자.

[그림 8]과 달리 [그림 9]에서는 ACK packet에 대한 header size가 20byte이다 보니, ACK 신호에 data도 같이 보낸다.

receiver의 응답에 대해 중점적으로 살펴보면, server가 data bytes: 15001~17000를 seq: 15001로 설정하여 보내려는 찰나에 client가 request로 보낸 byte가 9001~10000이라 ACK: 10001로 설정하여 ACK 신호와 같이 Data를 전송한다.

ACK packet을 수신하면 먼저 Control 영역의 ACK bit를 확인하고 ACK number를 확인한다. ACK: 10001을 통해 client가 이전에 보냈던 packet들에 대한 수신 여부를 확인한다. 이후 packet data에 대한 seq: 15001과 data byte 15001을 확인하고, client에서 마지막 data byte 17000에 의해 ack: 17001을 설정하여 control 영역의 ACK bit를 1로 설정하여 packet을 전송한다.

 

[그림 5] TCP 버퍼의 동작 (2)

종료 과정을 살펴보기 전, TCP segment의 buffer에 대해 추가적으로 살펴보자. web browser는 tab에 따라 child process를 생성하여 client를 만들어 낸다. server와 connection establishment가 이루어진 이후에 TCP sender, receiver buffer가 만들어져 process 별로 관리가 된다. 이렇게 buffer가 만들어 준비시켜둔 후에 data를 보내게 된다. 그렇기 때문에 연결 과정은 반드시 필요하고, 이를 위해 3 way-handshake 과정을 거쳐 각 connection 마다 buffer들이 생성된다.

 

Connection termination using three-way handshake

[그림 10] 3-way handshake 종료 과정

이제 종료 과정에 대해 알아보자. 연결 종료 또한 3-way handshake로 이루어진다. 앞서 client와 server는 connection establishment 이후에 만드는 buffer가 있다고 설명했다. client는 서버에 대해 하나의 buffer set만을 유지하지만, 서버는 여러 개의 conection을 가질 수 있기 때문에 여러개의 buffer를 만들어 관리한다. 이러한 상황에서 하나의 client와의 연결을 종료하기 위해서는 서버에서 해당 client를 위해 만들어둔 buffer를 없애야 한다. 이를 위해 client는 연결 종료를 위해 확실하게 연결 종료를 server에 알려줘야 한다. 이 과정이 connection termination using 3-way handshake이다.

연결 set up 때 SYN과 마찬가지로, client는 종료 요청 packet (FIN)을 보낸다. FIN packet에 seq: #X로 보냈기 때문에, 서버의 ack number는 x+1이 되고, FIN 요청에 대한 승인으로 ACK을 보낸다. 또한 서버도 종료 요청을 위해 FIN을 설정하여 FIN+ACK를 보낸다. 이 패킷을 받은 client는 FIN 요청에 대해 ACK packet을 만들고 ACK: y+1로 설정하여 ACK packet을 서버로 전송하여 연결 종료가 이루어지면서, 해당 connection에 대한 서버의 buffer가 지워지게 된다.

연결 종료는 3-way 또는 4-way가 될 수 있다. 3-way와 4-way의 차이는 종료 시 server에서 client에게 ACK과 FIN을 같이 보낼지, 또는 따로 보낼지에 따라 달라진다.

또한 위 그림과 같이 종료 과정에서도 seq number가 사용된다. FIN segment는 data를 전달하지 않는다면, 1개의 seq number를 소비한다. FIN+ACK segment도 데이터를 전송하지 않는다면 똑같이 1개의 seq number를 소비한다.

 

TCP의 전체적인 동작을 본다면 먼저 server는 socket (make socket), bind (bind ip, port to socket), listen (change socket to server socket), accept (waiting for connection establishment)를 한다.

client는 socket (make socket), connection (request connection establishment)를 통해 connection을 만들고, read, write를 통해 데이터를 주고받는다. 서버 또한 read, write로 데이터를 주고받을 수 있다.

연결 요청은 client의 connect()를 통해, 연결 종료는 client의 close()를 통해 이루어진다.

 

Harf-close

[그림 11] Harf-close

원래 close 요청은 client가 먼저 한다. 예를 들어 고객 센터에 전화를 거는 경우, 상담사가 먼저 끊는 일은 거의 없다. 따라서 서버는 먼저 connection을 close하지 않는다. 그렇기 때문에 client가 FIN packet을 보낼 때 보통 FIN+ACK packet을 가지고 응답하지만, 보내야 하는 data가 남아있는 경우 ACK로 응답하고, Data 전송 과정을 모두 수행한 후에 FIN packet을 전송하여 close 한다.

여기서 client가 먼저 FIN packet을 보냈기 때문에, 그 이후로는 server로 packet을 전달할 수 없다. 대신 서버는 FIN packet을 보내지 않았기 때문에 ACK을 한 뒤에 packet을 더 보낼 수 있다. 이 상황에서 client는 서버로부터 packet 수신에 대한 ACK 신호만 보낼 수 있다. server의 packet 전송이 다 완료된 이후에 FIN packet을 보내 ACK를 받게 되면, connection이 종료된다.

 

 

 

 

Reference

[그림 1]: https://github.com/gyoogle/tech-interview-for-developer/blob/master/Computer%20Science/Network/OSI%207%20%EA%B3%84%EC%B8%B5.md

[그림 2]: https://captcha.tistory.com/5

[그림 3]: http://www.myreadingroom.co.in/notes-and-studymaterial/68-dcn/849-tcp-services.html

[그림 4], [그림 5]: http://www.myreadingroom.co.in/notes-and-studymaterial/68-dcn/849-tcp-services.html

[그림 6]: https://www.geeksforgeeks.org/services-and-segment-structure-in-tcp/

[그림 7]: https://yonghyunlee.gitlab.io/temp_post/network-8/

[그림 8], [그림 9], [그림 10], [그림 11]: https://m.blog.naver.com/trvlnbh/222102617762

반응형
Comments