[WEEK 07] 컴퓨터 시스템 - 11. 네트워크 프로그래밍

신호정 벨로그·2021년 9월 16일
0

Today I Learned

목록 보기
31/89

11.1 클라이언트-서버 프로그래밍 모델

모든 네트워크 응용 프로그램은 클라이언트-서버 모델에 기초하고 있다.

응용이는 한 개의 서버 프로세스한 개 이상의 클라이언트 프로세스로 구성된다.

서버(server)일부 리소스를 관리하고, 이 리소스를 조작해서 클라이언트(client)를 위한 일부 서비스를 제공한다.

클라이언트-서버 모델에서 근본적인 연산트랜잭션이다. 클라이언트-서버 트랜잭션은 네 단계로 구성된다.

  1. 클라이언트가 서비스를 필요로 할 때, 클라이언트는 한 개의 요청(request)을 서버에 보내는 것으로 트랜잭션을 개시한다. 예를 들어 웹 브라우저가 파일을 필요로 할 때, 웹 서버로 요청을 보낸다.

  2. 서버는 요청을 받고, 해석하고, 자신의 자원들을 적절한 방법으로 조작한다. 예를 들어 웹 서버가 브라우저로부터 요청을 받을 때, 디스크 파일을 읽는다.

  3. 서버는 응답(response)을 클라이언트로 보내고, 그 후에 다음 요청을 기다린다. 예를 들어 웹 서버는 이 파일을 다시 클라이언트로 돌려보낸다.

  4. 클라이언트는 응답을 받고 이것을 처리한다. 예를 들어 웹 브라우저가 서버로부터 페이지를 한 개 받은 후, 이것을 스크린에 디스플레이한다.

클라이언트와 서버는 프로세스이며, 머신이나 호스트는 아니라는 것을 인식하는 것이 중요하다.

한 개의 호스트는 서로 다른 많은 클라이언트와 서버를 동시에 실행할 수 있으며, 클라이언트와 트랜잭션은 동일하거나 다른 호스트에 존재할 수 있다.

11.2 네트워크

클라이언트와 서버는 종종 별도의 호스트에서 돌아가며, 컴퓨터 네트워크의 하드웨어 및 소프트웨어 자원을 사용해서 통신한다.

  1. 네트워크는 호스트에게 I/O 디바이스로서 데이터를 위한 소스와 싱크로 서비스한다.

  2. I/O 버스의 확장 슬롯에 꽂혀 있는 어댑터는 네트워크에 물리적인 인터페이스를 제공한다.

  3. 네트워크에서 수신한 데이터는 I/O와 메모리 버스를 거쳐서 어댑터에서 메모리로, 대개 DMA 전송으로 복사된다.

네트워크는 기하학적 위치로 구성된 계층구조 시스템이다.

하위수준은 LAN(Local Area Network)이며 가장 대중적인 LAN 기술은 이더넷(Ethernet)이다.

이더넷 세그먼트는 몇 개의 전선들과 허브라고 부르는 작은 상자로 구성된다.

허브는 각 포트에서 수신한 모든 비트를 종속적으로 다른 모든 포트로 복사한다. 모든 호스트는 모든 비트로 볼 수 있다.

각 이더넷 어댑터는 어댑터의 비휘발성 메모리에 저장된 전체적으로 고유한 48비트 주소를 가진다.

호스트는 프레임이라고 부르는 비트들을 세그먼트의 다른 호스트에 보낼 수 있다.

각 프레임은 프레임의 소스와 목적지, 프레임의 길이를 식별할 수 있는 고정된 헤더 비트를 가지고 있으며, 그 뒤에 데이터 비트가 이어진다.

모든 호스트 어댑터는 이 프레임을 볼 수 있지만 목적지 호스트만이 실제로 이것을 읽어들인다.

전선들과 브릿지라고 하는 작은 상자들을 사용해서 다수의 이더넷 세그먼트가 연결되어 브릿지형 이더넷이라고 하는 더 큰 LAN을 구성할 수 있다.

브릿지는 허브보다 더 높은 전선의 대역폭을 가진다. 브릿지는 어떤 호스트가 어떤 포트에서 도달 가능한지 장기간에 걸쳐 학습하고, 선택적으로 하나의 포트에서 다른 포트로 프레임을 복사한다.

계층구조의 상부에서 다수의 비호환성 LAN들은 라우터라고 부르는 특별한 컴퓨터에 의해서 연결될 수 있으며, 라우터는 네트워크 간 연결을 구성한다.

각 라우터는 이들이 연결되는 각 네트워크에 대해 어댑터(포트)를 가지고 있다.

인터넷의 중요한 특성은 매우 다르고 비호환적인 기술을 갖는 여러 가지 LAN과 WAN들로 이루어져 있다는 점이다.

각 호스트는 물리적으로 다른 모든 호스트에 연결되어 있지만, 어떻게 어떤 소스 호스트가 모든 비호환적인 네트워크들을 지나서 데이터 비트를 다른 목적지 호스트로 전송할 수 있는가?

해답은 네트워크 간의 차이를 줄여 주는 각 호스트와 라우터에서 돌고 있는 프로토콜 소프트웨어의 계층이다.

프로토콜 소프트웨어는 어떻게 호스트들과 라우터들이 데이터를 전송하기 위해서 협력하는지를 결정하는 프로토콜을 구현한 것으로, 두 가지 기본 기능을 제공해야 한다.

  1. 명명법: 서로 다른 LAN 기술은 주소를 호스트에 할당하는 서로 다른 비호환성을 갖는 방법을 사용한다. 인터넷 프로토콜은 호스트 주소를 위한 통일됫 포맷을 정의해서 이 차이점들을 줄인다.

  2. 전달기법: 서로 다른 네트워킹 기술은 서로 다른 비호환성을 갖는 비트 인코딩 방법과 프레임 내에 이들을 패키징하는 방법을 가지고 있다. 인터넷 프로토콜은 데이터 비트를 패킷이라고 부르는 비연속적인 단위로 묶는 통일된 방법을 정의해서 이 차이점을 줄인다.

패킷은 패킷 크기와 소스 및 목적지 호스트 주소를 포함하는 헤더소스 호스트가 보낸 데이터 비트를 포함하는 데이터로 구성된다.

그림 11.7 예제

호스트와 라우터가 호환성이 없는 LAN을 통해서 데이터를 전송하기 위해 인터넷 프로토콜을 어떻게 사용하는지

하나의 라우터에 연결된 두 개의 LAN으로 구성 - 호스트 A에서 돌아가는 LAN1에 연결된 클라이언트는 LAN2에 연결된 호스트 B에서 돌고 있는 서버로 일련의 데이터 바이트를 전송한다.

  1. 호스트 A의 클라이언트는 클라이언트의 가상 주소공간에서 커널 버퍼로 데이터를 복사하는 시스템 콜을 호출한다.

  2. 호스트 A의 프로토콜 소프트웨어는 인터넷 헤더와 LAN1 프레임 헤더를 데이터에 추가해서 LAN1 프레임을 생성한다. 인터넷 헤더인터넷 호스트 B로 주소가 지정된다. LAN1 프레임 헤더라우터로 주소가 지정된다. 호스트 A는 이 프레임을 어댑터로 전달한다.

  3. LAN1 어댑터이 프레임을 네트워크로 복사한다.

  4. 프레임이 라우터에 도달하면, 라우터의 LAN1 어댑터는 전선에서 이것을 읽어서 프로토콜 소프트웨어로 전달한다.

  5. 라우터는 인터넷 패킷 헤더에서 목적지 인터넷 주소를 가져와서 패킷을 전달할 곳을 결정하기 위해, LAN2 라우팅 테이블에서의 인덱스로 이것을 사용한다. 라우터는 이전의 LAN1 프레임 헤더를 벗겨내고, 호스트 B의 주소를 갖는 새로운 LAN2 프레임 헤더를 앞에 부텽서 어댑터로 전달한다.

  6. 라우터의 LAN2 어댑터이 프레임을 네트워크로 복사한다.

  7. 이 프레임이 호스트 B에 도착하면 어댑터는 이 프레임을 전선에서 읽어들이고, 이것을 프로토콜 소프트웨어로 넘긴다.

  8. 호스트 B의 프로토콜 소프트웨어는 패킷 헤더와 프레임 헤더를 벗겨낸다. 프로토콜 소프트웨어는 최종적으로 데이터를 서버가 이 데이터를 읽는 시스템 콜을 호출할 때 서버의 가상 주소공간으로 복사한다.

11.3 글로벌 IP 인터넷

인터넷 클라이언트-서버 응용의 기본적인 하드웨어 및 소프트웨어 구조

각 인터넷 호스트는 TCP/IP 프로토콜(Transmission Control Protocol/Internet Protocol)을 구현한 소프트웨어를 실행한다.

인터넷 클라이언트와 서버는 소켓 인터페이스와 Unix I/O 함수들의 혼합을 사용해서 통신한다.

소켓 함수들은 일반적으로 시스템 콜들로 구현되는데, 이 시스템 콜은 커널에서 트랩을 발생시키며, TCP/IP에서 다양한 커널 모드 함수들을 호출한다.

TCP/IP는 프로토콜의 집합으로 각각은 서로 다른 기능을 제공한다.

예르 들면, IP명명법데이터그램이라고 하는 패킷을 한 인터넷 호스트에서 다른 호스트로 보낼 수 있는 배달 메커니즘을 제공한다.

IP 메커니즘은 만일 데이터그램을 잃어버리거나 네트워크 내에서 중보되는 경우에 복구하려고 노력하지 않는다는 점에서 안정적이지 못하다.

IP는 다소 확장해서 데이터그램이 호스트에서 호스트로 가는 대신 UDP(Unreliable Datagram Protocol)프로세스에서 프로세스로 전송될 수 있다.

TCP는 IP 위에 구현한 복잡한 프로토콜로 프로세스들 간에 안전한 완전 양방향 연결을 제공한다.

인터넷은 다음과 같은 특징을 갖는 전 세계적인 호스트의 집합이라고 생각할 수 있다.

  1. 호스트의 집합은 32비트 IP 주소 집합에 매핑된다.
  2. IP주소의 집합은 인터넷 도메인 네임이라고 부르는 식별자의 집합에 매핑된다.
  3. 하나의 인터넷 호스트의 프로세스는 연결을 통해서 다른 인터넷 호스트의 프로세스와 통신할 수 있다.

11.3.1 IP 주소

IP 주소는 비부호형 32비트 정수이며 네트워크 프로그램은 IP 주소를 같은 IP 주소 구조체에 저장한다.

TCP/IP는 네트워크 패킷 헤더에 포함되는 IP 주소 같은 모든 정수형 데이터 아이템에 대해서 통일된 네트워크 바이트 순서(빅 엔디안 바이트 순서)를 정의한다.

IP 주소 구조체의 주소는 호스트 바이트 순서가 리틀 엔디안인 경우에도 항상 네트워크 바이트 순서(빅 엔디안)로 저장된다.

11.3.2 인터넷 도메인 이름

11.3.3 인터넷 연결

인터넷 클라이언트와 서버는 연결(connection)을 통해서 바이트을 주고받는 방식으로 통신한다. 이 연결은 두 개의 프로세스를 연결한다는 점에서 point-to-point 연결이다.

소켓(socket)은 연결의 종단점이며, 각 소켓은 인터넷 주소와 16비트 정수 포트로 이루어진 소켓 주소(address : port)를 가진다.

클라이언트의 소켓 주소 내의 포트는 클라이언트가 연결 요청을 할 때 커널이 자동으로 할당하며, 단기 포트(ephemeral port)라고 한다.

연결은 두 개의 종단점의 소켓 주소에 의해 유일하게 식별된다. 이 두 개의 소켓 주소는 소켓 쌍이라고 하며 tuple로 나타낸다.

(cliaddr:cliport, servaddr:servport)

11.4 소켓 인터페이스

소켓 인터페이스는 네트워크 응용을 만들기 위한 Unix I/O 함수들과 함께 사용되는 함수들의 집합이다.

11.4.1 소켓 주소 구조체

리눅스 커널의 관점에서 보면 소켓은 통신을 위한 끝점이며, Unix 프로그램의 관점에서 보면 소켓은 해당 식별자를 가지는 열린 파일이다.

connect, bind, accept 함수는 프로토콜에 특화된 소켓 주소 구조체를 가리키는 포인터를 필요로 한다.

typedef struct sockaddr SA;

11.4.2 socket 함수

클라이언트와 서버소켓 식별자를 생성하기 위해서 socket 함수를 사용한다.

#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

AF_INET은 32비트 IP 주소를 사용하고 있다는 것을 나타내고, SOCK_STREAM은 소켓이 인터넷 연결의 끝점이 될 것이라는 것을 나타낸다.

11.4.3 connect 함수

클라이언트는 connect 함수를 호출해서 서버와의 연결을 수립한다.

#include <sys/socket.h>

int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen);

11.4.4 bind 함수

bind, listen, accept 소켓 함수는 서버가 클라이언트와 연결을 수립하기 위해 사용한다.

bind 함수는 커널에게 addr에 있는 서버의 소켓 주소를 소켓 식별자 sockfd와 연결하라고 물어본다.

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

11.4.5 listen 함수

클라이언트는 연결 요청을 개시하는 능동적 개체이고, 서버는 클라이언트로부터의 연결 요청을 기다리는 수동적 개체이다.

서버는 listen 함수를 호출해서 이 식별자를 클라이언트 대신에 서버가 사용하게 될 것이라고 알려준다.

#include <sys/socket.h>

int listen(int sockfd, int backlog);

listen 함수는 sockfd를 능동 소켓에서 듣기 소켓으로 변환하며, 듣기 소켓은 클라이언트로부터의 연결 요청을 승락할 수 있다.

11.4.6 accept 함수

서버는 accept 함수를 호출해서 클라이언트로부터의 연결 요청을 기다린다.

#include <sys/socket.h>

int accept(int listenfd, struct sockaddr *addr, int *addrlen);

accept 함수는 클라이언트로부터의 연결 요청이 듣기 식별자 listenfd에 도달하기를 기다리고, 그 후에 addr 내의 클라이언트의 소켓 주소를 채우고, Unix I/O 함수들을 사용해서 클라이언트와 통신하기 위해 사용될 수 있는 연결 식별자를 리턴한다.

듣기 식별자클라이언트 연결 요청에 대해 끝점으로서의 역할을 한다.

연결 식별자클라이언트와 서버 사이에 성립된 연결의 끝점이다.

11.4.7 호스트와 서비스 변환

addrinfo 구조체

struct addrinfo {
    /* Hints argument flags */
    int ai_flags;
    /* First arg to socket function */
    int ai_family;
    /* Second arg to socket function */
    int ai_socktype;
    /* Third arg to socket function */
    int ai_protocol;
    /* Canonical hostname */
    char *ai_canonname;
    /* Size of ai_addr struct */
    size_t ai_addrlen;
    /* Ptr to socket address struction */
    struct sockaddr *ai_addr;
    /* Ptr to next item in linked list */
    struct addrinfo *ai_next;
}

hints는 getaddrinfo가 리턴하는 소켓 주소들의 리스트 전반에 걸쳐 보다 상세한 제어를 제공하는 addrinfo 구조체다.

hints 인자로 전달될 때, ai_family, ai_socktype, ai_protocol, ai_flags 필드만이 설정될 수 있다.

다른 필드들은 0(또는 NULL)으로 설정되어야 하므로 memset을 이용해서 전체 구조체를 0으로 설정한다.

getaddrinfo 함수는 IPv4와 IPv6 소켓 주소를 리턴할 수 있다. ai_family를 AF_INET으로 설정하면 리스트를 IPv4 주소로 제한한다.

host에 연관된 각각의 고유의 주소에 대해, getaddrinfo 함수는 최대 세 개의 addrinfo 구조체를 리턴할 수 있으며, 각각은 서로 다른 ai_socktype 필드를 갖는다.

ai_socktype을 SOCK_STREAM으로 설정하면 리스트가 각 고유의 주소에 대해 자신의 소켓 주소가 연결의 끝점으로 사용될 수 있는 최대 한 개의 addrinfo 구조체로 제한한다.

ai_flags 필드는 기본 동작을 수정하는 비트 마스크다.

AI_ADDRCONFIG: getaddrinfo 함수는 로컬 호스트가 IPv4로 설정된 경우에만 IPv4 주소를 리턴할 것을 요구한다.

AI_CANONNAME: 이 플래그가 설정되면 getaddrinfo 함수가 host의 공식 이름으로의 리스트에서 첫 번째 addrinfo 구조체에서의 ai_canonname 필드를 가리키도록 지시한다.

AI_NUMERICSERV: service 인자는 서비스 이름이거나 포트 번호일 수 있다. 이 플래그가 사용될 경우 service 인자는 포트 번호이어야 한다.

AI_PASSIVE: getaaddrinfo 함수는 클라이언트가 connect 함수를 호출할 때 활성화된 소켓으로 이용할 수 있는 소켓 주소를 리턴한다. 이 플래그를 사용할 경우 getaddrinfo 함수가 서버들이 듣기 소켓으로 이용할 수 있는 소켓 주소를 리턴하게 해준다. 이 경우, host 인자는 NULL이어야 한다. 결과로 얻는 소켓 주소 구조체에서의 주소 필드는 와일드카드 주소가 된다.

getaddrinfo 함수

getaddrinfo 함수는 호스트 이름, 호스트 주소, 서비스 이름, 포트 번호의 스트링 표시를 소켓 주소 구조체를 변환한다.

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *host, const char *service,
				const struct addrinfo *hints,
                struct addrinfo **result);

void freeaddrinfo(struct addrinfo *result);

const char *gai_strerror(int errcode);

host와 service(소켓 주소의 두 개의 구성요소)가 주어지면, getaddrinfo는 각각이 host와 service에 대응되는 소켓 주소 구조체를 가리키는 addrinfo 구조체의 연결 리스트를 가리키는 result를 리턴한다.

  • memset 함수: 어떤 메모리의 시작점부터 연속된 범위를 어떤 값으로 모두 지정하는 함수
void(void *ptr, int value, size_t num);

void *ptr: 채우고자 하는 메모리의 시작 주소
int value: 메모리에 채우고자 하는 값; int 형이지만 내부에서는 unsigned char 1 바이트로 변환되어 저장
size_t num: 채우고자 하는 메모리의 크기

getnameinfo 함수

getnameinfor 함수는 getaddrinfo 함수의 역으로, 소켓 주소 구조체를 대응되는 호스트와 서비스이름 스트링으로 변환한다.

#include <sys/socket.h>
#include <netdb.h>

int getnameinfo(const struct sockaddr *sa, socklen_t salen,
                char *host, size_t hostlen,
                char *service, size_t servlen, int flags);

11.4.8 소켓 인터페이스를 위한 도움함수들

open_clientfd 함수

클라이언트는 open_clientfd 함수를 호출해서 서버와 연결을 설정한다.

/* open_clientfd 함수: 클라이언트가 호출하여 서버와 연결을 설정 */

int open_clientfd(char *hostname, char *port) {
    int clientfd;
    struct addrinfo hints, *listp, *p;

    /* Get a list of potential server addresses */
    memset(&hints, 0, sizeof(struct addrinfo));
    /* Open a connection */
    hints.ai_socktype = SOCK_STREAM;
    /* ... using a numeric port arg. */
    hints.ai_flags = AI_NUMERICSERV;
    /* Recommended for connections */
    hints.ai_flags |= AI_ADDRCONFIG;
    
    getaddrinfo(hostname, port, &hints, &listp);

    /* Walk the list for one that we can successfully connect to */
    for (p = listp; p; p = p->ai_next) {
        /* Create a socket descriptor */
        if ((clientfd = socket(p->ai_family, p-> ai_socktype, p->ai_protocol)) < 0)
            /* Socket failed, try the next */
            continue;
        
        /* Connect to the server */
        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1)
            /* Success */
            break;

        /* Connect failed, try another */
        close(clientfd);
    }

    /* Clean up */
    freeaddrinfo(listp);

    /* All connects failed */
    if (!p)
        return -1;
    /* The last connect succeeded */
    else
        return clientfd;

}

open_clientfd 함수는 호스트 hostname에서 돌아가고, 포트번호 port연결 요청을 듣는 서버와 연결을 설정한다.

Unix I/O 함수를 이용해서 입력과 출력에 대해 준비된 열린 소켓 식별자를 리턴한다.

getaddrinfo를 호출하고, 이것은 addrinfo 구조체의 리스트를 리턴하며, 이들의 각각은 hostname에서 돌아가고, port에서 듣는 서버와 연결을 설정하기에 적합한 소켓 주소 구조체를 가리킨다.

open_listenfd 함수

서버는 open_listenfd 함수를 호출해서 연결 요청을 받을 준비가 된 듣기 식별자를 생성한다.

/* open_listenfd 함수: 서버가 호출하여 연결 요청을 받을 준비가 된 듣기 식별자를 생성 */

#include "csapp.h"

int open_listenfd(char *port) {
    struct addrinfo hints, *listp, *p;
    int listenfd, optval = 1;

    /* Get a list of potential server addresses */
    memset(&hints, 0, sizeof(struct addrinfo));
    /* Accept connections */
    hints.ai_socktype = SOCK_STREAM;
    /* ... on any IP address */
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
    /* ... using port number */
    hints.ai_flags |= AI_NUMERICSERV;
    getaddrinfo(NULL, port, &hints, &listp);

    /* Walk the list for one that we can bind to */
    for (p = listp; p; p = p->ai_next) {
        /* Create a socket descriptor */
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
            /* Socket failed , try the next */
            continue;
        
        /* Eliminates "Address already in use" error from bind */
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
                    (const void *)&optval, sizeof(int));
        
        /* Bind the descriptor to the address */
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
            /* Success */
            break;
        
        /* Bind failed, try the next */
        close(listenfd);
    }

    /* Clean up */
    freeaddrinfo(listp);

    /* No address worked */
    if (!p)
        return -1;
    
    /* Make it a listening socket ready to accept connection requests */
    if (listen(listenfd, LISTENQ) < 0) {
        close(listenfd);
        return -1;
    }
    return listenfd;
};

open_listfd 함수는 포트 port에 연결 요청을 받을 준비가 된 듣기 식별자를 리턴한다.

getaddrinfo 함수를 호출해서 결과 리스트를 socket과 bind로의 호출이 성공할 때까지 탐색한다.

setsockopt 함수를 이용해서 서버가 종료되고 재시작하여 즉시 연결 요청을 받아들이기 시작할 수 있도록 한다.

AI_PASSIVE 플래그와 NULL host 인자로 getaddrinfo를 호출했기 때문에 각 소켓 주소 구조체의 주소 필드는 와일드카드 주소로 설정되며, 이것은 커널에게 이 서버가 호스트에 대한 모든 IP 주소에 대해 요청을 받을 것이라고 말해준다.

11.4.9 소켓 인터페이스 예제: Echo 클라이언트와 서버

Echo 클라이언트의 main 루틴

/* Echo 클라이언트의 main 루틴 */

int main(int argc, char **argv) {
    int clientfd;
    char *host, *port, buf[MAXLINE];
    rio_t rio;

    if (argc != 3) {
        fprintf(stderr, "usage: %s <host> <port> \n", argv[0]);
        exit(0);
    }
    host = argv[1];
    port = argv[2];

    clientfd = open_clientfd(host, port);
    rio_readinitb(&rio, clientfd);

    while (fgets(buf, MAXLINE, stdin) != NULL) {
        rio_writen(clientfd, buf, strlen(buf));
        rio_readlineb(&rio, buf, MAXLINE);
        fputs(buf, stdout);
    }
    close(clientfd);
    exit(0);
}
  • Echo 클라이언트의 메인 루틴
  1. 서버와의 연결을 수립한다.

2-1. 클라이언트는 표준 입력에서 텍스트 줄을 반복해서 읽는 루프에 진입한다.

2-2. 서버에 텍스트 줄을 전송하고, 서버에서 echo 줄을 읽어서 그 결과를 표준 출력으로 인쇄한다.

2-3. 루프는 fgets 함수가 EOF 표준 입력을 만나면 종료한다.

  1. 루프가 종료한 후에 클라이언트는 식별자를 닫는다.

  2. 듣기 식별자를 오픈한 후에 무한 루프에 진입한다.

Echo 서버의 main 루틴

/* Echo 서버의 메인 루틴 */

void echo(int connfd);

int main(int argc, char **argv) {
    int listenfd, connfd;
    socklen_t clientlen;
    /* Enough space for any address */
    struct sockaddr_storage clientaddr;
    char client_hostname[MAXLINE], client_port[MAXLINE];

    if (argc != 2) {
        fprintf(stderr, "usage: %s <port> \n", argv[0]);
        exit(0);
    }

    listenfd = open_listenfd(argv[1]);

    while (1) {
        clientlen = sizeof(struct sockaddr_storage);
        connfd = accept(listenfd, (SA *)&clientaddr, &clientlen);
        getnameinfo((SA *)&clientaddr, clientlen, client_hostname, MAXLINE, client_port, MAXLINE, 0);
        printf("Connected to (%s, %s) \n", client_hostname, client_port);
        echo(connfd);
        close(connfd);
    }
    exit(0);
}
  • Echo 서버의 메인 루틴
  1. 듣기 식별자를 오픈한 후에 무한 루프에 진입한다.

2-1. 각각의 반복 실행은 클라이언트로부터 연결 요청을 기다린다.

2-2. 도메인 이름과 연결된 클라이언트의 포트를 출력한다.

2-3. 클라이언트를 서비스하는 echo 함수를 호출한다.

  1. echo 루틴이 리턴한 후에 메인 루틴은 연결 식별자를 닫는다.

  2. 클라이언트와 서버가 자신들의 식별자를 닫은 후에 연결은 종료된다.

11.5 웹 서버

11.5.1 웹 서버 기초

웹 클라이언트와 서버는 HTTP(Hypertext Transfer Protocol)라고 하는 텍스트 기반 응용수준 프로토콜을 사용해서 상호 연동하다.

웹 클라이언트(브라우저)는 서버로의 인터넷 연결을 오픈하고 컨텐츠를 요청한다.

서버는 요청한 컨텐츠로 응답하고, 연결을 닫아준다.

브라우저는 컨텐츠를 읽고 컨텐츠를 스크린에 보여준다.

FTP 같은 전통적인 파일 전송 서비스를 웹 서비스와 구별하게 해주는 차이점은 웹 컨텐츠는 HTML(Hypertext Markup Language)이라는 언어로 작성될 수 있다는 것이다.

11.5.2 웹 컨텐츠

웹 클라이언트와 서버에게 컨텐츠는 연관된 MIME(Multipurpose Internet Mail Extensions) 타입을 갖는 바이트 배열이다.

웹 서버는 두 가지 다른 방법으로 클라이언트에게 컨텐츠를 제공한다.

  1. 디스크 파일을 가져와서 그 내용을 클라이언트에게 보낸다. 디스크 파일정적 컨텐츠라고 하며, 파일을 클라이언트에게 돌려주는 작업은 정적 컨텐츠를 처리한다고 말한다.

  2. 실행 파일을 돌리고, 그 출력을 클라이언트에게 보낸다. 실행파일이 런타임에 만든 출력동적 컨텐츠라고 하며, 프로그램을 실행하고 그 결과를 클라이언트에게 보내주는 과정을 동적 컨텐츠를 처리한다고 말한다.

웹 서버가 리턴하는 모든 내용들은 서버가 관리하는 파일에 연관되며 이 파일 각각은 URL(Universal Resource Locator)라고 하는 고유의 이름을 가진다.

http://www.google.com:80/index.html과 같은 URL은 포트 80에서 듣고 있는 웹 서버가 관리하는 인터넷 호스트 www.google.com의 /index.html이라는 HTML 파일을 지정한다. 포트 번호는 옵션이며, HTTP 포트 80이 기본 값이다. 실행파일을 위한 URL은 파일 이름 뒤에 프로그램의 인자를 포함할 수 있다. '?' 문자는 파일 이름과 인자를 구분하며, 각 인자는 '&'로 구분된다.

11.5.3 HTTP 트랜잭션

HTTP가 인터넷 연결 위로 전송된 텍스트 라인들에 기초하고 있기 때문에 리눅스 TELNET(Telecommunication Network: 인터넷을 통하여 원격지의 호스트 컴퓨터에 접속할 때 지원되는 인터넷 표준 프로토콜) 프로그램을 사용해서 인터넷 상의 웹 서버와 트랜잭션을 실행할 수 있다.

HTTP 요청

HTTP 요청요청 라인과 따라 나오는 여러 개의 요청 헤더, 헤더 리스트들을 종료하는 빈 텍스트 줄이 따라온다.

  1. 요청 라인

요청 라인은 'method URI version'와 같은 형태를 갖는다.

HTTP는 GET, POST, OPTIONS, HEAD, PUT, DELETE, TRACE 등이 포함된 많은 서로 다른 메소드를 지원한다.

GET 메소드는 서버에게 URI(Uniform Resource Identifier)에 의해 식별되는 내용을 리턴할 것을 지시한다.

URI는 파일 이름과 옵션인 인자들을 포함하는 URL의 접미어다.

  1. 요청 헤더

요청 헤더는 'header-name: header-data'와 같은 형태를 갖는다.

요청 헤더는 서버에 브라우저의 이름이나 브라우저가 이해하는 MIMT 타입 같은 추가적인 정보를 제공한다.

host 헤더는 프록시 캐시에 의해 사용되며, 때로는 브라우저와 요청된 파일을 관리하는 본래의 서버 사이의 중간자 역할을 한다.

  1. 빈 텍스트 줄

빈 텍스트 줄은 헤더를 종료하고, 서버에게 요청한 HTML 파일을 보낼 것을 명령한다.

HTML 응답

HTTP 응답은 응답 라인, 요청 헤더들, 헤더를 종료하는 빈 줄이 따라오고, 응답 본체가 따라온다.

응답 라인은 'version status-code status-message'와 같은 형태를 가진다.

버전 필드는 응답이 준수해야 할 HTTP 버전을 설명한다. 상태 코드는 요청의 특성을 나타낸다. 상태 메시지는 에러 코드를 영어로 나타낸 것이다.

응답 헤더는 응답에 대한 추가적인 정보를 제공한다.

11.5.4 동적 컨텐츠의 처리

서버가 어떻게 동적 컨텐츠를 클라이언트에 제공하는지

어떻게 클라이언트가 프로그램의 인자들을 서버에 전달하는가?

어떻게 서버는 이 인자들을 자신이 만든 자식 프로세스들에게 넘겨주는가?

어떻게 서버는 다른 정보를 컨텐츠를 생성할 필요가 있을 수 있는 자식에게 전달하는가?

CGI(Common Gateway Interface)

CGI 프로그램은 자신의 동적 컨텐츠를 표준 출력으로 보낸다.

0개의 댓글