다음은 성공과 실패를 결정하는 1%의 네트워크 원리 를 읽고 정리한 내용입니다. 본 글은 CH2. TCP/IP의 데이터를 전기 신호로 만들어 보낸다_프로토콜 스택과 LAN 어댑터의 탐험입니다 🙌


🛺 [Story1] 소켓을 작성한다.

1. 프로토콜 스택의 내부 구성

  • 네트워크를 제어하는 다음 두 가지가 필요하다.
    1. 소프트웨어 - (OS 내장) 프로토콜 스택
    2. 하드웨어 - LAN 어댑터
  • 아래가 네트워크 계층 구조이다.
    • 어플리케이션에서 데이터 송신을 시작한다. 이때 소켓 라이브러리를 사용하여 리졸버로 DNS 서버를 조회하는 등의 동작을 실행한다.

    • OS 내부에 있는 프로토콜 스택이 그 다음 작업을 의뢰받는다.

      • TCP 혹은 UDP로 데이터를 송수신한다.
      • IP 프로토콜로 패킷 송수신 동작을 제어한다. ICMP(패킷운반시 오류 통지, 제어용 메세지) 혹은 ARP(IP에 대응하는 MAC주소 조사)로 동작한다.
    • LAN 드라이버는 LAN 어댑터라는 하드웨어를 제어한다. LAN 어댑터라는 하드웨어가 실제 송수신 동작, 케이블 신호 송수신 동작을 제어한다.


      출처: 상위 1% 네트워크

2. 소켓은 통신 제어용 제어 정보이다

  • 소켓 내부에 제어 정보를 기록하는 메모리 영역이 존재 → 통신 동작 제어용 정보를 기록한다.
    • ex. 통신 상대의 IP 주소, 포트 번호, 통신 동작 진행 상태 등등
    • 이것에 소켓의 실체이다 → 제어 정보의 집합
  • 프로토콜 스택은 위 제어정보를 참조하여 동작한다.
    • 제어 정보를 참조하여 송신 대상으로 데이터를 송신하고 응답을 기다린다.
    • 일정 시간 경과 후 응답이 오지 않으면 데이터를 재송신 한다. (즉, 경과 시간등을 기록한다)
    • 이외의 많은 역할들을 한다. (이후에 추가적으로 다룸)
  • 소켓을 만든다는 것
    • netstat(윈도우 경우) 명령어에 의해서 나오는 소켓 통신 정보에 해당 소켓에 대한 제어정보를 추가하는 것
      • netstat 명령어 입력시 Local Address에 여러 IP 주소가 나온다면 여러 LAN 어댑터를 보유하고 있다는 뜻이다.
      • IP가 0.0.0.0 으로 기재되는 것은 양측이 모두 통신을 시작하지 않아서 IP가 정해지지 않았기 때문이다.
    • 통신을 시작하는 부분의 상태를 기록하고 송수신 데이터를 일시적으로 저장하는 버퍼 메모리를 준비하는 것 등등

3. 소켓을 호출했을 때 동작

먼저 Socket 라이브러리의 socket 메서드를 호출하여 프로토콜 스택에 의뢰해 소켓을 하나 생성한다.

  • 프로토콜 스택은 소켓 한 개가 사용하는 메모리 영역을 확보한다.
  • 아직 통신 이전이면 초기 상태의 정보를 메모리에 기록한다.
  • 포로토콜 스택은 소켓에 대한 디스크립터를 어플리케이션에 알려준다.
  • 이 디스크립터를 통해 어플리케이션은 이후 데이터 송수신을 프로토콜 스택에 의뢰한다.
  • 디스크립터만 있으면 프로토콜 스택이 소켓의 통신 상태, 상대 소켓 등등의 기타 정보를 모두 알 수 있다. (어플리케이션은 해당 정보에 대해서 알 필요가 없다)

🛺 [Story2] 서버에 접속한다.

1. 접속의 의미

어플리케이션은 소켓 생성 후 connect 메서드를 호출하여 접속 동작을 시작한다. 여기서 말하는 접속 동작은 케이블 등을 연결하는 접속 동작이 아니다. 주로 필요한 회선 및 케이블은 이미 연결이 되어 있다.

접속동작이라고 하는 것은 데이터를 주고받고자 하는 대상과 필요한 정보를 주고받아서 기록하고 데이터 송수신이 가능한 상태로 만드는 것이다.

  • 초기상태
    • 아무것도 기록되어 있지 않으므로 상대 소켓에 대한 IP 주소와 포트번호에 대한 정보가 필요하다.
    • connect 메소드는 어플리케이션이 알고 있는 상대 소켓의 IP 주소(DNS 리졸버를 통해서 획득한 정보) 및 포트 번호(사용자가 이미 알고 있는 정보)를 프로토콜 스택에 알려 소켓에 기록하는 로직을 수행한다.
  • 대상 소켓B도 소켓을 생성한 후에 자신과 접속하고자 하는 소켓A를 알지 못하므로 소켓을 생성하고 소켓A가 접속을 원한다는 요청을 받기 이전까지 대기한다. 요청을 받으면 소켓B도 소켓A의 정보를 기록하여 통신할 수 있도록 한다.
  • 소켓에 접속하는 connect 수행 시 데이터 송수신 메모리 버퍼도 확보한다.

2. 소켓 앞에 제어 정보를 기록한 헤더 배치

  • 소켓에 기록되는 제어정보는 무엇이 있을까 - 이 정보는 소켓의 헤더에 포함되서 보내진다.
    • 이더넷 혹은 IP 헤더 + TCP 헤더 - 클라이언트와 서버가 통신하기 위해서 필요한 정보이다. 이 정보는 소켓의 헤더에 포함되서 보내진다.
      • 헤더 사양이 정해져있다. 점점 통신을 하면서 덧붙여간다.
    • 소켓(프로토콜 스택 메모리 영역)에 기록되는 정보
      • 송수신 동작은 진행상황
      • 어플리케이션에서 받은 정보, 통신 상대로부터 받은 정보
  • 소켓의 제어 정보에 따라서 프로토콜 스택의 동작 대부분이 결정지어 지기 때문에 결합도가 매우 높다.

3. 접속 동작의 실체

  • connect를 호출하며 상대 IP와 포트 번호를 함께 쓴다.
  • 프로토콜 스택의 TCP 담당이 상대와 정보를 주고받아 헤더에 주고받은 정보를 기록하는 다음 과정을 거친다(송신처와 수신처 포트 번호 등등의 중요한 정보)
    • 상대소켓 지정(IP주소와 포트번호)과 헤더설정 끝나면 SYN 비트(컨트롤비트)를 1로 설정한다.
  • TCP 헤더 생성 이후 프로토콜 스택 내부의 IP 담당에게 넘겨주어 패킷 송신 동작을 실행하도록 한다. (여기서 송신한 패킷은 상대서버의 IP 담당이 받아 TCP 담당에게 넘겨준다)
  • 상대 소켓의 TCP 담당은 받은 패킷의 수신처 포트번호에 적힌 소켓을 찾는다. 소켓을 지정하여 필요한 정보를 기록하고 응답을 보낸다.
  • 응답을 보낼때, 마찬가지로 SYN을 1(시퀀스 초기번호)로 설정하고 TCP 헤더에 필요 정보를 설정한다. 추가로 패킷을 성공적으로 받았다고 알리는 ACK 비트를 1로 설정한다.
  • 생성된 헤더 정보를 IP 담당에게 넘겨 다시 응답한다.
  • 응답을 받은 소켓은 SYN 비트를 확인하여 접속이 성공했는지 보고 성공이라면 접속 완료를 기록한다. 그리고 상대 소켓에 패킷을 잘 받았다는 ACK 비트를 1로 만든 패킷으로 응답한다.


출처: 상위 1% 네트워크


🛺 [Story3] 데이터를 송수신 한다.

1. 프로토콜 스택에 HTTP 리퀘스트 메세지를 넘긴다

write 메서드를 호출하여 송신하고자 하는 데이터를 프로토콜 스택에 넘긴다.

  • 어플리케이션에서 받은 데이터를 프로토콜 스택 내부의 버퍼 메모리 영역에 우선 저장한다.
    • 이유는, 어플리케이션에 건네주는 데이터의 크기는 프로토콜 스택이 제어할 수 없기 때문에 받은 데이터를 곧바로 보내면 데이터 송수신 동작이 지나치게 많이 일어나서 네트워크 효율이 떨어진다.
    • 패킷의 최대크기인 MTU에서 헤더를 제외한 MSS 만큼의 최대 데이터를 보낼 수 있다. (이것보다 작은 데이터를 보내는 경우 패킷이 예상치 못하게 나누어지지 않는다)
    • 하지만 항상 버퍼를 꽉 채워서 데이터를 보내는 경우 대기 시간이 길어지므로 송신 동작이 지연된다.
    • 네트워크 이용 효율을 중시하는지, 송신 동작 시간을 중시하는지 잘 절충해야한다. (프로토콜 스택을 구현한 OS에서 담당하며 어플리케이션 레벨에서 어느 정도 설정을 할 수도 있다)

2. 데이터가 클 때는 분할하여 보낸다

  • 데이터가 지나치게 크면 MSS 크기만큼 분할하여 패킷(헤더 + 데이터)을 만들어 보낸다.

3. ACK 번호를 사용하여 패킷이 도착했는지 확인한다

  • ACK 번호 = 수신을 완료한 바이트 + 1

  • 최초 3way handshake를 할 때 초기 시퀀스 번호를 함께 주고받는다.

    • 초기 시퀀스 번호를 악용할 수 있기 때문에 난수로 설정하여 미리 주고받는다.
  • 이후 최초 시퀀스 번호 + 데이터의 크기 만큼의 데이터를 수신했다면 그것에 대한 확인으로 지금까지 수신한 바이트 + 1 숫자를 ACK로 지정하여 응답한다.

    • 데이터의 크기는 어떻게 알 수 있을까? 보낸 패킷에 헤더길이를 빼면 수신한 데이터의 크기를 유추할 수 있기 때문에 따로 기재하지 않는다.
  • 이후 송신할 데이터를 시퀀스 번호로 지정하고 송신하고, 동일하게 수신한 마지막 바이트 + 1을 ACK로 응답한다.


    출처: 상위 1% 네트워크

  • 시퀀스번호와 ACK 번호로 누락된 패킷 여부를 알 수 있다. 만일 누락되었으면 송신 버퍼 메모리에 저장되어 있는 데이터를 재송신한다.

    • 이렇게 TCP는 누락을 검출하고 회복 처리를 한다.
    • LAN 어댑터, 버퍼, 라우터는 회복조치를 취하지 않는다. 오류가 검출되면 패킷을 버린다.
    • TCP 여러번 패킷을 재송신해도 오류가 난다면 동작을 중지하고 어플리케이션에 오류를 통지한다.
  • 양방향 통신이 이루어지는 경우 역으로 동일하게 수행하면 된다.


    출처: 상위 1% 네트워크

4. 패킷 평균 왕복 시간으로 ACK 번호의 대기 시간을 조정한다

  • ACK가 오지 않는 것으로 패킷 유실을 판단하는데 ACK를 평생 기다릴 수 없으니 타임아웃 값 만큼 기다린다.
    • 제대로 수신하였는데 네트워크가 혼잡하여 ACK가 오지 못하는 상황일수도 있으니 타임아웃 값을 적절하게 잘 설정해야한다.
    • 네트워크 혼잡으로 ACK를 받지 못했을때 패킷을 재송신 하면 네트워크에 부하를 얹는 것이다. 하지만 너무 오래 기다리면 속도가 지연의 원인이 된다.
    • 상황에 따라서 항상 다르기 때문에 대기 시간을 동적으로 변경한다.
      • 항상 ACK가 돌아오는 시간을 기록하고 그 시간이 길어지면 대기 시간도 늘리며 짧으면 대기 시간도 줄인다.

5. 윈도우(Sliding Window) 제어 방식으로 ACK 번호를 관리한다

  • http://blog.skby.net/슬라이딩-윈도우sliding-window/
  • 패킷을 하나 보내고 ACK를 기다리고 또 패킷을 보내고 ACK를 기다리는 핑퐁 방식은 시간을 소요한다. 복수개의 패킷을 보내는 슬라이딩 윈도우 기법으로 효율적으로 관리하도록 한다.
    • 핑퐁의 경우 ack가 와야만 다음 패킷을 보내기 때문에 수신하는 측의 능력만큼 패킷을 보낸다.
    • 슬라이딩 윈도우는 여러개의 패킷을 우선 보내기 때문에 수신측의 능력을 초과하여 패킷을 보낼수도 있다. 이것을 방지하기 위해서 수신측의 수신버퍼만큼만 패킷을 보내도록 슬라이딩 윈도우 기법으로 송신하는 패킷 사이즈를 조절한다.
  • 수신측이 ACK 값 산출 등의 후처리를 하는 동안 수신한 데이터를 일시적으로 수신 버퍼 메모리에 저장한다. 이때 송신측에서 수신 버퍼 메모리가 넘치도록 패킷을 보내면 수신측의 능력을 초과한 것이다.
  • 이것을 해결하기 위해 슬라이딩 윈도우 방식에서 수신 측에 빈 버퍼 최대 사이즈(윈도우 사이즈)를 TCP 헤더의 윈도우 필드에 기록하여 송신측에 알려준다.
    • 송신측에 알려주는 타이밍은 수신 버퍼에서 메모리를 추출하여 빈 공간이 추가로 생긴 타이밍이다. 점점 줄어드는 사이즈는 패킷의 데이터 사이즈를 통해서 유추할 수 있다.


      출처: 상위 1% 네트워크

6. 윈도우 사이즈 + ACK 합승

  • ACK번호와 윈도우 사이즈를 각각 다른 패킷에 송신하면 주고받는 패킷이 너무 많기 때문에 효율적이지 않다.
  • 둘 중 하나만 생성되었을 때 기다리다가 두 개가 모두 일어나면 함께 하나의 패킷으로 송신한다.
  • 복수개의 ACK가 생겼을 때도 최후의 것만 송신한다.
  • 복수 윈도우 통지가 발생해도 최후 윈도우 사이즈만 보낸다.

7. HTTP 응답 메세지를 수신한다

  • 프로토콜 스택이 HTTP 요청 메세지를 모두 보면 응답 메세지를 수신해야한다.
  • read 메서드를 호출해 프로토콜 수택이 수신 버퍼에 응답 메세지를 수신한다.
  • 응답 메세지가 일정 시간 후 도착해 수신 버퍼에 담기면 프로토콜 스택은 그것을 추출해 어플리케이션에 넘겨준다.
  • 수신 데이터에 TCP 헤더 정보를 통해 누락된 데이터가 없는지 확인하고 ACK를 응답한다. 데이터 조각을 버퍼에 보관하고 원래 데이터로 복원하여 어플리케이션에 보낸다.
  • 어플리케이션에 데이터를 추출한 타이밍에 윈도우 사이즈를 상대에 통지한다.


[용어]

패킷 - 네트워크에 운반되는 분할된 데이터의 덩어리 단위 

MAC 주소 - LAN 방식의 기기가 가지고 있는 형식의 주소 

PID - Process ID의 약자. OS가 각 프로세스에 할당하는 번호 

소켓 - 통신 파이프 양 끝에 있는 출입구와 같은 것 

MTU - Maximum Transmission Unit 패킷 하나에 운받하는 디지털 데이터의 최대길이 (이더넷 1500바이트)

MSS - Maximum Segment Size 헤더를 제외한 TCP 데이터의 최대길이