본문 바로가기
Today I learned

[네트워크 프로토콜] 함께떠나요 네트워크 프로토콜 파티 TCP

by soheemon 2020. 1. 4.

재미없는 ICMP은 건너뛰고, 재미있는 TCP로 넘어갑시다..

 

TCP

애플리케이션은 IP네트워크에서 서로 통신하기 위해 두 가지 표준 전송 프로토콜을 사용한다.

- 가볍지만 신뢰성이 없는 전송 서비스를 제공하는 사용자 데이터그램 프로토콜 (UDP)

- TCP 신뢰할 수 있는 통제된 전송 서비스를 제공하는 전송제어 프로토콜 (TCP)

 

TCP는 현재 인터넷에서 사용되는 프로토콜 가운데 가장 중요하다고 할 수 있다. 비록 IP가 필요 따라 인터넷에서 데이터그램과 패킷을 운반하는등 대부분의 실제적인 작업을 맡고있지만.. TCP는 IP 데이터그램에 올바른 데이터가 저장되게끔 보장한다.

 

한가지 흥미로운 사실은 TCP의 첫번째 버전이 IP보다 먼저 설계되었고, 실제로 IP는 나중에 TCP에서 추출되었다는 것이다.

==> 꽤 의외인 부분이다..! IP가 먼저 만들어져서 열심히 통신하다가 패킷이 중간에 손상되거나 사라지는 문제로 인해 TCP가 만들어 졌을줄 알았는데..!

 

사실 TCP는 이더넷 또는 인터넷과 같이 분산된 IP기반 네트워크를 포함한 모든 패킷 스위칭 네트워크에서 동작하도록 설계되어있다. 이런 융통성있는 설계로 TCP는 OSI의 TP4(Transport Protocl4..?) 와 애플사의 ADSP를 포함하여 여러 네트워크 구조에 채택되었다.

 

TCP, 신뢰할 수 있는 연결 중심의 전송 프로토콜

TCP나 UDP를 포함한 모든 전송계층 프로토콜이 기본적으로 IP를 사용하지만, IP는 데이터그램이나 패킷이 목적지에 올바르게 전달된다고 보장하지 않는 신뢰성이 결여된 프로토콜이다. 따라서 IP패킷이 통째로 분실되거나, 손실될 수 있다. 또한 목적지로 향하는 과정에서 데이터그램의 순서가 바뀌는등 무수한 문제가 발생할 수 있다.

 

하지만 이메일 등등 네트워크 애플리케이션은 수많은 중요 임무의 기초가 되기 때문에 자신이 전송한 데이터가 원래 형태로 도달한다는 보장을 받아야 한다.

 

이런 신뢰성은 두 애플리케이션이 통신할 떄마다 가상 회선을 제공하는 TCP를 사용하여 얻을 수 있다. (TCP 세션을 '가상 회선'이라고 표현한것이 잘 와닿지 않는다. 개인적으로 '논리적 연결'이라는 단어가 와닿는듯...) 

TCP 세션은 애플리케이션에 관리된 전이중 지점간 통신 회선을 제공한다는 점에서 전화통화와 유사하다. 두 TCP 기반 애플리케이션에 데이터를 전송할 때 둘 사이에 가상 회선이 개설되며, 애플리케이션 데이터의 교환은 고도로 감시를 받는다. 모든 데이터가 성공적으로 전송되어 수신되면 둘 사이의 연결은 종결된다.

 

당연하게도 이러한 가상 회선을 개설하고 감시하는 일은 상당한 오버헤드를 초래하여 TCP의 동작을 UDP보다 느리게 만든다.

 

TCP가 제공하는 서비스

상위레벨의 애플리케이션 자체에서 신뢰할 수 있는 서비스와 흐름 제어 서비스를 제공할 수는 있으나 실용적이지 못하다.

정말 실용적이지 못한걸까?

극단적인 예를 하나 들어보자. 어느날 파일 업로드 기능을 구현해야 하는데, 뼛속에 골수대신 의심으로 가득차있는 황씨는 리눅스 커널의 TCP 스택이 정말로 신뢰할 수 있는 서비스를 제공하는지 믿을수가 없었다. (말도안되는 소리지만;; 이해를 위해 그렇다고 하자.) 그래서 특별히 콘텐츠 서버 내에 파일업로드용 데몬을 하나 만들어서 파일 업로드를 구현후, 커널스택의 TCP스택 대신 직접 개발한 UDP 스택을 대신 태우도록 하였다.

 

결과는 어떨까? 해보진 않았지만, 아마 아래와 같은 문제가 발생할것이다.

- 네트워크 환경은 생각보다 험해서, 패킷이 손상되거나, 유실되는 경우가 발생했다.

- Client에서 분할하여 차례대로 데이터그램을 전송했지만, 콘텐츠 서버에서 받는 패킷은 차례대로 오지않았다.

- 네트워크 스택이 아닌 애플리케이션에서 기능을 구현했기 때문에 성능이 저하된다.

 

특히, 위와같은 문제를 해결하느라, 정작 중심 기능인 파일업로드에 집중 할 수 없어서, 반쪽짜리 애플리케이션이 만들어 질것이다..

말이 길어졌지만 (...) 결론을 내자면 TCP와 같은 하위레벨의 프로토콜은 개발자로 하여금 비즈니스 로직에만 집중 할 수 있도록 해준다.

 

TCP는 상위 계층 애플리케이션에 다음과 같은 다섯가지 주요 서비스를 제공한다.

- 가상 회선

두 애플리케이션이 tcp를 사용하여 통신할 떄 두 애플리케이션 사이에 가상 회선이 연결된다. 가상 회선은 신뢰성, 흐름제어, 그리고 I/O 관리 기능을 제공하여 TCP를 UDP와 구분짓게 하는 TCP 설계의 핵심이다.

 

- 애플리케이션 I/O관리

TCP는 I/O버퍼를 제공하여 애플리케이션이 데이터를 연속적인 스트림으로 송수신 할 수 있도록 한다. TCP는 연속적인 스트림을 IP를 통해 전송되는 개별적으로 감시를 받는 세그먼트로 변환한다.

 

- 네트워크 I/O관리

TCP가 다른 시스템으로 데이터를 송신할 때는 IP를 사용하여 실질적으로 패킷을 전송한다. 따라서 TCP는 IP네트워크를 통해 효율적으로 전송될 수 있는 세그먼트를 생성하고, 다시 개별적인 세그먼트를 애플리케이션에서 사용하기에 적합한 데이터 스트림으로 변환하여 IP에 네트워크 I/O 관리 서비스를 제공한다.

 

- 흐름제어

네트워크의 호스트들은 처리능력, 메모리, 네트워크 대역폭 및 기타 자원을 포함하여 서로 다른 특성을 갖는다. (내 PC 짱짱빠름!! 내 공유기 짱짱빠름!! 하더라도 사용하려는 웹서버의 성능이 좋지 않을수도 있다...) 따라서 모든 호스트가 같은 속도로 데이터를 송수신할 수 없으므로 TCP는 이런 호스트의 속도차이에 대응할 수 있어야 한다. 

 

- 신뢰성

TCP는 전송 데이터를 감시하여 신뢰할 만한 전송 서비스를 제공한다. TCP는 일련 번호를 사용하여 데이터의 개별적인 바이트를 감시하며, ack플래그를 통해 일부 바이트가 분실되었는지 여부를 판별한다.(데이터를 받았다면 ack를 보내야함..! 안그러면 수신자가 못받은 줄 알고 계속 데이터를 보냄 ㅠ ㅠ)

 

- 가상회선

TCP는 IP 계층 위에 가상 회선을 개설하고, 그것을 통해 전송된 데이터를 추적하여 IP의 취약점을 극복한다.

TCP 시스템 사이에 연결이 설정될 때마다 모든 데이터는 가상 회선을 통해 전달된다.

 

흥미로운것은, 예를들어 어떤 웹 브라우저가 HTTP 'GET' 명령어를 통해 같은 서버로부터 4개의 GIF를 요청한다고 하자. 하지만 같은 웹서버에 대한 요청이라 할지라도, 각각의 TCP 요청 및 가상회선 이 생성된다는 것이다..! (이책이 오래된 책이라 현재는 어떤지 모르겠다...) 

 

애플리케이션 I/O 관리

가상회선의 주된 특징 가운데 하나는, 애플리케이션이 직접 패킷의 크기를 결정하고 관리하는 대신, 데이터 스트림으로 정보를 송수신 할 수 있게 한것이다. 이런 특징은 애플리케이션에서 이미지 하나를 보낸다고 할때, 수많은 개별 패킷 대신 하나의 데이터 스트림으로 전송 할 수 있도록 해준다. 

반복하여 말하지만, TCP에서 이런 기능을 제공해 줌으로써, 애플리케이션 개발자들은 이런 작업을 신경쓰지 않아도 된다! 우리 잠시 눈을 감고 리눅스 커널 개발자들에게 감사하는 마음을 갖도록 하자.

 

어쨌든 TCP는 애플리케이션에 네가지 종류의 애플리케이션 I/O관리 서비스를 제공한다.

- 내부 주소 지정 : TCP는 TCP 가상 회선을 사용하는 모든 애플리케이션의 인스턴스에 고유 포트번호를 할당한다. 포트번호로 어떤 애플리케이션의 데이터인지 구분한다는것임!

- 회선 개설 

- 데이터 전송

- 회선 종결

 

TCP 포트를 이용한 애플리케이션 주소 지정

애플리케이션은 포트를 사용하여 TCP와 통신한다. TCP의 포트는 실질적으로 UDP포트와 동일하다.

애플리케이션은 다른 애플리케이션과 통신하고자 할 때, 자신에게 부여된 포트 번호를 통해 TCP에게 데이터를 전달하면서 TCP에 수신지 애플리케이션의 시스템 IP와 PORT번호를 알린다.

TCP는 필요한 TCP메시지(IP는 데이터그램, TCP는 세그먼트라고 한다.) 를 생성하여 메시지 헤더에 출발지와 목적 표트번호를 기록하고 전송 데이터를 메시지의 몸체에 저장한다. 그리고 IP 데이터 그램을 생성하기 위해 하위 프로토콜인 IP에게 전달한다.

 

수신지 시스템에서 IP 데이터그램을 수신하면, IP 소프트웨어는 데이터그램의 데이터가 TCP세그먼트임을 확인하고 세그먼트의 내용을 TCP에 전달한다. TCP는 TCP 헤더를 까보고! dst 포트번호를 확인 후! 해당 포트 번호를 사용하는 애플리케이션에 세그먼트의 데이터를 전달한다.

 

덧붙혀, 포트는 시스템에 있는 어떤 애플리케이션의 한 인스턴스를 나타낸다. '소켓'이라는 용어는 포트번호와 IP주소를 합쳐서 나타내는 것이다. 

 

회선과 포트는 서로 밀접하게 관련되어 있지만, 전혀 별개의 개체라는 것도 알고 있어야 한다. 가상회선은 두 TCP 시스템간에 관리된 전송을 제공하는 반면, 포트 번호는 애플리케이션이 TCP와 통신할 때 사용할 수 있는 주소를 제공한다. 

=> 포트번호는 애플리케이션을 구분 할 수 있게 하지만, 가상회선은 각각의 연결을 구분 할 수 있다. 

이런 이유에서 서버 애플리케이션은 하나의 포트 번호를 통해 서로 다른 여러 개의 클라이언트 연결을 충분히 지원할 수 있다. 또한 소켓은 각 포트에 고유한 것이 아니라 가상회선 별로 구분된다.

 

그림이 너무 대충이지만, 뭐 대충 살자. 대충 웹서버처럼.

위의 그림은 각각 A와 B가 웹서버에 가상회선으로 연결 된 모습이다. 두 연결 모두 웹서버에 같은 IP주소와 포트번호를 사용하지만, "두 클라이언트 시스템의 IP주소와 포트번호가 다르므로 소켓 페어 자체는 고유하다." 

=> 여기서 추측 할 수 있는 사실은, 각 세션은 "IP주소와 포트번호"로 구분 되는듯 하다. 동일한 웹서버에서 크롬 브라우저에서의 세션과, 엣지브라우저의 세션이 다르게 맺어지는것은 아마도 두 애플리케이션의 포트번호가 다르기 때문일것이다.

80번 포트는 well known port이므로 따로 설정을 변경하지 않는다면 브라우저에서 URL입력시, PORT번호를 입력하지 않았을때  암묵적으로 80번포트로 접속하는것으로 가정한다.

 

+역사적으로 오직 특권 계정에서만 1024보다 작은 포트번호를 사용 할 수 있었다.

 

회선의 개설

애플리케이션은 TCP에서 제공하는 가상 회선을 사용하여 서로 통신한다. 회선은 사용하는 애플리케이션의 필요에 따라 개설되고 종결된다. 애플리케이션은 네트워크의 다른 애플리케이션과 통신하고자 할 때 커널에 가상 회선을 개설해 달라고 요청한다.

가상 회선의 개설을 요청하는 방법에는 두 가지가 있다. 

1) sender 역할! 데이터를 즉시 전송 할때 사용합니다.

2) reciever 역할! 포트를 하나 물고 socket을 열어, lienten 상태에서 데이터가 오기를 기다려요!

 

두가지 방법 가운데 더 간단한 것은 수동적인 개설이다. 수동적인 개설은 서버가 다른 시스템으로부터의 연결 요청을 수용할 의사가 있지만, 직접 외부 연결을 시작하지는 않는다는 것을 나타낸다.

일반적으로 수동적인 개설은 "무족건 무족건이다" 이말인 즉슨 서버는 port를 물고 socket을 listen상태로 두면 아무에게나 연결 요청을 수락한다는 것이다. 

따라서 너무나 당연하게도, 보안을 위해서라면 사전에 방화벽에서 White List를 작성해야 겠다. 

일부 보안에 민감한 애플리케이션은 사전에 정의된 클라이언트로부터만 연결 요청을 받아들이는 '제한적인 수동적 개설'을 요청한다. 이런 종류의 개설은 기업 웹 서버, ISP의 뉴스 서버 및 기타 접근을 제한하는 시스템에서 찾아 볼 수 있다.

 

웹 브라우저와 같은 클라이언트 애플리케이션은 '능동적인 개설'을 사용하여 연결 요청을 한다. (일반적으로 클라이언트는 자신과 결합된 서버의 잘 알려진 포트번호로 연결을 요청한다.)

목적 포트를 사용할 수 있는 상태가 아니라면 서버 시스템의 TCP스택은 연결 요청을 거부한다. 이것은 UDP에서 ICMP Destination Unreachable : Port Unreachable 에러 메시지에 의존하는 것과 대조적이다. TCP는 명시적으로 연결을 거부할 수 있어 ICMP를 사용하지 않고도 연결 요청을 중지 시킬 수 있다.

 

서버 시스템의 TCP 소프트웨어는 연결 요청이 받아들여지면 Client에 ACK를 날리고 Client는 ACK를 받은 후 다시 서버로 ACK를 날린다. 그리고 가상회선은 전송 가능한 상태가 되어 두 애플리케이션이 데이터를 교환하기 시작한다.

 

핸드셰이킹 과정에서 사용되는 세그먼트는 보통 데이터를 포함하지 않으며, 대신 헤더에 새로운 가상 회선의 개설을 나타내는 특별한 연결 관리 플래그를 가진 길이가 0인 '명령 세그먼트'다  이런 맥락에서, 두 시스템에서 가상 회선이 개설 된다는 것을 나타내는 데 사용하는 Synchonize 플래그가 가장 중요하다고 할 수 있다. 

 

3way-handShaking

 

1) 클라이언트는 첫번째로 전송할 명령 세그먼트를 Synchronize 플래그를 설정한다. 이 플래그는 서버에게 해당 세그먼트가 새로운 연결 요청임을 나타낸다. 또 이 명령세그먼트는 TCP헤더에 Synquence Identifier 필드에 데이터의 일련번호(데이터 조각의 시작바이트 번호)를 제공한다.

 

2) 서버가 클라이언트와 3way-handshake를 맺으려는 용의가 있다면 - 서버는 클라이언트에 데이터를 보내는데 사용 할 시작 일련번호와 Synchronize 플래그를 포함하여 서버의 명령 세그먼트에는 Acknowledgment 플래그가 설정되고, Acknowlegment Identifier 필드는 클라이언트의 다음 일련번호를 가리킨다.

 

3)클라이언트는 서버를 위해 Acknowledgment 플래그를 설정하고- Acknowledgment Identifier 필드에 서버의 다음 일련번호를 가리키며 명령 세그먼트를 서버에 보낸다. 

 

데이터의 교환

일단 가상 회선이 개설되면, 애플리케이션은 서로 데이터 교환을 시작할 수 있다. 여기서는 애플리케이션이 데이터를 직접 교환하지 않는다는 점에 유의해야 한다. (그럼 누가할까! 바로 리눅스 커널 & 네트워크 스택이 하지요!)

애플리케이션은 TCP스택에 데이터를 묶음 또는 연속적인 바이트 스트림 형태로 전달할 수 있다. 대부분의 TCP스택은 제한된 크기의 쓰기 서비스를 제공하여, 애플리케이션이 하드디스크에 데이터를 쓰는것처럼 데이터를 블록단위로 쓸 것을 강요한다. 한편 TCP의 버퍼 설계는 애플리케이션이 연속적으로 데이터를 쓰도록 지원하며 몇몇 구현에서는 이런 설계가 사용되고 있다.

 

송신측 TCP스택은 애플리케이션으로부터 수신한 데이터를 전송 버퍼에 저장하여 데이터를 정기적으로 목적지 시스템으로 전송할 것이다. 수신측 TCP스택은 이 데이터를 수신 버퍼에 저장하고, 궁극적으로는 애플리케이션에 이 데이터를 전달하게 된다.

 

예를들어 웹 브라우저가 HTTP 'GET'요청을 전송할 때마다 이 요청은 서버의 TCP스택을 거쳐 서버애플리케이션으로 전달된다. TCP는 또한 전송버퍼에 데이터를 저장하고, 목적지 소켓으로 향하는 다른 데이터와 함께 패키지 크기를 결정한다.

 

정리하자면

웹브라우저 : HTTP GET 요청 > 웹브라우저는 커널에 TCP 관련 처리 요청 위임 > 커널은 전송버퍼에 데이터 저장 (생략)

웹서버 : 커널, TCP스택이 데이터 받음 > 수신 버퍼에 보관 > 서버애플리케이션에 데이터 전달

 

회선의 종결

 

- 능동적인 종결 : 내가 FIN보냄

- 수동적인 종결 : 쟤가 FIN 보냄 

 

1) 3way-handShaking을 Synchronize(SYN) 플래그를 사용했듯이 종료할때 FIN(Finish) 플래그를 사용한다. 

연결 종료를 요청하는 쪽에서 FIN 플래그가 설정되고, 연결된 상태에서의 마지막 일련번호보다 하나 높은 값의 일련번호를 포함한 명령 세그먼트를 전송한다.

2) FIN을 받는애는 역시 FIN플래그가 설정되고, 하나 증가된 일련번호가 포함된 명령 세그먼트로 응답한다. 또 이 응답에서 Acknowledgment Identifier 필드는 다른 세그먼트가 돌아오지는 않지만 다음 일련번호를 가리킨다. 이런 맥락에서 Fin플래그는 Syn플래그와 마찬가지로 한 바이트의 데이터로 취급되어야 하며 따라서 그 수신여부에 반드시 응답하여야 한다.

 

-일단 Fin세그먼트가 서로 교환되면 시스템은 마지막 Fin 세그먼트에 대한 최종 확인 메시지로 응답해야 한다. 이 최종 확인 메시지는 Acknowlegment Identifier 필드에서 다음 일련번호를 가리키지만, 가상회선이 이미 종결된것으로 간주하며 Fin 플래그를 설정하지 않는다.

 

+ 클라이언트에서 연결을 종결하는것이 일반적이지만 연결된 두 시스템의 어느 한쪽에서든 회선 종결 절차를 시작 할 수 있으며, 특벌히 어느쪽에서 연결을 종결시켜야 한다는 규칙은 없다.

POP3는 클라이언트에서 한 세션 동안 여러 명령을 전송하는 것을 허용하므로 클라이언트에서 연결을 종결하는 경우의 좋은 예가 된다. 

+ 또 서버 애플리케이션은 애플리케이션 자체가 종료될 때까지 포트를 열어놓으며 다른 클라이언트가 계속해서 서버에 연결할 수 있도록 한다.

 

staggered close

때로는 둘다 회선을 종결하지 않는 경우가 발생한다. 그런 경우 half-close 이라고도 불리는 staggered close이 발생하여 각 시스템은 다른 시점에서 수동적인 종결을 요청하게 된다.

 

Reset Flag

회선을 종결하는 다른 방법은 규칙적인 종결 과정을 거치지 않고 단순히 회선을 종결 하는것이다. Reset 플래그가 설정된 세그먼트를 전송하여 상대편에 연결이 즉시 종결되도록 한다.

 

애플리케이션 설계 문제

일부 애플리케이션은 연결을 개설한 다음 열려있는 상태를 오랫동안 유지하는 반면, 어떤 애플리케이션은 연결을 개설하고 신속히 종결하여 하나의 작업에 많은 회선을 사용한다.

 

이 예로 HTTP 1.0 를 들었지만.. HTTP 1.1에서 하나의 세션을 사용하도록 개선 되었다고 한다. HTTP1.0을 사용하면 클라이언트는 같은 connection을 사용해서 그 페이지에 포함된 css 이미지 등등(물론 같은서버에 저장되어 있음을 가정!) 을 수신 할 수 있다. 

=> 하지만 요청에 대한 객체 전송이 끝나면 짤없이 세션을 끊는다.

 

대부분의 애플리케이션은 특별한 작접이 없어도 회선을 열려 있는 상태로 유지시켜 모든 작업을 처리하는 데 하나의 회선을 사용한다. TELNET을 예로 든다. 로그인 이후에도 connection을 물고있는다.

+이는 규칙이 정해진 것이 아니라 Server 어플리케이션과 Client 어플리케이션에서 구현하기 나름이다.+

 

연결 유지

설계에 따르면 TCP 연결 유지는 TCP 시스템 가운데 한쪽에서 연결을 종결하지 않고 사라지는 것을 발견하기 위해 사용되어야 한다. (connection을 계속해서 물고 있을 수 없으니까요. 이것도 자원인데.) 이 특징은 TELNET처럼 클라이언트가 오랜 시간 동안 아무런 작업을 하지 않는데 연결이 아직 유효한 지 판별할 방법이 없는 애플리케이션에 특히 유용하다.

 

RFC1122는 연결 유지가 사용자가 설정할 수 있어야 하는 선택 사항이며, 클라이언트가 사라지는 경우 큰 손상을 입는 서버 측 애플리케이션에서만 구현되어야 한다고 규정 하고 있다. 또한 연결 유지의 구현 방법이 다를 수는 있으나 다른 구현과의 호환성을 위해 한 바이트의 데이터를 보내는 경우 오에는 어떠한 데이터도 연결 유지 세그먼트에 포함되어서는 안 된다고 밝히고 있다.

 

대부분의 시스템은..

서버가 데이터를 전송하고 나서 클라이언트로부터 ACK가 일정 시간 후에 오지 않으면 다시 보내고 그래도 끝끝내 ACK가 오지 않는다면 서버는 connection을 끊어도 된다.

 

네트워크 I/O 관리

애플리케이션은 TCP를 통해 다른 애플리케이션으로 데이터를 전송할 때 TCP 스택에 데이터를 보내고 TCP 스택은 그 데이터를 전송 버퍼에 저장한다. TCP스택은 데이터의 일부를 정기적으로 세그먼트에 담아 목적지로 배달하려고 이를 IP에 전달한다. + 세그먼트 크기 결정 등등의 과정 포함...

 

세그먼트의 크기를 정확히 결정하는 것이 가상 회선의 성능과 관련된 특성을 크게 좌우하므로 매우 중요한 서비스다.

 

댓글