본문 바로가기

네트워크

[네트워크 원리] 성공과 실패를 결정하는 1%의 네트워크 원리 (2)

이 포스팅은 아래 링크의 책을 읽고 정리한 포스팅이다.

https://kyobobook.co.kr/product/detailViewKor.laf?mallGb=KOR&ejkGb=KOR&barcode=9788931553482

 

성공과 실패를 결정하는 1%의 네트워크 원리

『성공과 실패를 결정하는 1%의 네트워크 원리』는 네트...

www.kyobobook.co.kr

 

순서

1장: 브라우저가 요청을 의뢰하는 동작에 대한 설명

2장: 데이터를 운반하는 구조 설명. OS에 내장된 프로토콜 스택이 하는 일 설명

3장: LAN 어댑터가 송신한 패킷을 전송하는 과정 (스위칭 허브, 라우터)

4장: 인터넷 접속용 라우터의 앞부분, 인터넷 내부에 대한 구조 설명

5장: 웹 서버측의 LAN에 방화벽, 캐시 서버에 대한 설명

6장: 프로토콜 스택이 패킷의 메시지를 복원하고 서버에 넘기는 과정 설명

 

2장

개요

- OS에 내장된 프로토콜 스택이 어떻게 송신을 의뢰하는지 설명한다.

 

목차

1. 소켓을 작성한다.

2. 서버에 접속한다.

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

4. 서버에서 연결을 끊어 소켓을 말소한다.

5. IP와 이더넷의 패킷 송/수신 동작

6. UDP 프로토콜을 이용한 송/수신 동작

 

1. 소켓을 작성한다

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

- 맨 위에 있는 것은 네트워크 애플리케이션. 아래쪽을 향하여 데이터 송/수신 등의 일을 의뢰한다. 브라우저 뿐만 아니라 어떤 애플리케이션도 네트워크에서 데이터를 송/수신하는 동작은 거의 비슷하다.

- 애플리케이션의 아랫부분에는 Socket 라이브러리가 있고, 그 안에는 리졸버가 내장되어 있다. (1장에서 설명함)

- 그 아래가 OS의 내부를 나타내고, 여기에 프로토콜 스택이 있다. 프로토콜 스택의 윗부분은 TCP 프로토콜을 사용하여 데이터 송/수신을 담당하는 부분과 UDP라는 프로토콜을 사용하여 데이터 송/수신을 담당하는 부분이 있다.

- 일반적인 애플리케이션은 TCP를 사용하여 데이터를 송/수신하고 DNS 서버 조회 등 짧은 제어용 데이터를 송/수신하는 경우에는 UDP를 사용한다.

- 그 아래에는 IP 프로토콜을 사용하여 패킷 송/수신 동작을 제어하는 부분이 있다. 패킷을 통신 상대까지 운반하는 것이 IP의 주 역할이다. (패킷: 분할된 데이터의 덩어리)

- IP 아래에 있는 LAN 드라이버는 LAN 어댑터의 하드웨어를 제어한다. 그 아래에 있는 LAN 어댑터가 실제 송/수신 동작, 즉 케이블에 의해 신호를 송/수신하는 동작을 실행한다.

 

2. 소켓의 실체는 통신 제어용 제어 정보

- 프로토콜 스택은 내부에 제어 정보를 기록하는 메모리 영역을 가지고 있고 여기에 통신 동작을 제어하기 위한 제어 정보를 기록한다. 대표적인 정보로는 통신 상대의 IP 주소, 포트 번호, 통신 동작이 어떤 상태에 있는 가 하는 것이다. 이 제어 정보를 기록한 메모리 영역이 소켓의 실체이다.

- 프로토콜은 이 제어 정보를 참조하면서 동작한다. 소켓에는 응답이 돌아오는지의 여부와 송신 동작 후의 경과 시간 등이 기록되어 있다.

- 소켓에는 통신 동작을 제어하기 위한 여러 가지 제어 정보가 기록되어 있다. 프로토콜 스택은 이것을 참조하여 다음에 무엇을 해야 하는지를 판단하는데, 이것이 소켓의 역할이다.

 

3. Socket을 호출했을 때의 동작

1. 소켓 한 개 분량의 메모리 영역을 확보하는 것이 시작이다. 초기 상태임을 나타내는 제어 정보를 소켓의 메모리 영역에 기록한다. 소켓이 만들어지면 소켓을 나타내는 디스크립터를 애플리케이션에 알려준다.

2. 애플리케이션은 프로토콜 스택에 데이터 송/수신 동작을 의뢰할 때 디스크립터를 통지한다. 디스크립터가 어느 소켓인지를 나타내면 필요한 정보는 전부 프로토콜 스택에서 알 수 있다.

 

2. 서버에 접속한다.

1. 접속의 의미

- 소켓을 만들면 애플리케이션은 connect를 호출한다. 그러면 프로토콜 스택은 자기쪽의 소켓을 서버측 소켓에 접속한다.

- 이더넷이나 통신 회선은 항상 케이블이 연결되어 있으므로 언제나 신호를 보낼 수 있다.

- 소켓을 만든 직후는 아무것도 기록되어 있지 않으므로 통신 상대가 누구인지 모른다.

- 서버의 IP 주소나 포트 번호를 프로토콜 스택에 알리는 동작이 필요한데, 이것이 접속 동작의 한 가지 역할이다.

- 서버측의 프로토콜 스택도 소켓을 만드는 동작만으로는 통신 상대를 알 수 없다. 그렇기 때문에 통신하려는 클라이언트가 있다는 것을 서버측에 전달한다. 그러면 서버측의 프로토콜 스택도 클라이언트의 정보를 가질 수 있다.

- 접속 동작의 첫번째는 통신 상대와의 사이에 제어 정보를 주고받아 소켓에 필요한 정보를 기록하고 데이터 송/수신이 가능한 상태로 만드는 것이다.

- 데이터 송/수신 동작을 실행할 때는 송/수신하는 데이터를 일시적으로 저장하는 메모리 영역이 필요한데, 이 메모리 영역을 '버퍼 메모리' 라고 부른다.

 

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

TCP 헤더의 포맷

- 제어 정보는 크게 나누어 두가지가 있다.

  1. 클라이언트와 서버가 서로 연락을 절충하기 위해 주고받는 제어 정보

  - 위 표에 나와있는 항목이 제어 정보로 규정되어 있다. 이 항목은 고정화되어 있기 때문에 접속, 송/수신, 연결 끊기의 각 단계에서 클라이    언트와 서버가 대화할 때마다 거기에 이 제어 정보를 부가한다. 패킷의 맨 앞 부분에 부가한다. 이것을 헤더라고 부른다.

  2. 애플리케이션에서 통지된 정보, 통신 상대로부터 받은 정보, 송/수신 동작의 진행 상황 정보

- 소켓에 기록한 제어 정보는 상대측에서 볼 수 없다. 헤더에 제어 정보를 기록하여 그것으로 대화한다.

 

3. 접속 동작의 실제

- 여기에 서버측의 IP 주소와 포트 번호를 쓰면 명령이 프로토콜 스택의 TCP 담당 부분에 전달된다. TCP는 먼저 데이터 송/수신 동작의 개시를 나타내는 제어 정보를 기록한 헤더를 만든다.

- 헤더에서 중요한 것은 송신처와 수신처의 포트 번호이다. 송신처가 되는 클라이언트측의 소켓과 수신처가 되는 서버측의 소켓을 지정할 수 있다.

- TCP 헤더를 만들면 이것을 IP 담당 부분에 건네주어 송신 의뢰한다.

- 네트워크를 통해 패킷이 서버에 도착하면 서버측의 IP 담당 부분이 이것을 받아 TCP 담당 부분에 건네준다. 서버측의 TCP 담당 부분이 TCP 헤더를 조사하여 기록되어 있는 수신처 포트 번호에 해당하는 소켓을 찾아낸다. 소켓을 발견하면 여기에 필요한 정보를 기록하고 접속  동작이 진행중이라는 상태가 된다. 이 과정이 끝나면 서버의 TCP 담당 부분은 응답을 돌려보낸다.

- 응답을 돌려보낼 때 ACK라는 컨트롤 비트도 1로 만든다. 이것은 패킷을 받은 것을 알리기 위한 동작이다. 그리고 TCP 헤더를 IP 담당 부분에 건네주어 클라이언트에 반송하도록 의뢰한다.

- 패킷이 클라이언트에 돌아오고 IP 담당 부분을 경유하여 TCP 담당 부분에 도착한다. 이때 TCP 헤더를 조사하여 서버측의 접속 동작이 성공했는지 확인한다. SYN이 1이면 접속 성공이므로 소켓에 서버의 IP 주소나 포트 번호 등과 함께 소켓에 접속 완료를 나타내는 제어 정보를 기록한다.

- 패킷이 도착한 것을 서버에 알리기 위해 ACK 비트를 1로 만든 TCP 헤더를 반송한다. 이것이 서버에 도착하면 접속 동작의 대화가 끝난다.

- 그 결과 소켓은 데이터를 송/수신할 수 있는 상태가 된다. 이것을 커넥션이라고 한다. 커넥션은 close 함수를 호출해서 연결을 끊을 때 까지 계속 존재한다. 커넥션이 이루어지면 프로토콜 스택의 접속 동작이 끝나므로 connect의 실행이 끝나면서 애플리케이션을 제어할 수 있게 된다.

 

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

1. 프로토콜 스택에 HTTP 요청 메시지를 넘긴다

- connect 동작이 완료되면 데이터 송/수신 동작에 들어간다. 이 동작은 애플리케이션이 write를 호출하여 송신 데이터를 프로토콜 스택에 건네주는 곳부터 시작된다.

- 프로토콜 스택은 받은 데이터의 내용에 무엇이 쓰여있는지 알지 못한다. 해당 길이만큼만 바이너리 데이터가 1바이트씩 차례로 나열되어 있다고 인식한다.

- 데이터를 곧바로 송신하는 게 아니라 일단 자체의 내부에 있는 송신용 버퍼 메모리 영역에 저장하고 애플리케이션이 다음 데이터를 건네주기를 기다린다. 데이터를 저장하는 이유는, 한 번의 송신 의뢰에서 건네주는 데이터의 길이는 애플리케이션의 사정에 따라 결정되고, 프로토콜 스택에서 제어할 수 없다. 이런 상황에서 받은 데이터를 곧바로 보낸다면 작은 패킷을 많이 보내는 경우가 생긴다. 그렇기에 어느 정도 데이터를 저장하고 나서 송/수신 동작을 한다.

 

어느 정도까지 저장하고 송신할 지 판단하는 두 가지 요소

  1. 한 패킷에 저장할 수 있는 데이터의 크기 (MTU)

  - MTU는 한 패킷으로 운반할 수 있는 디지털 데이터의 최대 길이로, 이더넷에서는 보통 1500바이트이다. 맨 앞부분에는 헤더가 포함되는데, 헤더를 제외한 것이 하나의 패킷으로 운반할 수 있는 데이터의 최대 길이가 되고, 이것을 MSS라고 한다. 애플리케이션에서 받은 데이터가 MSS를 초과하거나 MSS에 가까운 길이에 이르기까지 데이터를 저장하고 송신한다.

 

  2. 타이밍

  - 프로토콜 스택은 내부에 타이머가 있어서 일정 시간이 경과하면 MTU 여부에 상관없이 패킷을 전송한다.

 

- 전자를 중시하면 네트워크 이용 효율이 높아지지만 송신 동작이 지연될 우려가 있다. 후자를 중시하면 지연은 적어지지만 이용 효율이 떨어진다. 이 둘에 대한 절충 규정은 없으며, 개발자에게 달려있다.

- 애플리케이션측에서 송신의 타이밍을 제어할 수 있다. 데이터 송신을 의뢰할 때 옵션을 지정할 수 있고, 브라우저 같은 경우는 버퍼에 머무는 만큼 응답 시간이 지연되기 때문에 옵션을 사용해서 바로 보내도록 할 수 있다.

 

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

- 폼을 사용하여 긴 데이터를 보낼 경우 등 한 개의 패킷에 들어가지 않을 만큼 긴 것도 있다. (긴 문장을 등록하는 경우)

- 송신 버퍼에 저장된 데이터는 MSS 길이를 초과하므로 다음 데이터를 기다릴 필요가 없다. 따라서 송신 버퍼에 들어있는 데이터를 맨 앞부터 차례대로 MSS의 크기에 맞게 분할하고, 분할한 조각을 한 개씩 패킷에 넣어 송신한다.

 

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

-TCP에는 송신한 패킷이 상대에게 올바르게 도착했는지 확인하고, 도착하지 않았으면 다시 송신하는 기능이 있으므로 패킷을 송신한 후에는 확인 동작으로 넘어간다.

- 데이터를 조각으로 분할할 때 조각이 통신 개시부터 따져서 몇 번째 바이트에 해당하는지를 세어둔다. 그리고 데이터의 조각을 송신할 때 세어둔 값을 TCP 헤더에 기록하는데, 시퀀스 번호라는 항목이 해당된다. 패킷 전체의 길이에서 헤더 길이를 빼면 데이터의 크기를 계산할 수 있으므로 수신측에서 이 방법에 따라 크기를 산출한다.

- 누락이 없는 것을 확인하면 수신측은 그 이전에 수신한 데이터와 합쳐서 데이터를 몇 번째 바이트까지 수신한 것인지 계산하고, 그 값을 TCP 헤더의 ACK 번호에 기록하여 송신측에 알려준다. 이렇게 ACK 번호를 되돌려주는 동작을 수신 확인 응답이라고 부른다.

- 실제로 시퀀스 번호는 1부터 시작하지 않고 난수를 바탕으로 산출한 초기값으로 시작한다. 데이터의 송/수신을 시작하기 전에 초기값을 상대에게 알리게 되어 있다.

- TCP는 상대가 데이터를 받았는 지 확인할 때까지 송신한 패킷을 송신용 버퍼 메모리 영역에 보관해둔다. 그리고 대응하는 ACK 번호가 상대로부터 돌아오지 않으면 패킷을 다시 보낸다. 이렇기 때문에 다른 곳에서 오류를 회복 조치할 필요가 없다.

- TCP는 몇 번 다시 보낸 후 회복의 전망이 없는 것으로 보고 데이터 송신 동작을 강제로 종료하고 애플리케이션에 오류를 통지한다.

 

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

- ACK 번호가 돌아오는 것을 기다리는 시간을 타임아웃 값이라고 한다. 대기 시간은 너무 짧지도, 길지도 않은 적절한 값으로 설정해야 한다. 하지만 이것은 어려운 것이다. 그래서 TCP는 대기 시간을 동적으로 변경하는 방법을 취하고 있다. ACK 번호가 돌아오는 시간을 기준으로 대기 시간을 판단하는 것이다.

- 데이터 송신 동작을 실행하고 있을 때 항상 ACK 번호가 돌아오는 시간을 계측해둔다. 그리고 ACK 번호가 돌아오는 시간이 지연되면 이것에 대응하여 대기 시간도 늘린다. 반대로 ACK 번호가 곧바로 돌아오면 대기 시간을 짧게 설정한다.

 

5. 윈도우 제어 방식으로 효율적으로 ACK 번호를 관리한다

- ACK 번호가 돌아올 때까지 아무 일도 하지 않고 기다리는 것은 시간 낭비이다.

- 윈도우 제어는 한 개의 패킷을 보낸 후 ACK 번호를 기다리지 않고 차례대로 연속해서 복수의 패킷을 보내는 방법이다.

- 그러나 ACK 번호를 기다리지 않고 차례로 패킷을 보내면 수신측의 능력을 초과하여 패킷을 보내는 사태가 일어날 수도 있다.

- 수신측의 TCP는 패킷을 수신하면 일단 수신용 버퍼 메모리에 데이터를 일시 보관한다. 수신측에서는 ACK 번호를 계산하거나 조각을 연결하여 원래 데이터를 복원한 후 애플리케이션에 건네주어야 한다. 그런데 애플리케이션에 건네주는 속도보다 빠른 속도로 데이터가 도착하면 수신 버퍼에 데이터가 차곡차곡 쌓여서 곧 넘쳐버린다.

- 이 문제는 수신측에서 송신측에 수신 가능한 데이터 양을 통지하고, 송신측은 이 양을 초과하지 않도록 송신 동작을 실행하는데, 이것이 윈도우 제어 방식의 개념이다.

- TCP 헤더의 윈도우 필드에서 이것을 알려준다. 수신 가능한 데이터 양의 최대값을 윈도우 사이즈라고 부르고, 보통 수신측의 버퍼 메모리의 크기와 같은 크기가 된다.

 

6. ACK 번호와 윈도우를 합승한다

- 윈도우 통지가 필요한 것은 수신측이 수신 버퍼에서 데이터를 추출하여 애플리케이션에 건네주었을 때이다. 수신측에서 애플리케이션에 데이터를 건네주고 수신 버퍼의 빈 영역이 늘어났을 때, 이것을 송신 측에 통지해야 하는데 이것이 윈도우 통지의 타이밍이다.

- ACK 번호는 데이터를 수신한 후 즉시 보낸다고 볼 수 있다.

- 설명대로라면 ACK 번호 통지와 윈도우 통지의 패킷이 하나씩 따로따로 송신측에 보내지는 데, 이것은 효율성이 저하된다.

- 수신측은 ACK 번호나 윈도우를 통지할 때 소켓을 바로 보내지 않고 잠시 기다렸다가 다음 통지 동작이 일어나면 한 개의 패킷으로 묶어서 보낸다.

- 복수의 ACK 번호 통지가 연속해서 일어나는 경우도 최후의 것만 통지하고 도중의 것은 생략해도 상관 없다.

 

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

- 브라우저는 요청 메시지를 송신해 달라고 의뢰하고, 서버에서 돌아오는 응답 메시지를 받기 위해 read 프로그램을 호출한다. read를 경우하여 프로토콜 스택에 제어가 넘어가고, 프로토콜 스택이 움직이기 시작한다.

 

4. 서버에서 연결을 끊어 소켓을 말소한다

1. 데이터 보내기를 완료했을 때 연결을 끊는다

- 데이터 송/수신을 종료하는 것은 애플리케이션이 송신해야 하는 데이터를 전부 송신 완료했다고 판단했을 때이다. 어디에서 데이터 송/수신 동작이 끝나는지는 애플리케이션에 따라 다르다. 웹이라면 브라우저에서 웹 서버에 리퀘스트 메시지를 보내고, 서버가 이것에 응답하여 응답 메시지를 반송 완료하면 데이터 보내기가 완료된 것이다. 이 경우 서버 측이 연결 끊기 단계에 들어간다. ( HTTP 1.1에서는 서버가 응답 메시지를 반송한 후 계속 클라이언트가 다음 리퀘스트 메시지를 보내도 좋게 되어 있다. 이 경우에는 클라이언트측에서 먼저 연결 끊기 단계에 들어갈 수도 있다) 프로토콜 스택은 어느 쪽에서 먼저 연결 끊기 단계에 들어가도 좋게 만들어져 있다.

- 서버측의 애플리케이션이 먼저 Socket 라이브러리의 close를 호출한다. 그러면 서버측의 프로토콜 스택이 TCP 헤더를 만들고, 여기에 연결 끊기를 나타내는 정보를 설정한다. 구체적으로는 컨트롤 비트의 FIN 비트에 1을 설정하고, IP 담당에 의뢰하여 클라이언트에 송신해 달라고 한다.

- 클라이언트 측에서는 서버에서 FIN이 1로 설정된 TCP 헤더가 도착하면 클라이언트측의 프로토콜 스택은 자신의 소켓에 서버측이 연결 끊기 동작에 들어갔다는 것을 기록한다. 그리고 ACK 번호를 서버측에 반송하고 이것이 끝나면 애플리케이션이 데이터를 가지러 올 때까지 기다린다.

- 애플리케이션이 read를 호출하여 데이터를 가지러 오면 데이터를 건네지 않고 서버에서 보낸 데이터를 전부 수신 완료 했다는 사실을 브라우저에게 알린다. 그래서 클라이언트측의 애플리케이션도 close를 호출하여 데이터 송/수신 동작을 끝낸다. 그러면 클라이언트측의 프로토콜 스택은 서버측과 마찬가지로 FIN 비트에 1을 설정한 TCP 헤더를 만들고 IP 담당 부분에 의뢰하여 서버에 송신한 후 ACK 번호가 돌아오면 서버와의 대화가 끝난다.

 

2. 소켓을 말소한다

- 바로 소켓을 말소하지 않고 잠시 기다린 후 소켓을 말소한다.

- 오동작을 막기 위해 일반적으로 몇 분 정도 기다리고 나서 소켓을 말소한다.

 

5. IP와 이더넷의 패킷 송/수신 동작

1. 패킷의 기본

2. 패킷 송/수신 동작의 개요

3. 수신처 IP 주소를 기록한 IP 헤더를 만든다

4. 이더넷용 MAC 헤더를 만든다

5. ARP로 수신처 라우터의 MAC 주소를 조사한다

6. 이더넷의 기본

7. IP 패킷을 전기나 빛의 신호로 변환하여 송신한다

8. 패킷에 3개의 제어용 데이터를 추가한다

9. 허브를 향해 패킷을 송신한다

10. 돌아온 패킷을 받는다

11. 서버의 응답 패킷을 IP에서 TCP로 넘긴다