본문 바로가기
공부/Network

[Network] CH6. 웹 서버에 도착하여 응답 데이터가 웹 브라우저로 돌아간다.

by persi0815 2025. 2. 24.
성공과 실패를 결정하는 1%의 네트워크 원리를 읽고 정리한 내용입니다. 

목차

  1. 서버의 개요
  2. 서버의 수신 동작
  3. 웹 서버 소프트웨어가 리퀘스트 메시지의 의미를 해석하여 요구에 응한다. 
  4. 웹 브라우저가 응답 메시지를 받아 화면에 표시한다. 

패킷은 웹 서버 앞의 방화벽, 캐시 서버, 부하 분산 장치를 통과한 후, 웹 서버 안으로 들어가는데, 해당 내용에 대해 다루려 한다.


1️⃣ 서버의 개요 (구조)

서버는 동시에 복수의 클라이언트와 통신 동작을 실행한다. 다만, 하나의 프로그램으로 여러 클라이언트들의 상대를 처리하는 것은 각 클라이언트와의 대화 진행 상황을 파악해야 하기에 어렵다. 

-> 그래서 클라이언트가 접속할 때마다 새로운 서버 프로그램을 작동하여 서로 애플리케이션이 클라이언트와 1대1로 대화한다. 

서버 프로그램은 접속을 기다리는 부분(a)과 클라이언트와 대화하는 부분(b)으로 나누어진다. 
1. 프로그램을 작동하면 a부분이 실행되어 소켓이 작성되고, 클라이언트의 접속 동작을 기다리는 상태가 된다. 
2. 클라이언트가 접속하면 a부분이 작동되고, 이후 b부분을 작동시켜 접속이 끝난 소켓을 건네준다. 
3. 소켓을 받은 b는 해당 소켓을 사용하여 클라이언트와 대화를 시작해나간다.
*클라이언트와 대화하는 b부분은 클라이언트와 1대1로 대응되어 자신에게 해당하는 클라이언트만 상대하게 된다.

 

서버 OS는 멀티태스크 또는 멀티스레드 기능에 의해 다수의 프로그램을 동시에 작동할 수 있는데, 위와 같은 작동은 해당 성질을 이용한 프로그래밍 기법이다. 

 

위의 방법은 클라이언트가 접속 시 새로 프로그램을 기동하는 부분에서 시간이 걸리는 단점이 있다. 

-> 그래서 미리 클라이언트와 대화하는 몇개의 부분(b)을 작동시켜 두고 클라이언트가 접속했을 때 비어있는 것을 찾아 접속한 소켓을 건네주어 대화를 계속하기도 한다. 


이제 더 구체적으로 서버 애플리케이션이 Socket 라이브러리를 호출하여 프로그램을 작동시키는 부분에 대해 알아보자. 

 

[접속 기다리는 동작]

1. socket()을 호출해 소켓을 생성한다. 

2. bind()를 호출해 소켓에 포트 번호를 기록한다. * 클라이언트측에서 서버측에 포트 번호를 지정

3. listen()을 호출해 소켓에 클라이언트가 접속하기를 기다린다. 

4. accept()을 호출하여 접속 접수 동작을 실행한다. 

 

*accept()는 클라이언트의 패킷이 도착하지 않았을 때 보통 호출되어 기다리는 상태가 된다. 패킷 도착시 응답 패킷 반송하여 접속 접수를 한다. 이때 접속 대기의 소켓을 복사하여 새로운 소켓을 만들고, 접속 제어 정보를 새 소켓에 기록한다. 

-> 원래 있던 접속 대기의 소켓은 계속 존재하며 접속 접수 동작을 실행한다. 이렇게 잇달아 복사하며 새 소켓을 만듦으로써, 다른 클라이언트들도 접속할 수 있도록 하는 것이다. 

 

접속을 접수하는 부분(a)은 클라이언트와 대화하는 부분(b)을 기동하고, 소켓을 클라이언트와 주고받는 부분에 전해준다.

 

[클라이언트와 대화]

1. read()

2. write()

3. close()

 

해당 부분은 ch1에 자세히 나와있다. 

 

소켓을 복사한다고 했는데, 그러면 포트번호가 중복되어 식별이 어렵다는 문제가 발생하는 거 아닌가?

소켓을 복사할 때 새로 만든 소켓에 같은 포트 번호를 할당해야 제대로 된 회답의 패킷이 돌아왔는지 판별할 수 있다. 그런데, 이때 같은 번호를 할당한 여러 소켓이 존재하게 되어 포트 번호로 소켓을 지정할 수 없게 된다.  

-> 그래서 다음 네가지 정보를 사용하여 소켓을 지정한다. 

1. 클라이언트측의 IP 주소 // 다른 클라이언트 끼리는 동일한 포트 번호를 사용할 수 있기에  IP 주소도 사용
2. 클라이언트측의 포트 번호 // 클라이언트 측의 소켓은 모두 다른 포트 번호를 할당
3. 서버측의 IP 주소
4. 서버측의 포트 번호

 

해당 4가지 정보로 개별 소켓 식별할 수 있으면, 디스크립터는 사용 안해도 되는거 아닌가? 

아니다. 접속 대기의 소켓에는 클라이언트측 IP 주소와 포트 번호가 기록되어 있지 않다. 또한, 디스크립터라는 한 개의 정보로 소켓을 식별하는 쪽이 더 간단하다.  

 


2️⃣ 서버의 수신 동작

이제 클라이언트가 보낸 패킷이 서버에 도착하여 이루어지는 과정들에 대해 알아보자. 

 

1. LAN 어댑터에서 수신 신호를 디지털 데이터로 변환한다. 

LAN에 흐르는 신호는 1와 0으로 이루어진 디지털 데이터 신호와 타이밍 나타내는 클록신호의 합성이다. 클록 신호를 추출하고 이의 타이밍을 계산하며 신호를 읽어오면 디지털 신호를 알아낼 수 있다. 

 

전압이 (+) -> (-) 로 가면 0을 대응, (-) -> (+)로 가면 1을 대응시켜 전기 신호의 변화를 디지털 신호로 바꾼다.

 

 

2. FCS(프레임 체크 시퀸스)라는 오류 검사용 데이터를 이용하여 오류 유무를 검사한다. 

디지털 데이터로 되돌리는 것을 오류 검사의 계산식에 따라 계산하고, 패킷 맨 끝에 있는 FCS 필드값과 비교한다. FCS는 송신 시, 전기 신호로 변환하기 전의 디지털 데이터를 바탕으로 계산된 것이다. 두 값이 일치하지 않으면 신호가 변형되었다는 뜻이므로 해당 패킷은 버린다. 

 

3. MAC 헤더에 있는 수신처 MAC 주소를 조사하여 자신을 수신처로 하여 보낸것인지 판단한다. 

이더넷의 기본 동작은 신호를 LAN 전체에 흘리고 해당자만 신호를 수신하는 방법을 취해, 수신처는 자신으로 되어 있는 패킷만 남기고 다른 패킷은 버리게 된다.

 

=> 즉, LAN 어댑터의 MAC 부분이 패킷을 신호로부터 디지털 데이터로 되돌리고, FCS와 수신처를 점검한 후, 버퍼 메모리에 저장한다. 

 

4. LAN 드라이버는 패킷을 추출하고 프로토콜 판별한 후, CPU 통해 프로토콜 스택에 패킷을 건네준다.

CPU는 인터럽트를 통해 LAN 어댑터로의 패킷 도착 사실을 인지한다. 그러면 CPU는 실행하고 있던 작업을 중단하고, LAN 드라이버로 실행을 전환한다.

-> 이렇게 동작을 시작한 LAN 드라이버는 LAN 어댑터의 버퍼 메모리에서 수신한 패킷을 추출하고, MAC 헤더의 타입 필드의 값에 따라 프로토콜을 판별하고, 프로토콜을 처리하는 소프트웨어(ex. TCP/IP 프로토콜 스택)를 호출하여 패킷을 건네준다. 

 

5. IP 담당 부분이 패킷을 처리한다. 

IP 담당 부분이 IP 헤더에서 수신처 IP를 조사해 자신이 수신처가 맞는지 확인한다.

 

자신이 수신처가 아닐 경우, 라우터 기능이 활성화 된 경우, 라우팅 테이블(경로표)에서 중계 대상을 조사하고, 그것에 패킷을 전달한다. 

 

자신이 수신처가 맞다면, 패킷이 분할되었는지 조사한다. 분할된 경우, 메모리에 일시적으로 저장한 후, 모든 패킷 조각이 모였을 때 패킷을 조립하여 원래 패킷을 복원한다. 

 

-> 이후,  IP 헤더의 프로토콜 번호 항목을 조사하여 해당하는 전송계층 담당 부분에 패킷을 건넨다. 06이라면 TCP, 11이라면 UDP 담당 부분에 건넨다. 

 

6. (TCP) 헤더의 SYN을 확인하고 접속을 접수한다.

SYN 컨트롤 비트가 1이면 접속 동작을 시작한다.

 

수신처 포트 번호 조사하여 해당 번호와 일치하는 접속 대기 상태의 소켓이 있는지 확인한다. 없으면 오류를 통지하고, 있다면 새 소켓을 만든다.

새 소켓에 필요한 정보를 기입하고 송신, 수신 버퍼로 이용되는 메모리 영역을 확보한다.

 

이후, ACK 번호나 SYN 번호 초기값, 윈도우 값 등 제어 정보를 기록한 TCP 헤더를 만들고, IP 담당 부분에 의뢰하여 클라이언트에 반송한다. 

 

ACK 번호가 돌아오면 접속 동작은 완료된다. 

-> 서버측에게 새로 만든 소켓의 디스크립터를 전달하여 서버 애플리케이션의 동작을 재개한다. 

 

7. TCP 담당 부분이 데이터 패킷 수신한 후, IP와 포트번호로 소켓 찾아 데이터 수신 후 저장하고, 응답을 반송한다. 

이제 송 수신 단계에 들어서서 TCP 담당 부분이 도착한 데이터 패킷이 어느 소켓에 해당하는지 송수신처 IP 주소와 포트 번호를 이용하여 조사한다. 

 

소켓 발견하면 소켓에 기록된 제어 정보와 패킷의 TCP 헤더 정보를 비교해 송수신 동작 정상 여부를 판단한다. 이는 구체적으로 소켓에 기록된 시퀀스 번호와 데이터 조각의 길이를 통해 다음 시퀀스 번호를 계산하고, 도착한 패킷의 TCP 헤더에 기록된 시퀀스 번호를 비교하는 과정이다.

 

정상이라면, 패킷에서 데이터 조각을 추출해 메모리 영역(수신 버퍼)에 저장하고, 수신 확인 응답용 TCP 헤더를 만든다. 수신 패킷의 시퀸스 번호와 데이터 조각의 길이로 계산한 ACK 번호를 기록하고, IP 담당 부분에 의뢰하여 클라이언트에 반송한다. 

 

8. 서버 애플리케이션은 데이터를 요청하고 받아서 처리한다.  

애플리케이션이 read()를 호출하여 수신 버퍼의 데이터를 요청하면, TCP 담당 부분은 데이터를 건네주게 된다. 이후 제어가 서버 애플리케이션으로 넘어가서 받은 데이터를 처리한다. 즉, HTTP 리퀘스트 메시지 내용을 조사하고, 내용에 따라 브라우저에 데이터를 반송한다. 

 

9. 송수신이 끊나면 연결 끊기 동작을 실행한다. 

TCP 프로토콜의 경우 연결 끊기 동작을 클라이언트와 서버 중 어느 쪽이 먼저 실행해도 상관이 없는데, 웹의 HTTP 1.0 의 경우 서버에서 연결 끊기 동작을 시작한다. 서버측에서 close()를 호출하여 TCP 담당 부분이 FIN 컨트롤 비트에 1 설정한 후 IP 부분에 의뢰하여 클라이언트에 보내고, 클라이언트는 ACK 번호를 반송한다. 클라이언가 close를 호출하고 FIN을 1로 설정한 TCP 헤더를 서버에 보내고 서버가 ACK 번호를 반송하면 연결 끊기 동작은 끝난다. (4 HandShake)

 

HTTP 1.1의 경우에는 어느 쪽이 먼저 실행해도 상관없다. 


3️⃣ 웹 서버 소프트웨어가 리퀘스트 메시지의 의미를 해석하여 요구에 응한다.

프로토콜 스택의 read()가 수행되면, HTTP 리퀘스트 메시지를 읽고, 적절한 처리를 실행하여 응답 메시지를 만든다. 그리고, write()를 통해 이것을 클라이언트에 반송한다. 

 

리퀘스트 메시지는 메소드라는 일종의 명령과 데이터 출처를 나타내는 URI라는 파일의 경로명 같은 것이 쓰여있으며, 내용에 따라 데이터를 클라이언트에 반송한다. HTTP 메서드나 URI의 내용에 따라 작업 내용은 매우 다양하며, 서버 내부의 동작이 달라진다. 

 

그런데, 단순히 URI에 기록되어있는 파일을 디스크에서 읽지 않는다. 디스크 파일에 전부 액세스할 수 있게 되어 웹 서버의 디스크가 무방비 상태로 노출되어 위험해질 수 있기 때문이다. 

-> 이를 해결하기 위해 웹서버에서는 실제 디렉토리가 아닌 가상 디렉토리를 공개한다. 가상의 경로명을 URI에 쓰면, 가상의 디렉토리와 실제 디렉토리의 대응 관계를 조사하고, 실제 디렉토리의 경로명으로 변환한 후, 파일을 읽어 데이터를 반송한다. 

 

URI의 파일 내용이 HTML 문서나 화상 데이터인 경우 내용을 그래도 응답 메시지로 클라이언트에 반송한다. 하지만, 만일 프로그램 파일의 이름이라면, 해당 프로그램을 작동시켜서 프로그램이 출력하는 데이터를 클라이언트에 반송한다. 


웹 서버로 수행하는 액세스 제어

웹 섭의 기본 동작은 리퀘스트 메시지의 내용에서 데이터 출처를 판단하고, 그곳에서 클라이언트에게 반송하는 것이다. 

 

그러나, 사전에 설정해 둔 조건에 해당하는지 조사하고, 조건에 해당하는 경우에만 이후 동작을 수행하도록 조건에 따라 동작 여부를 설정할 수 있는데, 이러한 기능을 액세스 제어라고 한다. 

 

보통 클라이언트의 주소, 클라이언트의 도메인명, 사용자명과 패스워드라는 조건을 사용해 액세스 제어를 사용한다. 

 

1. 클라이언트의 IP

: accept()를 통해 파악한 클라이언트의 IP와 조건으로 설정한 IP를 비교하여 일치 여부를 판단한다. 

 

2. 클라이언트의 도메인명

: DNS 서버를 이용해 클라이언트의 IP 주소에서 도메인명을 조사한다. 

 

위와 같은 순서로 클라이언트 IP주소에서 도메인명을 판명하고, 만일에 대비하여 도메인명에서 IP주소를 조사한 후, 송신처 IP 주소와 일치하는 것을 확인하여 이중 체크한다. 이후, 도메인명을 설정한 조건을 대조하여 액세스할 수 있는지 판단한다. 

 

3. 사용자명과 패스워드

사용자가 사용자명과 패스워드를 입력하면 이것을 리퀘스트 메시지에 기록하고, 다시 한 번 서버에 액세스한다. 웹 서버는 통지된 사용자명과 패스워드와 사전에 설정한 것을 대조하여 액세스 가능 여부를 판단하고, 액세스를 허가하는 경우에는 데이터를 반송한다. 

 

=> 리퀘스트 메시지에 대해 적절하게 처리하고, 처리가 완료되면 응답 메시지를 반송한다. write() 호출하여 응답 메시지를 프로토콜 스택에 건네준다. 디스크립터를 함께 통지하여 상대 소켓을 지정해줘야 한다. 소켓에는 통신의 상태가 전부 기록되어 있으니 디스크립터만 통지하면 된다. 

 

-> 그러면 프로토콜 스택은 데이터를 한 개의 패킷에 들어가는 길이로 분할하고 헤더를 붙여서 패킷을 송출한다. 해당 패킷은 스위치나 라우터를 경유하여 인터넷 속을 통해 최종적으로 클라이언트에게 도착한다. 


4️⃣ 웹 브라우저가 응답 메시지를 받아 화면에 표시한다.

웹 서버가 보낸 응답 메시지는 다수의 패킷으로 나뉘어 클라이언트에 도달하고, 클라이언트 또한 위에서 살펴본 과정을 거친다. 

즉, LAN 어댑터가 신호로부터 디지털 데이터로 변환한 후 프로토콜 스택이 분할된 패킷을 모아 데이터를 추출한다. 이후, 응답 메시지를 브라우저에게 건네고, 브라우저의 화면 표시 동작이 진행된다. 

이때 데이터 종류에 따라 표시 방법이 다르기 때문에, 데이터 종류부터 판단해야 한다. 

 

'Content-Type'이라는 헤더 파일의 값으로 데이터 종류를 판단한다. (원칙)

text/html과 같은 형식으로 나타나며, 왼쪽은 주 타입, 오른쪽은 서브 타입을 의미한다. 주 타입은 데이터 종류의 대분류이며, 서브 타입은 실제 데이터의 종류이다.

 

'Content-Type'을 조사한 경우, 'Content-Encodeing'이라는 헤더 필드의 값도 조사해야 하는데, 특정 기술로 원래 데이터를 변환한 경우, 이곳에 기록해야 하기 때문이다. 그러나, 'Content-Type'은 정확하지 않은 경우가 있는데, 이런 경우에는 파일 확장자나 데이터 내용의 포맷 등을 통해 종합적으로 판단한다. 

 

데이터 종류를 판단한 이후에는, 브라우저 화면에 웹 페이지를 표시하여 액세스를 완료한다. 

HTML, 일반 텍스트, 화상이라는 기본적인 데이터는 브라우저 자체에서 화면 표시 동작을 실행한다.

표시 동작은 데이터 종류에 따라 달라지는데, HTML의 경우, 태그의 의미를 해석하여 문장을 배치하며 화면에 표시한다. 실제 화면 표시 동작은 os가 담당하므로 os에 대해 태그를 통해 표시를 지시하는 것이다. 

 

워드 프로세서나 프레젠테이션 소프트웨어 등 브라우저가 자체 표시 기능을 가지지 않는 경우에는, 데이터 종류에 따른 애플리케이션을 호출해서 데이터를 건네게 되고, 이로써 화면에 호출한 프로그램이 표시된다. 


참고자료

https://product.kyobobook.co.kr/detail/S000000559964

 

1%의 네트워크 원리 | Tsutomu Tone - 교보문고

1%의 네트워크 원리 | 성공과 실패를 결정하는 1%의 네트워크 원리 (2nd Edition(개정1판 10쇄))이 책만큼 네트워크의 구조와 작동 원리에 대해 체계적으로 설명한 책은 없다! 이 책은 네트워크 기술을

product.kyobobook.co.kr