과정을 즐기자

TCP/IP를 이용한 통신 알아보기(feat. 소켓, LAN 어댑터, UDP) 본문

Network

TCP/IP를 이용한 통신 알아보기(feat. 소켓, LAN 어댑터, UDP)

320Hwany 2023. 7. 4. 12:24

이 글은 '성공과 실패를 결정하는 1%의 네트워크 원리'를 읽고 정리한 내용입니다.   

 

브라우저에 URL을 입력하면 그것을 바탕으로 브라우저는 HTTP Request Message를 만들고

OS에 송신을 의뢰합니다.  여기까지의 과정은 이전 글에서 설명하였습니다. 

 

웹 브라우저가 메세지를 만든다(HTTP, DNS, OS 프로토콜 스택, 소켓)

이 글은 '성공과 실패를 결정하는 1%의 네트워크 원리' 책의 1장으로 보고 정리한 내용입니다. HTTP Request Message를 작성한다 먼저 사용자가 브라우저에 URL을 입력합니다. URL에는 프로토콜, 웹서버

320hwany.tistory.com

 

TCP/IP 전체 구조

이번 글에서는 그 다음 과정인 OS에 내장된 프로토콜 스택이 어떻게 송신을 의뢰하는 지를 알아보겠습니다.

먼저 전체 구조를 살펴보겠습니다.

 

(1) 애플리케이션

브라우저와 같은 애플리케이션이 있습니다. 이 안에는 네트워크 송수신을 할 수 있도록 도와주는 

Socket 라이브러리가 존재합니다.

(2) OS

OS 내부에는 프로토콜 스택이 있고 다시 프로토콜 스택 안에는 TCP, UDP, IP를 담당하는 부분이 있습니다.  

(3) 드라이버 소프트웨어

하드웨어인 LAN 어댑터를 제어하는 LAN 드라이버가 있습니다

(4) 하드웨어

디지털 데이터를 전기 신호, 전기 신호를 디지털 데이터로 변환하는 송수신하는 LAN 어댑터가 있습니다.  

 

이러한 전체 구조를 바탕으로 다음과 같은 순서로 알아보겠습니다.   

1. 소켓을 작성한다

2. 서버에 접속한다

3. 데이터를 송수신한다

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

이 과정에서 IP와 이더넷의 패킷 송수신 동작도 알아보고 UDP에 대해서도 알아보겠습니다.

 

소켓을 작성한다

소켓이란 무엇일까요? 소켓은 사실 물리적인 개념이 아니라 추상적인 개념이라고 할 수 있습니다.  

OS 내부의 프로토콜 스택에는 제어 정보를 기록하는 메모리 영역이 있는데 여기에는  

통신 상대의 IP 주소, 포트 번호, 진행 상태 등 여러 정보가 저장되어있습니다.   

이 제어 정보가 소켓의 실체라고 할 수 있습니다. 

 

프로토콜 스택의 동작은 이 제어 정보를 참조하여 동작합니다.

데이터를 송신할 때는 소켓에 기록되어 있는 상대측의 IP 주소, 포트 번호를 보고 진행합니다.   

또한 소켓의 정보에는 응답이 돌아오는지의 여부, 경과 시간 등도 기록되어 있습니다.   

 

브라우저와 같은 애플리케이션이 Socket 라이브러리의 socket 명령어를 호출하면 프로토콜 스택은  

의뢰에 따라 한 개의 소켓을 만듭니다. 프로토콜 스택이 최초로 하는 일은 소켓 한 개 분량의 메모리 영역을  

확보하는 것입니다. 소켓의 제어 정보를 기록하는 메모리 영역이 처음부터 존재하는 것은 아니기 때문에  

먼저 그것을 확보하는 작업을 진행합니다. 

 

소켓이 만들어지면 소켓을 나타내는 디스크립터 를 애플리케이션에 알려줍니다.  

디스크립터는 프로토콜 스택의 내부에 있는 다수의 소켓 중 어느 것을 가리키는지를 나타내는 번호표입니다.

디스크립터를 받은 애플리케이션은 이후 프로토콜 스택에 데이터 송수신 동작을 의뢰할 때 디스크립터를 

통지합니다.

 

서버에 접속한다

socket을 호출하여 소켓을 만드는 동작만으로는 프로토콜 스택에는 아무것도 전달되지 않기 때문에  

서버의 IP 주소나 포트번호를 프로토콜 스택에 알리는 동작이 필요합니다. 이것을 '서버에 접속한다'라고 합니다.

 

접속 동작의 첫 번째 동작은 통신 상대와의 사이에 제어 정보를 주고받아 소켓에 필요한 정보를 기록하고  

송수신이 가능한 상태로 만드는 것입니다. 

데이터 송수신 동작을 실행할 때는 송수신하는 데이터를 일시적으로 저장하는 메모리 영역이 필요한데  

이 메모리 영역을 버퍼 메모리 라고 부릅니다. 버퍼 메모리 확보는 접속 동작을 할 때 진행됩니다.

 

애플리케이션이 Socket 라이브러리의 connect를 호출하면 프로토콜 스택 안에 있는 TCP 담당 부분은  

IP 주소로 표시된 상대 즉 서버의 TCP 담당 부분과의 사이에 제어 정보(TCP 헤더)를 주고 받습니다.

TCP 헤더에는 송신처 포트 번호, 수신처 포트 번호, 컨트롤 비트, 시퀀스 번호, ACK 번호, 윈도우 등이 있습니다.

클라이언트가 접속을 요청할 때 컨트롤 비트인 SYN이라는 비트를 1로 만들어 보냅니다.

서버는 제대로 받았다는 ACK 비트 1과 함께 SYN이라는 비트 1을 보냅니다. 

다시 클라이언트는 제대로 받았다는 ACK 비트 1을 서버로 보냅니다. 

이와 같은 과정을 통해 클라이언트, 서버의 소켓에 제어 정보가 저장되어 송수신이 가능한 상태가 됩니다.

이 과정을 3-way-handshake 라고 합니다.

데이터를 송수신한다

다음은 소켓 연결 완료 후 데이터를 송수신하는 단계입니다.

애플리케이션이 Socket 라이브러리의 write를 호출하여 송신 데이터를 프로토콜 스택에 건네줍니다. 

프로토콜 스택은 받은 데이터의 내용이 무엇인지 모른 상태로 자체 내부에 있는 송신용 버퍼 메모리 영역 에 

저장합니다. 애플리케이션이 데이터를 건네줄 때마다 패킷을 보낸다면 네트워크의 이용 효율이 저하되므로

어느 정도 데이터를 저장하고 송수신 동작을 합니다.

 

MTU, MSS, 타이밍

이때 언제 송신을 할 지는 OS에 따라 다르지만 기본적으로 2가지를 고려합니다.  

첫 번째는 한 패킷에 저장할 수 있는 데이터 크기입니다. MTU 는 한 패킷으로 운반할 수 있는 디지털 데이터의

최대 길이로 이더넷에서는 보통 1500바이트가 됩니다. MTU에는 패킷의 맨 앞 부분에 헤더가 포함되어 있으므로

여기부터 헤더를 제외한 것이 하나의 패킷으로 운반할 수 있는 데이터의 최대 길이가 되고 MSS 라고 합니다.

애플리케이션에서 받은 데이터가 MSS를 초과하거나 MSS에 가까운 길이에 이르기까지 데이터를 저장하고 

송신 동작을 하면 패킷을 너무 많이 보내 네트워크 이용 효율이 떨어지는 것을 막을 수 있습니다.

두 번째는 타이밍입니다. 애플리케이션의 송신 속도가 느려 MSS에 가깝게 데이터를 저장하면 여기에도  

시간이 걸려 송신 동작이 지연되므로 버퍼에 데이터가 모이지 않아도 일정 시간이 지나면 송신 동작을 

실행할 수 있도록 프로토콜 스택은 내부에 타이머가 있습니다.    

이때 송신을 OS에 완전히 맡기지 않고 데이터 송신을 의뢰할 때 옵션을 지정하여 애플리케이션에서 

송신 타이밍을 제어할 수도 있습니다.

 

송신 버퍼에 들어가 있는 데이터를 맨 앞부터 차례대로 MSS의 크기에 맞게 분할하고 분할한 조각을 한 개씩  

패킷에 넣어 송신합니다. 이때 데이터의 조각의 순서를 결정하기 위한 시퀀스 번호가 존재합니다.

또한 전체 크기에서 헤더 크기를 빼면 데이터의 크기를 계산할 수 있어 송신한 데이터가 몇 번째 바이트부터

시작되는 몇 바이트 분의 것인지 알 수 있습니다.

 

ACK 번호, 타임 아웃 값

TCP 헤더에는 ACK 번호 가 존재하는데 이것은 수신 확인 응답이라고 부르며 송신측은 이것을 통해  

상대가 어디까지 수신했는 지를 파악할 수 있습니다.

송신측에서 보낸 시퀀스 번호에 1을 더한 ACK 번호를 수신측에서 송신측으로 응답합니다.

일정 시간동안 응답이 오지 않았다면 송신측에서 다시 패킷을 보냅니다.

이렇게 TCP는 자체적으로 회복 처리를 할 수 있어 LAN 어댑터, 버퍼, 라우터에서 회복 처리를 고려하지 않습니다.  

이때 일정 시간 응답이 오지 않았을 때 다시 보내는데 여기서 일정시간은 타임 아웃 값 이라고 하며

패킷 평균 왕복 시간으로 계산합니다.

 

윈도우 제어 방식

송신 측이 하나의 패킷을 보내면 수신 측이 하나의 응답을 보내는 방법은 비효율적입니다.

송신 측이 여러 개의 패킷을 보내고 수신 측에서 응답을 보내고 유실된 패킷만 다시 보내는 방식을 

생각해볼 수 있습니다. 이것을 윈도우 제어 방식 이라고 합니다.

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

보내는 방법 입니다. 이때 수신측의 능력을 초과하여 패킷을 보내는 사태가 일어날 수 있는데

이를 위해 수신용 버퍼 메모리 에 데이터를 보관하여 수신측에서 송신측에 수신 가능한 데이터 양을  

통지하고 송신측은 이 양을 초과하지 않도록 송신 동작을 실행해야 합니다.

이 값은 TCP 헤더의 윈도우 필드 에 작성합니다.

이때 ACK 번호 통지와 윈도우 통지를 한 개의 패킷으로 묶어서 보냅니다. 

ACK 번호 통지, 윈도우 통지는 연속하여 일어나면 최후의 것만 통지하고 도중의 것은 생략해도 상관 없습니다. 

 

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

프로토콜 스택은 어느 쪽에서 먼저 연결 끊기 단계에 들어가도 좋게 만들어져 있습니다. 

여기서는 서버측의 애플리케이션이 먼저 Socket 라이브러리의 close를 호출하는 것을 예시로 들겠습니다.   

서버에서 FIN에 1을 설정한 TCP 헤더가 도착하면 클라이언트측의 프로토콜 스택은 자신의 소켓에 

서버측이 연결 끊기 동작에 들어갔다는 것을 기록합니다. ACK 번호를 서버측에 반송합니다.

잠시 후 애플리케이션이 read를 호출하여 데이터를 가지러 올 때 데이터를 건네지 않고 

서버에서 보낸 데이터를 전부 수신 완료했다는 사실을 클라이언트측의 애플리케이션에게 알립니다.  

클라이언트측의 애플리케이션도 close 호출하여 데이터 송수신 동작을 끝냅니다.

클라이언트측의 프로토콜 스택은 서버측과 마찬가지로 FIN 비트에 1을 설정한 TCP 헤더를 만들고 

IP 담당 부분에 의뢰하여 서버에 송신한 후 서버에서 ACK 번호가 돌아오면 서버와의 통신이 끝납니다.

 

이때 소켓을 즉시 말소하지 않고 잠시 기다리는데 서버에 ACK 번호가 돌아오지 않으므로 

다시 한번 FIN을 보낼 수 있습니다. 이때 클라이언트의 소켓이 말소되어 있으면 어떻게 될까요?

소켓을 말소하면 거기에 기록되어 있던 제어 정보가 없어지므로 소켓에 할당되어 있던 

포트 번호도 몇 번인지 알 수 없게 됩니다. 이러한 문제를 막기 위해 소켓을 즉시 말소하지 않고

잠시 기다립니다.  이 과정을 4-way-handshake 라고 합니다

 

이렇게 4가지 동작의 과정이 마무리됩니다.

 

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

 

이번에는 IP 담당 부분이 의뢰를 받아 어떻게 패킷을 상대에게 송신하는지 알아보겠습니다.   

패킷은 헤더와 데이터를 두 부분으로 나뉩니다.

IP 패킷 : IP 헤더 + TCP 헤더 + 데이터 조각

이더넷 패킷 : MAC 헤더 + IP 헤더 + TCP 헤더 + 데이터 조각

IP 담당 부분은 패킷을 운반하는 동작 전체에서 입구 부분입니다. IP 담당 부분은 TCP 헤더와 데이터 조각을

한 덩어리의 바이너리 데이터로 간주하여 내용을 보지 않고 송수신 동작을 실행합니다.

IP가 패킷을 송수신하는 동작은 제어 패킷이든지 데이터의 패킷이든지 패킷의 역할에 상관없이 모두 같습니다.

IP 헤더에는 송신처 IP주소, 수신처 IP 주소, ID 정보, 플래그, 프래그먼트 오프셋 등이 있습니다.

MAC 헤더에는 송신처 MAC 주소, 수신처 MAC 주소, 이더 타입이 있습니다.

이때 IP의 ARP로 수신처 라우터의 MAC 주소를 조사합니다.

 

이제 IP 담당 부분이 LAN 드라이버를 통해 LAN 어댑터로 의뢰합니다.

LAN 어댑터에서 패킷을 디지털 신호에서 전기 신호로 변환하여 전달할 수 있도록 합니다.

서브넷은 라우터허브 라는 두 종류의 패킷 중계 장치가 있습니다.

허브는 이더넷의 규칙에 따라 패킷을 운반하고 라우터는 IP의 규칙에 따라 패킷을 운반합니다.

이더넷의 원리에 따라 움직이는 허브에 도착합니다. 허브에는 패킷의 목적지를 판단하기 위한 표가 있습니다.

이 표를 보고 이더넷의 헤더의 수신처 정보와 표를 결합해서 패킷의 목적지를 판단하여 중계하여 패킷은

다음 라우터에 도착합니다. 

다시 라우터에는 IP 용 표가 있으므로 이것과 IP 헤더의 수신처를 결합하면 다음에 어느 라우터에 패킷을 

중계하면 좋을 지가 결정됩니다. 

 

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

TCP의 통신은 원리가 복잡한데 그 이유는 데이터를 확실하면서도 효율적으로 전달하기 위해서입니다.  

UDP는 TCP와 같은 수신 확인이나 윈도우가 없어서 데이터 송수신 전에 제어 정보를 주고 받을 필요가 없고

접속이나 연결 끊기 단계가 없습니다.

수신 단계도 간단한데 IP 헤더에 기록되어 있는 수신처 IP 주소와 송신처 IP 주소 그리고 UDP 헤더에 기록되어 

있는 수신처 포트 번호와 송신처 포트 번호라는 4가지 항목과 소켓에 기록된 정보를 결합하여 데이터를 

건네줄 대상 애플리케이션을 판단하고 여기에 데이터를 건네주기만 합니다.  

 

UDP는 음성, 영상 데이터를 보낼 때 사용합니다. 음성이나 영상 데이터는 결정된 시간 안에 데이터를 건네주어야 

합니다. 음성, 영상 데이터는 다소 없어져도 큰 문제가 없기 때문에 단순히 UDP로 보내는 것이 더 효율적입니다.

Socket 라이브러리의 리졸버를 이용하여 DNS에서 IP주소를 조회할 때도 UDP 프로토콜을 사용합니다.  

 

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