본문 바로가기
CS

[Network] 프로토콜 스택이 서버와 통신하는 방법 훑어보기

by doodoom 2023. 5. 28.

0. 이 글을 쓰게 된 이유

지난 글에서는 브라우저가 요청을 주고 받을 때, 어떻게 동작하는지에 대해 알아보았다. 지금까지는 TCP/IP 계층 구조에서 어플리케이션 계층에 해당하는 내용을 알아봤다고 생각하면 된다.
지난번에 말했듯이 브라우저는 네트워크 기능을 가지고 있지않아서, OS의 프로토콜 스택에게 네트워킹을 의뢰한다. 오늘은 OS의 프로토콜 스택이 어떻게 네트워킹을 하는지 내부 동작에 대해서 순서대로 알아보자. TCP/IP 계층으로 따지면 Transport, Network 기능이다.

들어가기 전에 일단 프로토콜 스택의 내부 구성에 대해서 알아보자.


출처 : https://github.com/Road-of-CODEr/one-percent-network/blob/master/20201014/Chapter2-1.md

위 사진은 TCP/IP 계층 구조이다. 프로토콜 스택의 윗부분에는 TCP, UDP 프로토콜이 있는 것을 볼 수 있다. 이는 Transport 계층을 담당하므로 데이터 송수신을 담당하는 부분이라고 볼 수 있다.
그 아래에는 IP 프로토콜을 볼 수 있는데, 이 부분은 Network 계층을 담당한다. 이 부분에서 패킷의 송수신 동작을 제어하게 된다.

1. 소켓 작성

저번 글에서 소켓을 통해서 통신을 한다고 했다. 그렇다면 소켓이 무엇일까?

1.1 소켓이란?

네트워크 소켓(network socket)은 컴퓨터 네트워크를 경유하는 프로세스 간 통신의 종착점이다.네트워크 통신을 위한 프로그램들은 소켓을 생성하고, 이 소켓을 통해서 서로 데이터를 교환한다. 소켓은 RFC 147에 기술사항이 정의되어 있다. - 위키백과

즉, 소켓은 실체가 없고 표준이 정해져있는 개념인 것이다. RFC 147에서 정의하고 있는 소켓의 대표적인 정보는 다음과 같다.
인터넷 프로토콜 (TCP, UDP, raw IP), 로컬 IP 주소, 로컬 포트, 원격 IP 주소, 원격 포트 등
굳이 말하자면 이 제어 정보가 소켓의 실체라고 할 수 있다.

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

1.2 소켓의 생성 과정

  1. 프로토콜 스택이 소켓 생성을 의뢰 받으면(Socket 라이브러리의 socket) 당연하게도 소켓의 메모리 영역을 확보한다.
  2. 그 후 해당하는 제어정보를 기록한다.
  3. 이 소켓은 송수신이 시작되지 않았으므로 초기 상태임을 나태내는 제어 정보를 소켓의 메모리 영역에 기록한다.
  4. 소켓이 만들어지면 디스크립터를 애플리케이션에게 알려준다.

2. 서버 접속

  1. 서로에게 제어 정보를 주고받는다.
    소켓을 만든 직후, 그 안에 있는 정보는 매우 부족하다. 브라우저의 경우, 서버의 IP 주소를 알고 포트 번호도 알지만 송수신하기에는 부족한 상태라고 한다. 서버의 경우 소켓을 만들어도 통신 상대를 알지 못하니 아무것도 할 수 없다.
    그래서 클라이언트와 서버는 서로의 송수신을 제어하기 위한 제어 정보를 주고받아 각 소켓에 필요한 정보를 기록하고 송수신이 가능한 상태로 만든다. (Ex - ip 주소, port 번호) 접속 동작에서 주고받아야하는 제어 정보는 통신의 규칙으로 정해져있다.
  2. 버퍼 메모리 확보
    그리고 송수신을 할 때에는 데이터를 일시적을 저장하는 메모리 영역이 필요한데, 이 메모리 영역을 '버퍼 메모리'라고 한다. 이 영역도 접속 동작을 할 때, 확보한다.

이렇게 되면 서버와 접속된다고 할 수 있다. 즉, 접속이란 서버와 알아야하는 제어 정보를 주고받고, 메모리 영역을 확보하여 통신 가능한 상태로 만드는 것이다.

2.1 제어정보

위에서 간단하게 제어정보라고 소개를 했는데 이 제어정보에 대해서 간단하게 훑어보자.
제어 정보는 크게 두 종류가 있다.

2.1.1 TCP 헤더에 기록되는 제어정보

패킷에는 어플리케이션에서 보내는 데이터 영역이 있고, 여러가지 메타정보가 담기는 헤더 부분이 있다. TCP 담당부분에서는 붙히는 헤더 영역을 TCP 헤더라고 한다.

TCP 헤더에 있는 제어정보는 다음과 같다. 이 내용들은 TCP 프로토콜에 의해 규정 되어있다.


이 제어 정보들은 클라이언트와 서버가 서로 연락을 절충하기 위해 주고받는다. 즉, 상대에게 보내는 정보들이다.

2.1.2 소켓에 기록되는 제어정보

TCP 헤더와 다르게 소켓에 기록되는 제어정보는 상대에게 보내지 않는다. 이 정보들은 자기의 프로토콜 스택을 위해 소켓에 기록하는 것이다. 그리고 OS마다 프로토콜 스택 구현이 다르기 때문에 필요한 제어 정보도 각각 다르다.
이 제어 정보에는 애플리케이션에서 통지된 정보, 송수신 동작의 진행 상황 등이 기록된다. 프로토콜 스택은 이를 참조하여 움직이게된다.

실제로는 TCP 헤더에 있는 컨트롤비트와 시퀀스 번호, 윈도우들을 이용하여 통신하는 과정이 있지만, 여기에서는 생략하겠다.
접속을 하면 소켓은 데이터를 송수신할 수 있는 상태가 된다. 네트워크 업계에서는 이와 같은 상태를 파이프와 같은 것이 연결되어있다고 표현하고 커넥션이라고 한다.

3. 데이터 송수신

이제 접속이 되었으니 데이터 송수신할 준비를 마치었다. 프로토콜 스택에 HTTP Request 데이터를 넘겨서 송신을 할 수 있다.
하지만 송신하는 것은 그렇게 간단한 문제가 아니다.

정말 간단하게 프로토콜 스택이 데이터를 수신하는 과정을 알아보자. 각 키워드들에 대한 자세한 설명은 생략한다.

  1. 먼저 수신한 데이터 조각과 TCP 헤더의 내용을 조사하여 도중에 데이터 누락되었는지 검사한다.
    프로토콜 스택의 TCP 부분은 데이터를 조각으로 분할할 때 조각이 통신 개시부터 따져서 몇번째 바이트에 해당하는지를 세어둔다. 그리고 데이터의 조각을 송신할 때 세어둔 값을 TCP 헤더에 기록하는데, 이를 시퀀스 번호라고 한다. 그리고 패킷 전체의 길이에서 데이터의 크기를 산출한다.
    이 두가지 방법을 통해서 송신한 데이터가 몇 번째 바이트부터 시작되는 몇 바이트 분의 것인지 알 수 있다. 이렇게 하면 수신측에서 패킷이 누락되었는지 확인이 가능하다.
    누락이 없는 것을 확인한 수신 측은 데이터를 몇번째 바이트까지 수신한 것인지 계산하고, 그 값을 TCP 헤더의 ACK 번호에 기록한다. 즉, 어디까지 받았는지 송신 측에 확인시켜주는 것이다.
  2. 데이터 조각을 수신 버퍼에 일시 보관하고, 조각을 연결하여 데이터를 원래 모습으로 복원한 후 애플리케이션에 건네준다.
    수신 버퍼에 있는 데이터를 애플리케이션이 지정한 메모리 영역에 옮겨 기록한 후 애플리케이션에 제어를 되돌려준다. 그리고 애플리케이션에 데이터를 건네주고 나서 타이밍을 가늠하여 윈도우(수신 버퍼에 용량이 얼마나 남았는지)를 송신측에 통지한다.

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

애플리케이션에서 송신해야 하는 데이터를 전부 송신 완료했다고 신호를 주면 프로토콜 스택에서는 연결 끊기 단계로 들어간다. 프로토콜 스택은 클라이언트나 서버 어느쪽에서든 먼저 연결 끊기에 들어가도 좋도록 설정해놨다. 하지만 보통 서버에서 먼저 연결을 끊는다.

이제 연결 끊기 단계에 대해 간단하게 알아보자. 서버가 Socket 라이브러리의 close를 호출한 뒤의 과정을 설명한다.

  1. 서버 측의 프로토콜 스택이 연결 끊기를 나타내는 정보를 담은 TCP 헤더를 만든다.
    프로토콜 스택의 TCP 영역은 TCP 헤더 컨트롤 비트의 FIN 비트에 1을 설정하고, IP 담당 부분에 의뢰하여 송신한다.
  2. 클라이언트 측에서 해당 서버의 연결 끊기 요청을 받는다.
    클라이언트 측의 프로토콜 스택의 TCP 영역은 자신의 소켓에 서버측이 연결 끊기 동작에 들어갔다는 것을 기록한다.(소켓마다 기록 방식이 다름)
    그리고 FIN을 1로 설정한 패킷을 받은 사실을 알리기 위해 ACK 번호를 서버측에 반송하고 이것이 끝나면 애플리케이션이 데이터를 가지러 올 때까지 기다린다.
  3. 클라이언트 측의 애플리케이션이 데이터를 가지러온다. (Socket 라이브러리의 read호출)
    프로토콜 스택은 데이터를 건네지 않고 서버에서 보낸 데이터를 전부 수신 완료했다는 사실을 애플리케이션에게 알린다.
    웹의 동작은 서버가 응답을 반송하면 끝나도록 규칙이 정해져 있으므로 서버에서 보낸 데이터를 전부 수신 완료하면 클라이언트는 종료한다.
  4. 클라이언트 측의 애플리케이션에서 close를 호출한다.
    1단계에서의 서버와 마찬가지로 클라이언트 측의 애플리케이션도 close를 호출한다.
    이렇게 되면 서버 측과 마찬가지로 클라이언트 측의 프로토콜 스택은 FIN을 1로 설정한 TCP 헤더를 만들고 IP 담당 부분에 의뢰하여 서버에 보낸다.
  5. 서버에서 ACK 번호가 돌아온다.
    클라이언트와 마찬가지로 FIN이 1인 패킷을 받았다는 것을 확인하기 위해 서버에서 ACK 번호가 돌아온다. 이렇게 되면 서버와의 대화가 끝이난다.
  6. 소켓을 말소한다.
    소켓이 이제 필요 없기 때문에 말소한다. 하지만 오동작을 막기위해 바로 소켓을 말소하지 않고 잠시 기다린 후 소켓을 말소한다. 오동작은 굉장히 많은 경우가 있지만 하나의 간단한 예시를 들자면, 만약 마지막에 서버에 FIN을 보내고 ACK가 돌아오지 않는 경우가 그 중 하나이다. 그렇게 되면 FIN을 다시 보내야하기 때문에 소켓을 말소하지 않고 잠시 기다린다.

'CS' 카테고리의 다른 글

[보안] JWT 토큰에 대해서 알아보자  (0) 2023.07.21
[CS] SHA-256 + Salt  (1) 2023.07.13
[Network] 브라우저는 어떻게 웹 서버로 요청을 보내는걸까?  (0) 2023.05.17
[CS] Web Socket  (0) 2022.12.13
[CS] Web Server와 WAS  (2) 2022.12.05