. [C언어, 웹서버] Echo 클라이언트 (작성 중)
본문 바로가기
프로그래밍 공부/CSPP

[C언어, 웹서버] Echo 클라이언트 (작성 중)

by 불냥이_ 2021. 2. 1.

Echo 클라이언트는 서버와의 연결을 수립한 이후에 클라이언트는 표준 입력에서 텍스트 줄을 반복해서 읽는 루프에 진입하고, 서버에 텍스트 줄을 전송, 서버에서 echo 줄을 읽어서 그 결과를 표준 출력으로 인쇄한다.

 

Echo의 메인 함수는 아래와 같다.

#include "csapp.h"

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);
}

9~17 줄 : argument를 제대로 받아왔다면, 사용자의 입력은 프로그램이름_host명_port 가 될 것이다. 이를 host와 port 변수에 저장한다. 그리고 이 host와 port로 clientfd를 생성한다.

 

18 줄 :  clientfd로 rio_t 구조체를 초기화한다.Rio_readinitb의 함수의 상세는 아래와 같다. (Robust I/O package :: 돼지 (tistory.com) 참조)

/* rio_t 구조체 초기화 */
void rio_readinitb(rio_t *rp, int fd) 
{
    rp->rio_fd = fd;  
    rp->rio_cnt = 0;  /* unread size 라고는 하는데 사용하는 걸 보면 read size 라고 봐야 된다.... */
    rp->rio_bufptr = rp->rio_buf; /* 내부 버퍼 포인터 */
}

 

20~24 줄 : 클라이언트가 서버로 텍스트 줄을 전송하면, 서버에서 echo 줄을 읽어서 그 결과를 표준 출력으로 인쇄한다. 루프는 fgets가 EOF(End of File) 표준 입력을 만나면 종료한다. (그러니깐 입력이 더 들어오지 않으면)

 

22 줄 : Rio_writen을 실행한다. (rio_writen은 buf에서 식별자 fd로 n바이트를 전송한다.) Rio_writen 의 상세는 다음과 같다.

ssize_t rio_writen(int fd, void *usrbuf, size_t n) 
{
    size_t nleft = n;
    ssize_t nwritten;
    char *bufp = usrbuf;
 
    while (nleft > 0) {
        if ((nwritten = write(fd, bufp, nleft)) <= 0) {
            if (errno == EINTR)  /* interrupted by sig handler return */
                nwritten = 0;    /* and call write() again */
            else
                return -1;       /* errorno set by write() */
        }
        nleft -= nwritten;
        bufp += nwritten;
    }
    return n;
}

23 줄 : Rio_readlineb를 실행한다. (텍스트 줄을 파일 rio에서 읽고, 이것을 메모리 위치 buf로 복사하고, 텍스트라인을 널(0) 문자로 종료시킨다. ( read/write 에서 fd를 인자로 받아 사용했다면, rio_t라는 별도의 구조체가 존재한다. 이 구조체에는 fd정보는 물론 내부적인 임시 버퍼와 관련된 정보들도 포함되어 있다.)

24 줄 : buf가 가지고 있는 문자열을 stdout에 쓴다. (표준 출력으로 표시한다.)

 

26줄 : 클라이언트의 프로세스가 종료할 때, 커널이 fd를 모두 닫아주기 때문에 따로 close할 필요는 없으나, 임의로 만든 것은 자기 손으로 닫는 것이 올바른 프로그래밍 습관이다. (라고 책이 말한다.)

 

※ Rio 구조체 ( [개발 일지] rio_t 구조체 :: 돼지 (tistory.com) 참조 )

Rio 구조체는 파일 디스크립터 뿐 아니라 내부 임시 버퍼와 관련된 정보가 포함되어 있다. 상세는 아레와 같다.

/* $begin rio_t */
#define RIO_BUFSIZE 8192

typedef struct {
    int rio_fd;                /* Descriptor for this internal buf */
    int rio_cnt;               /* Unread bytes in internal buf */
    char *rio_bufptr;          /* Next unread byte in internal buf */
    char rio_buf[RIO_BUFSIZE]; /* Internal buffer */
} rio_t;

/* $end rio_t */

int rio_fd : 파일 디스크립터 정보 (client_fd나 server_fd가 여기에 들어간다.)

int rio_cnt : 아직 읽지않은 버퍼의 바이트 수 ( 0이 되면 버퍼르 다 읽어서 비었다고 판단한다.)

rio_bufptr : 읽을 위치를 표시하는 포인터

rio_buf[RIO_BUFSIZE] : 내부 버퍼 (문자열을 보관하는 array)

 

/* 지금의 추측으로는 이렇다.

1. 서버와 통신이 시작된 뒤, 우리가 입력을 넣으면 (네트워크용) 버퍼에 저장된다.(fgets())

2. 버퍼에 저장된 데이터를 fd (소켓은 일종의 통로라고 생각하면)로 상대방 버퍼에 넘긴다.

 (그러면 서버는 이 데이터를 받고 무엇가 작업을 한 뒤, 결과 텍스트를 넘겨줄 것이다. 그리고 fd를 통해서 서버로부터 온 데이터들이 클라이언트 쪽 버퍼에 저장된다. )

3. 버퍼에 저장되어있는, 서버로부터 온 데이터를 읽는다.

4. 데이터를 출력한다.

 

RIO는 네트워크를 위해서 쓰는 I/O 형식이라고 하는데 웹서버를 이해하기 위해선 공부해야될 것 같다. */

 

 

그 다음은 반복적 서버 메인 루틴이다. 

#include "csapp.h"

void echo(int connfd);

int main(int argc, char **argv)
{
    int listenfd, connfd;
    socklen_t clientlen;
    struct sockaddr_storage clientaddr; /* Enough space for any address */
    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);
}

 서버는 listenfd를 연 뒤에 connect 요청이 올 때까지 무한 반복 루프를 시행한다. 

댓글