프로토콜 스택의 내부 구성
OS에 내장된 네트워크 제어용 소프트웨어(프로토콜 스택)와 네트워킁용 하드웨어(LAN 어댑터)가 네트워크 애플리케이션에서 받은 메시지를 서비스하는 동작을 알아보자.
네트워크를 통해 데이터를 송수신하는 컴퓨터는 다음과 같이 계층을 나눌 수 있다.
모든 네트워크를 통한 데이터 송수신은 네트워크 애플리케이션에서 부터 아래로 향한다. 그러면서 데이터 송/수신 등의 일을 하위 계층에 의뢰한다. TCP, UDP는 데이터 송수신을 담당한다. 애플리케이션에서 보낸 의뢰를 받아 송/수신 동작을 실행한다. 일반적인 데이터 송/수신은 TCP를 사용하며 DNS 서버에 대한 조회 같은 짧은 제어용 데이터는 UDP를 사용한다.
IP는 패킷 송/수신 동작을 제어한다. 패킷을 통신 상대 까지 운반하는 것이 IP의 주된 역할이다. IP 내에는 ICMP(Internet Control Message Protocol)과 ARP(Address Resolution Protocol)이 존재한다. ICMP는 패킷 운반시 발생하는 오류 통지와 제어용 메시지 통지에 사용된다. ARP는 IP 주소에 대응하는 MAC 주소를 조사할 때 사용한다.
LAN 드라이버는 LAN 어댑터의 하드웨어를 제어한다. LAN 어댑터는 케이블에 대해 신호를 송/수신한다.
소켓의 실체는 통신 제어용 제어 정보
프로토콜 스택은 내부에 제어 정보를 기록하는 메모리 영역을 가지고 있다. 여기에 통신 동작을 제어하기 위한 제어 정보가 들어있다. 프로토콜 스택은 이 제어 정보를 참조하면서 동작하며 이것이 소켓의 역할이다. 이 소켓의 내용을 netstat 명령어로 출력해보면 다음과 같다.
여기서 확인할 수 있는 정보는 다음과 같다.
proto: 프로토콜의 종류. TCP/IP를 사용할 경우 TCP, UDP 중 하나.
Recv-Q: 소켓 버퍼 크기. 전송받은 데이터 중 아직 처리되지 못하고 recv() 하고 있는 상태. recv()는 소켓 통신에서 정보를 보낼 때 사용한다. recv()의 원형은 다음과 같다.
1
|
int recv(int socket, void* buf, size_t len, int flags);
|
cs |
Send-Q: 소켓 버퍼 크기. send() 되었으나 아직 전송되지 못하고 대기하는 상태. send()는 소켓 통신에서 정보를 받을 때 사용한다. send()의 원형은 다음과 같다.
1
|
int send(int socket, const void* msg, size_t len, int flags);
|
cs |
Local Address: netstat 명령을 실행한 기계의 IP 주소와 포트번호. 0.0.0.0이면 특정 IP 주소와 연결되 있지 않음을 의미한다.
Foreign Address: 통신 상태측의 IP 번호. 0.0.0.0이면 아직 통신이 시작되지 않고 특정 IP 주소나 포트와 연결되 있지 않은 것을 의미한다. UDP는 상대층 주소와 포트와 연결하지 않기 때문에 Proto가 UDP라면 Foreign Address는 *.*가 된다.
State: 통신 상태. 통신 상태는 다음과 같은 값들을 가질 수 있다.
상태 | 설명 |
LISTEN | 서버의 데몬이 떠서 접속 요청을 기다리는 상태 |
SYS-SENT | 로컬의 클라이언트 애플리케이션이 원격 호스트에 연결을 요청한 상태 |
SYS_RECEIVED | 서버가 클라이언트로 부터 접속 요구를 받아 클라이언트에게 응답을 했지만 클라이언트에게 확인 메시지를 받지 않은 상태 |
ESTABLISHED | 3-way-handshaking 이 완료된 후 서로 연결된 상태 |
CLOSING | 확인 메시지가 전송 도중 분실된 상태 |
TIME-WAIT | 연결은 종료되었지만 분실되었지 모를 느릴 세그먼트를 위해 일정 시간동안 소켓을 열어놓은 상태 |
CLOSE | 종료 |
CLOSE-WAIT | TCP 연결이 애플리케이션 레이러로 부터 연결 종료를 대기하는 상태 |
FIN-WAIT1 | 클라이언트가 서버에게 연결을 끊고자 요청하는 상태 |
FIN-WAIT2 | 서버가 클라이언트로부터 연결 종료 응답을 기다리는 상태 |
Socket 라이브러리를 호출했을 때의 동작
네트워크 애플리케이션이 Socket 라이브러리를 통해 프로토콜 스택에 의하는 동작은 다음과 같다.
위 흐름에서 socket() 을 호출하면 프로토콜 스택은 한 개의 소켓을 만든다. 이 때, 프로토콜 스택은 소켓 한 개에 해당하는 메모리 영역을 확보한다. 이 메모리 공간에는 소켓의 제어 정보를 저장한다. 소켓이 생성되면 소켓을 나타내는 디스크립터를 애플리케이션에 알려준다. 이후 애플리케이션은 데이터 송/수신을 프로토콜 스택에 의뢰할 때 디스크립터를 통지한다. 소켓에는 데이터 송/수신에 필요한 정보가 존재하므로 디스크립터를 알면 프로토콜 스택이 필요한 정보를 얻을 수 있다.
출처 - 성공과 실패를 결정하는 1%의 네트워크 원리