본문 바로가기
CS/Network

[Network] 성공과 실패를 결정하는 1% 네트워크 원리(2) - 소켓과 TCP

by sebinChu 2024. 8. 27.

개요


성공과 실패를 결정하는 1% 네트워크의 원리 챕터 2를 정리한 포스팅이다.

이 포스팅에서는 소켓과 TCP를 중심으로 정리한다.

 

 

1. 소켓을 작성한다.


1-1) 프로토콜 스택의 내부 구조

  • 인터넷에서 데이터를 운반할 때는 데이터를 작게 나누어 패킷이라는 형태로 운반한다.
  • 패킷을 통신 상대까지 운반하는 것이 IP의 주 역할이다.
  • IP 안에는 ICMP와 ARP라는 프로토콜을 다루는 부분이 포함되어 있다.
    • ICMP: 패킷 운반 통제 및 제어용 메시지 통제
    • ARP: 이더넷의 MAC 주소

 

1-2) 소켓의 실체는 통신 제어용 제어 정보

소켓은 개념적인 것이어서, 실체가 없다. 굳이 말하자면 제어 정보, 즉 제어 정보가 기록된 프로토콜 스택 내부의 메모리 영역이다.

  • 제어 정보에는 통신 상대의 IP 주소, 포트 번호, 통신 동작의 상태 등이 있다.
  • 소켓에는 응답이 돌아오는지의 여부와 송신 동작 후의 경과 시간 등이 기록되어 있다. → 이 정보를 보고 포기하거나 다시 보내는 동작을 실행해야 하기 때문
  • netstat 명령어

 

1-3) 소켓을 호출했을 때의 동작

브라우저가 소켓이나 connect같은 소켓 라이브러리의 부품을 호출했을 때, 프로토콜 스택의 내부는 어떻게 움직일까?

  • 브라우저에서 gethostnyname같은 걸로 요청하면…
  • TCP/IP layer에서 소켓 생성하기
    • 소켓의 제어 정보를 기록할 메모리 영역 확보하기
    • 소켓을 작성한 직후에는 송수신 동작이 시작되지 않은 상태이므로, 초기 상태임을 나타내는 정보 기록
  • 소켓을 만들고, 디스크립터를 애플리케이션에 알려주기
  • 애플리케이션은 앞으로 통신할 때 이 디스크립터를 활용

 

 

2. 서버에 접속한다.


2-1) 서버에 접속 == 소켓에 정보를 기록

소켓을 만든 직후는 아무것도 기록되어 있지 않다. 이 상태로는 송신 의뢰가 와도 제대로된 통신을 할 수가 없다. → 소켓을 호출하여 만드는 것만으로는 프로토콜 스택에는 아무것도 전달되지 않기 때문에 IP 주소나 port 번호같은 내용을 프로토콜 스택에 알리는 동작(접속 동작)이 필요하다.

  • 접속을 위해.. 즉, 통신을 위한 정보를 만들기 위해
    1. 통신 상대와 제어 정보를 주고받아 소켓에 필요한 정보를 기록한다. (ex. 클라이언트 측의 IP 주소나 포트 번호를 서버 측에 알리는 것)
  • 접속 동작에서 주고받는 제어 정보는 규칙이 정해져 있다.

 

2-2) 맨 앞부분에 제어 정보를 기록한 헤더를 배치한다.

TCP 헤더 내용

 

  • 통신 동작 전체에서 어떤 정보가 필요한지 검토하는 TCP 프로토콜의 사양으로 규정되어있다.
  • 이 항목들은 고정화되어 있어, 송수신/연결끊기/대화할 때마다 각 단계에서 이 제어 정보를 사용한다.
  • 즉, 데이터를 나눈 패킷마다, 패킷의 가장 앞부분인 헤더에 이 내용이 부가되어 있다.
    • 제어 정보만 있는 패킷은 뒷부분 데이터가 없고 제어 정보가 적힌 헤더만 존재

 

2-3) 접속 동작의 실체

애플리케이션이 socket 라이브러리의 connect를 호출하는 것부터 시작된다.

connect(<디스크립터>, <서버 IP>, <서버 PORT>)

 

접속 동작의 순서

  1. 클라이언트의 TCP 담당 부분에 ip와 port 전달 → 서버의 TCP 담당 부분에 전달
    • 포트번호: 클라이언트 소켓의 수신처가 되는 서버측의 소켓을 지정할 수 있는 관문
  2. 포트를 통해 접속 소켓을 확실히 하고, 컨트롤 비트인 SYN = 1 세팅
  3. 위와 같이 TCP 헤더를 만들고 IP 담당 부분에 건넴
  4. IP 담당 부분이 패킷 송신 동작 실행
  5. 네트워크를 통해 패킷을 서버에 보냄
  6. 서버측의 IP 담당 부분이 TCP 담당 부분에 건네어, 헤더 조사
  7. 수신처 소켓 즉, 포트 번호를 찾아서 필요한 정보를 기록하고 상태 변경(접속 동작 진행중)
  8. 응답 … 응답을 보낼 때도 마찬가지로, TCP 헤더 만들고, ACK = 1 세팅
  9. 응답을 받은 클라이언트는 또 다시 TCP 분석하고, SYN = 1 확인 후 소켓에 접속 완료를 나타내는 제어 정보를 기록
  10. 서버에게 응답을 잘 받았다는 ACK = 1 반송

⇒ 이 접속 동작이 끝나면 비로소 데이터를 송수신할 수 있는 상태가 된다. 파이프와 같은 것으로 소켓이 연결되었다고 생각하면 된다!

 

 

3. 데이터를 송수신한다.


서버와 연결이 완료되면, 애플리케이션 단에서 write 요청을 보낸다. 이때, 프로토콜 스택은 애프리케이션이 보내는 송신 데이터의 내용을 알지 모ㅅ한다. 다만, 데이터 길이만큼만 바이너리 데이터가 1바이트씩 차례로 나열되어 있다고 알고 있다.

 

3-1) 송수신 시, 내부 송신용 버퍼 메모리 활용

  • 프로토콜 스택은 애플리케이션으로부터 받은 데이터를 일단 내부 송신용 버퍼 메모리에 저장한다.⇒ OS의 종류나 버전에 따라 버퍼의 수용 가능한 크기, 데이터를 보내는 시점이 다르다.
  •  송신 요청이 올 때마다 작은 소켓을 네트워크 상에 흘려보내면 네트워크 이용 효율이 저하되므로, 어느정도 데이터를 저장하고 나서 송수신 동작을 한다.

 

3-2) 일반적으로 데이터를 보내는 시점은?

📌 2 가지 기준

 

1. MTU로 판단한다. 

  • MTU(Maximum Transmission Unit): 패킷 하나로 운반가능한 데이터 최대 길이
  • MSS(Maximum Segment Size): MTU에서 TCP/IP의 헤더를 뺀 부분 ⇒ 즉, 페이로드 부분

 

 

 

2. 타이밍

애플리케이션의 송신 속도가 느려지는 경우 → 프로토콜 스택 내부의 타이머 활용

 

 

*두 기준의 trade-off를 잘 생각해야 한다. MTU에 너무 집중하면 네트워크 이용효율이 높지만 서비스/통신 지연 가능성이 생기고, 2번을 기준으로 타이머를 너무 짧게 잡으면 느려짐은 없어도 네트워크 이용 효율이 좋지 않아진다. → 이에 대한 명확한 기준은 없고 OS/프로토콜 스택 개발자에 달려있는 상황이다.

 

*타이밍 제어는 옵션으로 지정 가능하다.

 

 

3-3) 데이터가 크면 분할해서 보내기

 

블로그나 게시판 등에서 긴 문장을 투고하는 등 한 개의 패킷에 다 담기지 않는 경우 → 송신 버퍼에 저장된 데이터는 MSS 길이를 초과하므로, 맨 앞부터 차례대로 MSS 크기에 맞게 분할하고 분할한 조각을 한 개씩 패킷에 넣어 송신한다.

 

 

3-4) ACK를 통해 패킷이 도착했는지 확인

  • 송신 측이 한 데이터를 N 바이트씩 나누어서 그 시작점을 시퀀스 넘버로 설저하고, 시퀀스 번호와 데이터 크기를 수신측에 같이 전송 → 수신측은 송신측으로받은 시퀀스 넘버 단위로 데이터를 받았다는 OK 사인을 전송
    1. seq: 1, 1460 byte
    2. ACK 1461
    3. seq: 1461, 1460 byte
    4. ACK 1462

그런데, TCP의 송수신 동작은 양방향이므로, 두 가지의 데이터 흐름이 있다. (,) 이에 대응하기 위해서 클라이언트 측에서도 ACK를 전송, 서버측에서도 시퀀스 넘거를 전송해준다.

 

  • 먼저 시퀀스 넘버를 양쪽에서 산출하고, 접속 동작을 할 때 서로 통지한다. → 이때도 서로 ACK 반송
  • 시퀀스 번호와 ACK 번호가 준비되면 데이터 송수신 동작에 들어간다.
  • 최초 클라이언트가 요청하면 서버에서 ACK를 반송하는 방식으로 데이터를 주고받는다.

→ TCP는 상대의 데이터 수신을 확인할 때까지 송신용 패킷을 버퍼 메모리에 저장해둔다. 만약 이에 대응하는 상대의 ACK가 오지 않으면 다시 이 데이터를 보내야 하기 때문이다.

 

→ TCP의 이러한 통신 방식으로 인해, 송수신을 TCP에만 맡겨두면 LAN 어댑터, 라우터 등 어디에서도 오류 검출을 할 필요가 없다. 네트워크의 어디에서 오류가 발생하더라도 TCP의 통신 특성만으로 회복 처리를 취할 수 있기 때문이다. 단, 도중에 케이블이 분리되거나 서버가 다운되는 등의 문제가 생기면 TCP가 아무리 데이터를 다시 보낸다고 해도 해결할 수 없다. 이럴 때에는 TCP가 몇 번 다시 보낸 후 회복의 전망이 없는 것으로 보고 데이터 송신 동작을 강제로 종료하고 애플리케이션에 오류를 통지한다.

 

3-5) ACK의 대기시간은?

ACK의 반송이 지연되는 상황은 보통 혼잡인 경우가 많으므로, 너무 자주 다시 데이터를 보내면 혼잡을 더 악화시킬 수 있다. 하지만 대기 시간을 너무 길게 잡으면 패킷을 다시 보내는 동작이 지연돼서 속도 저하의 원인이 될 수 있다. (→ 아까 내부 송신용 버퍼에 저장하고 데이터를 보내는 시점을 설정하는 것과 비슷한.. trade-off)

 

  • 동적으로 TCP 대기시간 변경이 가능하다.
    • ACK 번호가 돌아오는 시간을 기준으로 대기 시간을 판단한다. 데이터 송신 동작을 실행하고 있을 때 항상 ACK가 돌아오는 시간을 계측해 두고, 조정한다.

 

3-6) 윈도우 제어 방식으로 패킷 송수신 효율 높이기

  • 수신 측의 능력을 초과할 만큼 데이터를 한 번에 쏟아내버리면?수신 가능한 데이터의 최대양을 윈도우 크기라고 한다.
    • 감당 가능한 데이터의 양을 정해서 송신(클라이언트)측에 알려준다. 클라이언트는 데이터를 보낼 때 보낸 데이터의 크기와 용량을 비교하면서, 가능한 남은 영역이 0이 되면 송신을 중지한다.
  • 윈도우 통지 타이밍
    • 송신측은 한 번 통지를 받으면 송신한 데이터와 비교하여 스스로 남은 값을 계산할 수 있다. 그러므로, 수신 측에서 애플리케이션에 데이터를 건네주고 수신 버퍼의 빈 영역이 늘어났을 때 송신측에 통지해야 한다.
  • ACK와 윈도우 통지 패킷을 줄이고 효율적인 통신을 하는 방법
    1. 소켓을 바로 보내지 않고 잠시 기다린다. 이때 다음 통지 동작이 일어나면 양쪽을 상승 시켜서 함께 하나의 패킷으로 묶어 보낸다.
    2. ACK 번호 통지가 여러 번 연속해서 일어나면 최후의 것만 통지한다. (도중의 것 생략 가능)