네트워크를 통한 데이터 송/수신

Violet_Evgadn·2023년 4월 26일
0

네트워크

목록 보기
9/37

데이터 송/수신 동작 개요

개요

이제 브라우저는 Request Message도 만들었고 OS에 메시지 송신을 의뢰하기 위해 IP 주소도 찾아냈다.

남은 것은 브라우저 측에서 OS 내부에 있는 프로토콜 스택에 데이터(Request Message)를 목적지(웹 서버 IP 주소)까지 보내달라고 의뢰하는 단계이다.

OS 내부 프로토콜 스택에 메시지 송신을 의뢰할 때에도 이전에 사용했던 "Socket 라이브러리"를 활용한다.

하지만 IP 주소를 조회할 때는 메서드 1개(gethostbyname)만 사용한 것과 반면에 이 단계에선 여러 개의 메서드를 순서대로 사용해야 한다.

즉, OS 내부 프로토콜에 메시지 송신을 의뢰할 때는 라이브러리에 존재하는 복수의 프로그램을 결정된 순번대로 실행시켜야 하기 때문에 훨씬 복잡한 작업이라 할 수 있다.

이 과정은 조금 복잡하기 때문에 바로 학습을 들어가기엔 이해가 어려울 수 있다.

따라서 세세한 과정을 공부하기 전 데이터 송/수신 동작 전체의 개념부터 살펴보자.

데이터 송/수신 동작 개념

Socket 라이브러리를 활용하여 데이터를 송/수신하는 동작을 이미지로 표현하면 위 사진과 같다.

클라이언트와 서버 사이에는 데이터가 흐를 수 있는 통로(파이프)가 존재하며 이 파이프를 통해 서버에서 클라이언트로, 혹은 클라이언트에서 서버로 데이터를 보낼 수 있다.(초록색 원기둥)

데이터는 별다른 문제가 없다면 전달되는 방향이 바뀌지 않는다.

예를 들어 클라이언트가 파이프에 데이터를 주입할 경우 별다른 문제가 없다면 무조건 서버 쪽으로만 데이터가 흐르는 것이다.

두 번째로 파이프 입구의 어느 쪽에서든 데이터를 주입할 수 있다.

즉, 서버 측에서도 데이터를 주입할 수 있고 클라이언트 측에서 데이터를 주입할 수 있다.

따라서 파이프가 1개만 존재하더라도 양방향으로 데이터가 흐를 수 있게 되는 것이다.

그리고 파이프의 입구, 즉 데이터를 주입할 수 있는 공간을 "소켓"이라고 부른다.

소켓과 파이프를 통해 본 데이터 송/수신 동작

위 그림만 보면 서버와 클라이언트를 연결해 주는 파이프 및 소켓이 처음부터 존재하는 것처럼 보인다.

하지만 실제로는 데이터를 송/수신하기 전 먼저 송/수신하는 양자 사이를 파이프로 연결하는 동작이 선행되어야 한다.

그리고 송/수신 양자 사이를 파이프로 연결하는 과정에서 중요한 역할을 하는 것이 파이프의 입구 역할을 하는 소켓이다.

데이터 송/수신 동작은 크게 4단계로 요약할 수 있다.

  1. (클라이언트 측) 소켓을 만든다.
  1. 서버 측의 소켓에 파이프를 연결한다.
  1. 연결된 파이프를 통해 데이터를 송/수신한다.
  1. 파이프를 분리하고 소켓을 말소한다.

아래에서 자세히 설명하겠지만 여기서도 간단히 각 단계를 설명하고 넘어가겠다.

먼저 서버 측 소켓은 서버 프로그램이 실행된 직후 만들어져 있는 상태로써 클라이언트 소켓이 파이프를 연결하기를 기다린다.

그리고 클라이언트 측에서 Reqeust Message를 보내야 할 경우에 클라이언트 측은 소켓을 생성하고 이 소캣에서 파이프를 늘려 서버 측의 소켓에 연결하게 된다.

이렇게 파이프가 연결된다면 Request Message와 Response Message를 주고받으며 통신을 수행한 뒤 통신이 종료되면 파이프를 분리시킨다.

이때 어느 쪽에서 먼저 파이프를 분리할지는 상관없으나 HTTP 1.0에서는 서버 측에서 먼저 파이프를 분리한다.

위 4가지 동작을 실행하는 것이 바로 OS 내부의 프로토콜 스택인 것이다.

그렇다면 이제 각 단계에 대해서 조금 더 자세히 알아보자.


메시지 송/수신 동작 자세히 보기

공부하기 전 알아 둘 것

각 단계를 공부하기 전 미리 알아 두어야 할 것이 있다.

브라우저가 OS의 프로토콜 스택에 의뢰하는 동작은 Socket 라이브러리에 존재하는 데이터 송/수신용 프로그램이 애플리케이션에서 의뢰를 받아 그대로 프로토콜 스택에 전달하는 중간 과정이 필요하다.

이전 DNS 리졸버를 공부했을 때 브라우저가 gethostbyname 명령을 실행시키면 바로 OS 내부 프로토콜 스택으로 가는 것이 아닌 Socket 라이브러리를 잠시 들렀다 가는 것을 생각하면 이해가 편하다.

하지만 이 데이터 송/수신용 프로그램은 단순히 데이터를 전달해주는 중개역 역할을 할 뿐 실질적인 작업과는 큰 관계가 없다.

따라서 무리하게 Socket 라이브러리의 존재를 나타내 학습을 어렵게 하기보다는 Socket 라이브러리와 프로토콜 스택을 한 몸으로 간주하여 설명할 것이다.

하지만 실제 네트워크 환경에서는 Socket 라이브러리의 데이터 송/수신용 프로그램이 중개역 역할을 하는 과정이 추가된다는 것을 알고 있자.

1. (클라이언트 서버) 소켓 생성

클라이언트는 socket 메서드를 호출함으로써 클라이언트 측 소켓을 만들 수 있다.

socket을 호출할 경우 리졸버를 호출했을 때와 동일하게 제어권이 socket 내부로 넘어간다.

이후 소켓이 정상적으로 생성될 경우 다시 제어권이 애플리케이션으로 돌아온다.

소켓을 생성할 경우 "디스크립터"라는 정수 값이 생성된다.

이 디스크립터는 소켓을 구별하기 위해 사용하는 고유 값이라고 생각하면 된다.

그렇다면 왜 소켓에는 디스크립터라는 고유 값이 필요할까?

최근 윈도우 같은 OS 환경에서는 Multitasking이 가능하다. 즉, 브라우저 창 2개를 열고 각자 다른 사이트에 동시에 접근할 수 있어야 하는 것이다.

만약 소켓을 구분하는 디스크립터 값이 없다면 어떤 소켓에서 보낸 Request에 대한 응답인지 알 수가 없어진다.

따라서 데이터 송/수신이 동시에 진행되더라도 소켓들을 식별하여 Request를 보낸 소켓에 적절한 응답이 가게 하기 위하여 소켓 고유 값인 디스크립터가 생성되는 것이다.

2. 클라이언트 소켓과 서버 소켓을 연결하는 파이프를 생성하는 접속 단계

1번 과정을 통해 클라이언트 소켓이 정상적으로 만들어졌다면 이제 클라이언트 측 소켓에서 파이프를 늘려 서버 소켓과 연결되어야 한다.

이 과정은 Socket 라이브러리의 "connect" 메서드를 수행함으로써 실행할 수 있다.

connect 함수를 사용할 때 필요한 파라미터들이 중요한데 바로 "디스크립터", "서버 IP 주소", "포트 번호"이다.

먼저 디스크립터는 1번 과정에서 소켓을 만들고 반환된 디스크립터 값을 주입해 주면 된다.

또한 서버 IP 주소도 DNS 서버에서 조회한 결과를 주입해 주면 된다.

문제는 포트 번호이다.

IP 주소는 어디까지나 컴퓨터(정확히 말하자면 네트워크 기기에 장착된 네트워크용 HW)를 식별하기 위한 고유 값이다.

즉, IP 주소를 통해서는 어디까지나 웹 서버 컴퓨터에 어떻게 도달하는지까지만 지정할 수 있다.

데이터를 송/수신 하는 것은 웹 서버 측의 소켓에 대하여 이루어지기 때문에 웹 서버에 존재하는 소켓을 지정해줘야 한다.

하지만 IP 주소만으로는 이 소켓까지 지정할 수 없다.

따라서 웹 서버의 어떤 소켓을 사용할지 결정하기 위한 값이 필요한데 이 값이 바로 포트 번호이다.

프로토콜 스택은 IP 주소와 포트 번호 2가지를 지정하여 웹 서버의 어느 소켓에 접속할지를 명시해야 소켓 사이를 잇는 파이프를 만들 수 있다.

IP 주소가 "OO아파트 A동"이라면 포트 번호는 "편지를 받아야 하는 사람"을 의미하는 것이다.

그럼 이런 의문이 들 수 있다. 왜 굳이 포트 번호를 사용할까? 모든 소켓마다 고유 값이 디스크립터가 존재한다면 웹 서버의 연결하고 싶은 소켓 또한 디스크립터를 가질 텐데 이 값을 활용하면 되지 않을까?

디스크립터는 소켓을 만들도록 의뢰한 애플리케이션에 건네주는 값으로 외부 애플리케이션에서는 이 값을 확인할 수 없다.

즉, 웹 서버 측 소켓의 디스크립터는 웹 서버 측만 알고 있을 뿐 접속 상대(Clinet; 브라우저)는 이 값을 알 수 없는 것이다.

따라서 디스크립터 값을 통해 연결하고 싶은 웹 서버의 소켓을 지정할 수 없는 것이다.

반대로 포트 번호와 IP 주소만 있으면 모든 소켓이 식별 가능하니 디스크립터 값이 필요 없다 생각할 수 있을 것이다.

이런 상황을 이해하기 위해선 포트 번호를 조금 더 깊이 알 필요가 있으므로 나중에 자세히 다루도록 하겠다.

그렇다면 포트 번호는 몇 번으로 지정하면 될까?

이전에 공부했던 URL, DNS 서버의 IP 주소에는 포트 번호가 적혀 있지 않았던 것 같은데 말이다.

결론부터 말하면 간단한데, 바로 규칙에 의해 사용할 포트 번호가 미리 정해져 있는 것이다.

예를 들어 HTTP는 80, HTTPS는 443, 메일 서버는 25번으로 지정되어 있다.

즉, URL의 프로토콜이 정해졌다면 포트 번호도 자연스럽게 정해지는 것이다.

그렇다면 웹 서버는 어떻게 클라이언트(브라우저)의 소켓을 인지할 수 있을까?

클라이언트 측 소켓은 만들어지는 과정에서 프로토콜 스택이 적당한 값을 골라 포트 번호를 할당해 준다.

이렇게 할당된 포트 번호를 파이프를 만들며(접속 동작을 수행하며) 웹 서버 측에 알려줌으로써 웹 서버도 IP 주소와 포트번호를 통해 클라이언트 소켓을 특정할 수 있게 된다.

3. 메시지를 실제로 주고받는 송/수신 단계

이제 소켓이 상대측과 파이프를 통해 연결되었으므로 실제 데이터를 주고받는 과정을 수행할 수 있다.

여기에서 송신할 데이터는 Request Message일 것이고 수신할 데이터(웹 서버에서 보낼 데이터)는 Respond Message가 될 것이다.

먼저 Request Message를 보낼 때 활용하는 Socket 라이브러리의 메서드는 "write"이다.

애플리케이션은 송신 데이터를 메모리에 준비한 뒤 디스크립터와 송신 데이터를 write의 Parameter 값으로 넘긴다.

웹 서버의 IP 주소와 Port 번호를 저장하고 있는 소켓에는 연결 상대가 기록되어 있으므로 데이터를 송신할 소켓의 디스크립터를 통해 사용할 소켓을 지정하면 2번 과정에서 구축된 파이프를 통해 메시지가 송신되는 것이다.

Respond Message를 받을 때 활용하는 Socket 라이브러리의 메서드는 "read"이다.

이 떄 수신한 응답 메시지를 저장하기 위한 메모리 영역을 지정하는데 이 메모리 영역을 "수신 버퍼"라고 한다.

응답 메시지가 돌아오면 read 메서드가 지정된 수신 버퍼에 데이터를 저장하게 될 것이다.

이 메모리 영역은 애플리케이션 프로그램 내부에 마련된 메모리 영역이므로 브라우저(애플리케이션 프로그램)는 수신 버퍼에 접속하여 데이터를 추출함으로써 응답 메시지를 읽을 수 있게 되는 것이다.

4. 연결을 끊고 파이프를 분리하는 단계

브라우저가 데이터 수신까지 완료하면 송/수신 동작은 끝이 난다.

이후 Socket 라이브러리의 "close" 메서드를 수행함으로써 소켓 사이에 파이프로 연결되었던 연결을 끊을 수 있다.

소켓 사이를 연결한 파이프가 완벽히 분리되었다면 클라이언트 측 소켓도 말소되게 된다.

기본적인 연결 끊기 동작은 아래와 같다.

웹에서 사용하는 HTTP 프로토콜에서는 웹 서버가 응답 메시지 송신을 완료했을 경우 웹 서버 측에서 먼저 연결 끊기 동작을 실행한다.

즉, 웹 서버 측에서 먼저 close 함수가 호출되어 연결 끊기가 진행되는 것이다.

이후 클라이언트 측은 웹 서버 측에서 close 함수가 호출되었음을 전달받고 클라이언트 소켓 또한 연결 끊기 단계로 들어가는데 브라우저에게 read의 결과물로써 송/수신 동작이 완료되어 연결이 끊겼음을 통보한다.

브라우저는 이를 인지하고 브라우저에서도 close 함수를 실행시킴으로써 연결 끊기를 진행한다.

이는 HTTP 1.0 버전에서 활용했던 본래 동작이다.

하지만 이 동작에는 문제점이 존재한다.

이전에 미디어가 들어간 HTML 파일을 표기할 경우 (미디어 파일 개수 + 1)만큼 Request를 진행해야 한다고 말했다.

HTTP Protocol은 HTML 문서와 미디어 데이터를 위한 Request를 모두 별도의 것으로 취급하기 때문에 (미디어 파일 개수 + 1) 번 만큼 접속, 메시지 송신, 응담 메시지 수신, 연결 끊기 동작을 반복하게 된다.

즉, 미디어 개수가 많이 포함되어 있을수록 접속, 데이터 송/수신, 연결 끊기 동작이 여러 번 반복되므로 비효율적인 동작이 된다.

따라서 HTTP 버전 1.1에서는 리퀘스트마다 연결을 끊지 않고 복수의 리퀘스트 및 응답을 주고받을 수 있으며 모든 리퀘스트가 종료되었을 때 연결 끊기 동작을 수행하는 방법이 마련되었다.

이 경우 리퀘스트해야 할 데이터가 없어진 상태여야지만 브라우저에서 연결 끊기 동작에 들어갈 수 있다.

profile
혹시 틀린 내용이 있다면 언제든 말씀해주세요!

0개의 댓글