학교/네트워크

[네트워크] 6. 웹 서버에 도착하여 응답 데이터가 웹 브라우저로 돌아간다.

daykim 2023. 4. 19. 16:19
아래 도서 기반 정리
 

성공과 실패를 결정하는 1%의 네트워크 원리 | Tsutomu Tone - 교보문고

성공과 실패를 결정하는 1%의 네트워크 원리 | 『성공과 실패를 결정하는 1%의 네트워크 원리』는 네트워크 전체의 움직임을 해설하고 현실의 네트워크 기기나 소프트웨어가 어떻게 움직이는지

product.kyobobook.co.kr

 

목차

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

 

서버의 개요


1. 클라이언트와 서버의 차이점

  • 네트워크 부분 : LAN 어댑터, 프로토콜 스택, Socket 라이브러리 등 기능은 똑같다.
  • TCP, IP 기능 : 하드웨어, OS가 무엇이든 달라지지 않았다.
  • 접속 동작 :  서버는 클라이어늩에서 접속 동작 수행하는 것을 기다리기 때문에 Socket 라이브러리 사용법이 좀 다르다.
  • 서버의 애플리케이션은 동시에 다수의 클라이언트 PC와 대화한다. 따라서 클라이언트와 서버의 애플리케이션 구조가 다르다.

 

2. 서버 애플리케이션의 구조

서버가 하나의 프로그램으로 다수의 클라이언트를 처리하기는 어렵다. 클라이언트와 어디까지 대화가 진행되고 있는지 전부 파악해야하기 때문이다.

따라서 클라이언트가 접속할 때마다 새로 서버 프로그램을 작동해, 서버 애플리케이션이 클라이언트와 1 대 1로 대화하는 방법이 일반적이다.

  • 접속 기다리는 부분
  • 클라이언트와 대화하는 부분

서버 프로그램은 위와 같은 구조로 이루어져있다.

  • 서버 프로그램을 기다리는 부분을 실행해, 소켓을 작성하고 소켓을 클라이언트에서 접속 동작을 기다리는 상태로 만든채 쉬는 상태가 된다.
  • 클라이언트가 접속을 때 다시 작동해 접속을 기다린다.
  • 클라이언트와 대화하는 부분을 작동시켜 접속이 끝난 소켓을 건네주면, 대화하는 부분에서 소켓으로 클라이언트와 대화한다.
  • 대화가 끝나면, 연결을 끊고 이 부분을 종료한다.

클라이언트와 대화하는 부분은, 새 클라이언트가 접속할 때마다 1 대 1로 대응한다.

  • 멀티 태스크
  • 멀티 스레드
  • 라는 기능으로 동시에 여러 프로그램을 작동한다.
  • 클라이언트가 접속할때 마다 새로 프로그램을 기동하는데 다소 시간이 걸린다는 단점이 있다.
  • 그래서 미리 클라이언트와 대화하는 몇 개의 부분을 작동시켜 두고, 비어있는 것을 찾아 접속한 소켓을 건네주는 방법이 있다.

 

3. 서버측의 소켓과 포트 번호

다양한 형태로 데이터를 송, 수신하는 구조를 지원하려면, 좌우 대칭으로 어디에서나 자유롭게 데이터를 송신할 수 있는 방법이 좋다.

그러나, 어떻게 해도 좌우 대칭으로 만들 수 없는 부분이 접속 동작이다.
한 쪽이 기다리고 있는 곳에 또 한쪽을 연결해야 하기 때문에, 이 부분은 역할 분담이 필요하다.

즉, 데이터 송수신 시점에서 접속을 하는 측이 클라이언트고, 접속을 기다리는 측이 서버다.

이 경우 Socket 라이브러리를 호출하는 부분에 차이가 있다.

클라이언트의 데이터 송수신 동작 서버의 데이터 송수신 동작
1. 소켓 작성 : 소켓을 만든다.
2. 접속 : 서버측 소켓과 파이프로 연결한다.
3. 송수신 : 데이터를 송, 수신한다.
4. 연결 끊기 : 파이프를 분리하고 소켓을 말소한다.
1. 소켓 작성 : 소켓을 만든다.
2 - 1. 접속 대기 상태 : 소켓을 접속 대기 상태로 만든다.
2 - 2. 접속 접수 : 접속을 접수한다.
3. 송수신 : 데이터를 송수신 한다.
4. 연결 끊기 : 파이프를 분리하고, 소켓을 말소한다.

서버 측

// 접속을 기다리는 부분
// 1
<디스크립터1> = socket(<IPv4를 사용>, <TCP 사용>, ...);
// 2-1
bind(<디스크립터1>, <포트 번호 등>, ...);
listen(<디스크립터1>, ...);
// 2-2
<디스크립터2> = accept(<디스크립터1>, ...);
// 클라이언트와 대화하는 부분 호출 -> 제어가 클라이언트로 넘어감


// 클라이언트와 대화하는 부분

// 수신
<수신 데이터 길이> = read(<디스크립터2>, ...);
...
<리퀘스트 메시지 의뢰 내용 처리>;
...
// 송신
write(<디스크립터2>, <송신데이터>, <송신데이터 길이>);
...
close(<디스크립터 2>);
  • bind를 호출해, 소켓에 포트 번호를 기록한다. ex) 웹서버는 80번
  • listen을 호출해 소켓에 접속하기를 기다리는 상태라는 제어 정보를 기록한다.
  • accept 호출해 접속을 접수한다.
    • accept는 서버 애플리케이션을 가동한 후 즉시 실행되고, 패킷이 도착할 때까지 도착을 기다리는 상태가 된다.
    • 이 상태에서 애플리케이션에서 접속 패킷이 도착하면, 응답 패킷을 반송해 접속 동작을 실행한다.
    • 접속 대기의 소켓을 복사해 새로운 소켓을 만들고, 접속 상대의 정보를 비롯한 제어 정보를 새 소켓에 기록한다.

이렇게 소켓이 클라이언트 측 소켓과 연결된다.

  • 원래 있던 접속 대기 상태의 소켓은 계속 대기 상태로 존재하고, 복사해 새 소켓을 만든다.
  • 새 소켓을 만들지 않고 접속하면, 접속 대기의 소켓이 없어져 다음에 다른 클라이언트가 접속하면 곤란해지기 때문이다.

포트 번호

  • 새로 복사한 소켓에 새 포트 번호를 할당하면, 클라이언트 입장에선 송신한 포트 번호와는 다른 번호에서 회답이 돌아오게 되어, 접속이 제대로 이루어진 것인지 판별할 수 없다.
  • 해결방법 : 다음 정보들로 소켓을 지정한다.
    • 클라이언트 측 IP 주소 -> 클라이언트가 여러대일 경우 필요
    • 클라이언트 측 포트 번호
    • 서버 측 IP 주소
    • 서버 측 포트 번호

소켓 식별에 디스크립터 사용 이유

  • 접속이 되기 전에는 클라이언트 측 정보가 없다.
  • 디스크립터 하나로 식별하는 것이 더 간단하다.

 

서버의 수신 동작


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

  • LAN 어댑터에서 패킷 신호를 수신하고, 디지털 데이터로 바꾼다.
  • 디지털 데이터로 변환 후 프레임 체크 시퀀스(FCS)라는 오류 검사용 데이터로 오류 유무를 검사한다.
  • 오류가 없다면, MAC 헤더에 있는 수신처 MAC 주소를 조사해 자신이 수신처가 맞는지 확인한다.
  • 디지털 데이터로 되돌린 것을 LAN 어댑터 내부의 버퍼 메모리에 저장한다.
  • 수신처리 진행을 위해서, 인터럽트를 통해 LAN 어댑터에서 CPU로 패킷의 도착을 알린다.
  • CPU는 LAN 드라이버로 실행을 전환하고, 버퍼 메모리에서 수신한 패킷을 추출한다.
  • MAC 헤더 타입 필드 값에 따라 프로토콜을 판별해 이를 처리하는 소프트웨어를 호출한다.
  • 타입 필드 값은 IP 프로토콜을 나타내므로, TCP/IP의 프로토콜 스택을 호출하고 여기에 패킷을 건네준다.

 

2. IP 담당 부분의 수신 동작

  1. IP 헤더를 점검해 수신처가 본인 IP 인지 확인한다. 
    • 서버에 라우터와 같이 패킷을 중계하는 기능이 유효한 경우, 경로표에서 중계대상을 조사하고 그것에 패킷을 중계한다.
  2. 패킷이 자신을 대상으로 한 것이 아니라면, 조각 나누기에 의해 분할되었는지 조사한다.
    • IP 헤더를 조사해 분할된 패킷이라면, 일시적으로 메모리에 저장해둔다.
    • 분할된 패킷 조각이 모두 도착했다면, 패킷을 조립해 복원한다.
  3. IP 헤더의 프로토콜 번호 항목을 조사해 담당 부분에 패킷을 건네준다.

 

3. TCP 부분이 접속 패킷을 수신했을 때 동작

접속 동작 패킷이 도착한 경우

  • TCP 헤더의 SYN 컨트롤 비트 값이 1인 경우, 접속 동작 패킷이다.
  • 도착한 패킷의 수신처 포트 번호와 같은 번호를 할당한, 접속 대기 상태의 소켓이 있는지 확인한다.
    • 없다면, 오류 통지 패킷을 클라이언트에 반송한다.
  • 있다면, 접속 접수 동작을 실행한다. -> 해당하는 접속 대기 소켓을 복사해 새 소켓을 만들고, 필요한 정보를 기록한다.
  • 동시에 송신 버퍼나 수신 버퍼로 사용하는 메모리 영역을 확보한다.
  • 패킷을 받았음을 나타내는 ACK 번호, 윈도우 값 등을 기록한 TCP 헤더를 만들고, IP 담당 부분에 의뢰해 클라이언트에 반송한다.
  • 클라이언트에서 받았음을 나타내는 ACK 번호가 돌아오면 접속 동작은 완료된다.

 

4. TCP 담당 부분이 데이터 패킷을 수신했을 때 동작

  • TCP 담당 부분은 도착한 패킷이 어느 소켓에 해당하는지 조사한다.
    • 서버에 같은 포트를 가진 소켓이 많으므로, 앞에서 말한 4가지 정보로 적절한 소켓을 찾는다.
  • 소켓을 발견하면, 패킷에 기록된 정보를 통해 데이터 송수신이 올바르게 동작하는지 점검한다.
    • 다음 시퀀스 번호 값을 계산하고, 도착한 패킷의 TCP 헤더에 기록된 시퀀스 번호와 합치되는지 조사한다.
    • 합치되면, 패킷에서 데이터 조각을 추출해 수신 버퍼에 저장한다.
  • 수신 확인 응답용 TCP 헤더를 만들고, IP 담당 부분에 의뢰해 클라이언트에 반송한다.
    • 수신 패킷의 시퀀스 번호, ACK 번호를 기록한다.
  • 수신 버퍼에 기록된 데이터들은 소켓 라이브러리의 read 함수 호출로 애플리케이션에 넘어간다.
  • 애플리케이션에서 HTTP 리퀘스트 메시지 내용을 조사하고, 브라우저에 데이터를 반송한다.

 

5. TCP 담당 부분의 연결 끊기 동작

연결끊기 동작은 클라이언트 측과 같다.

  • 어느 측에서 먼저 실행해도 상관 없다. (여기선 서버 시작으로 가정)
  • close() 호출하고, FIN 컨트롤비트를 1로 설정한 TCP 헤더를 만들어 IP 담당 부분에 의뢰해 클라이언트에 전송한다.
  • 클라이언트에서 수신후 ACK 번호를 반송한다.
  • 클라이언트가 close() 호출하고, FIN을 1로 한 TCP 헤더를 서버에 보낸 후, 서버가 ACK 번호를 반송하면 연결 끊기 동작은 끝난다.
  • 반대 경우도 똑같은 과정을 거친다.
  • 어느 경우든 잠시 기다렸다 소켓을 말소한다.

 

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


1. 조회의 URI를 실제 파일명으로 변환한다.

// 클라이언트와 대화하는 부분

<수신 데이터 길이> = read(<디스크립터2>, ...);
...
<리퀘스트 메시지 의뢰 내용 처리>;
...
write(<디스크립터2>, <송신데이터>, <송신데이터 길이>);
...
close(<디스크립터 2>);
  • read()에서 받은 데이터의 내용이 HTTP 리퀘스트 메시지가 된다.
  • 리퀘스트 메시지에 기록된 내용에 따라 적절한 처리 후 응답 메시지를 만들고, write()로 클라이언트에 반송한다.
  • 이 때, 단순히 URI에 기록된 파일을 디스크에서 읽는 것이 아니다.
    • 디스크 파일에 전부 액세스할 수 있게 되면, 디스크가 무방비 상태로 노출되어 위험하다.
    • 웹 서버에서 공개하는 디렉토리는 실제가 아닌 가상 디렉토리고, 이 가상 디렉토리 구조의 경로명을 URI에 작성 해결한다.
    • 따라서 파일을 읽어올땐 가상 디렉토리와 실제 디렉토리의 대응 관계를 조사하고, 실제 경로명으로 전환 후 파일을 읽어 데이터를 반송한다.

 

2. CGI 프로그램을 작동하는 경우

URI에 쓰는 파일이 프로그램 파일의 이름일 수도 있다.
이 경우 해당 프로그램을 작동시켜, 프로그램이 출력하는 데이터를 클라이언트에 반송한다.

CGI 프로그램의 동작

  • URI에 적힌 파일명을 보고 프로그램인지 확인한다.
    • 파일명의 확장자 명 또는 설정해둔 디렉토리명을 보고 구분한다.
  • 프로그램이라면, 해당 프로그램을 작동시키도록 OS에 의뢰한다.
    • 리퀘스트 메시지 안의 데이터를 추출해 프로그램에 건네준다.
  • 데이터를 처리한 후 출력 데이터를 웹 서버에 돌려준다.
  • 출력 데이터는 HTML 태그를 내장한 문서이므로, 이것을 응답 메시지로 클라이언트에 반송한다.

 

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

액세스 제어

웹 서버에서 리퀘스트 내용에서 데이터 출처를 판단하고, 데이터를 얻어 클라이언트에 전송하는 기본 동작을 실행할 때, 사전에 설정해 둔 조건에 해당하는지 조사하고, 조건에 해당하는 경우 그 동작을 금지하거나 조건에 해당하는 경우만 동작을 실행하는 것이다.

주로 설정하는 조건

  • 클라이언트 주소
  • 클라이언트의 도메인 명
  • 사용자 명과 패스워드

이 조건을 판단해 액세스가 허가된 경우에만 파일을 읽거나, 프로그램을 실행할 수 있다.

  • accept로 접속을 접수했을 때, 클라이언트의 IP 주소를 알 수 있으므로 이 때 점검한다.
  • 도메인 명은 DNS 서버로 요청을 보내 조사한다.
  • 이를 통해 송신처 IP 주소에서 도메인명을 판명하고, 만일을 대비해 도메인 명에서 IP 주소를 조사한 후, 송신처 IP 주소와 일치하는 것을 이중으로 확인한다.
  • DNS 서버의 조회 메시지가 왕래하는 시간만큼, 응답 시간이 길어진다.
  • 사용자 명과 패스워드는, 웹 서버에 기록하거나 리퀘스트 메시지를 보내도록 응답 메시지를 통해 클라이언트에게 통지한다.

 

4. 응답 메시지를 되돌려 보낸다.

응답 메시지 전송은, 최초에 클라이언트가 리퀘스트 메시지를 웹 서버에 보내는 동작과 같다.

  • write() 함수를 호출해 응답 메시지를 프로토콜 스택에 전달한다.
  • 어느 소켓을 사용해 통지할지, 디스크립터를 통지해 상대를 지정한다.
  • 해당 패킷이 프로토콜 스택에 의뢰되어 응답된다.

 

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


1. 응답 데이터의 형식을 보고 본질을 파악한다.

응답 메시지에 저장된 데이터가 어떤 종류인지 조사한다.

  • 'Content-Type' 이라는 헤더의 값으로 판단한다.
  • ex) Content-Type: hext/html -> 주 타입 / 서브 타입
  • p.424 데이터 형식 표
  • 'Content-Encoding' : 압축 기술이나 부호화 기술에 따라 원래 데이터를 변환해 메시지에 저장한 경우, 어떤 변환을 했는지 기록하는 필드
  • 이 필드를 조사해 원래대로 되돌린다.

 

2. 브라우저 화면에 웹 페이지를 표시해 액세스를 완료환다.

데이터의 종류에 따라 화면 표시의 프로그램을 호출해 데이터를 표시한다.

  • HTML의 경우 태그의 의미를 해석해 문장을 배치하며 화면에 표시한다.
  • 실제 화면 표시 동작은 OS가 담당한다.
  • 프레젠테이션과 같은 애플리케이션의 데이터는 자체에서 표시할 수 없어, 해당 애플리케이션을 호출한다.

 

이렇게 브라우저는 표시 동작을 마치고 사용자가 다음 행동을 하길 기다린다.

 

- 끝 -