C로 웹 및 프록시 서버 만들기

hooooon·2022년 11월 9일
0

본문은 Computer Systems 10장, 11장 12장 내용을 기반으로 작성하였습니다.

책 이름 : Computer Systems: A Programmer's Perspective, Global Edition (3rd Edition)
저자 : Randal E. Bryant / David R, O'Hallaron

빨간색으로 된 키워드에 관한 글 위주로 글을 읽어 주시면 감사하겠습니다.

tiny.c 웹 서버 구동순서

전체 코드 및 과제 설명은 https://github.com/hoon25/webproxy-jungle를 참고해 주세요.

  1. 듣기소켓 1개 생성
  2. 연결소켓 (Request 당)1개 생성
  3. 요청정보 및 헤더를 parse
    3-1. 정적/동적으로 구별해서 처리
  4. 연결소켓에 Response 헤더와 바디를 작성

듣기 소켓은 서버에서 1개이고 Request당 연결소켓을 만들어 낸다.

listenfd = Open_listenfd(argv[1]); // 듣기 소켓을 오픈
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); // 연결소켓을 오픈

  1. listenfd(듣기 소켓)는 main쓰레드 동작시 1개만 생성
  2. connfd(연결 소켓)는 while문 안에서 클라이언트의 connect를 기다리고 있으면 생명주기가 (Request 1회)
    2-1. 또한 connfd의 생성함수 인자로 listenfd가 필요

1,2번 로직이 합쳐진 Main 함수 전문

int main(int argc, char **argv) {  // argc 메인함수 전달 정보 개수, Argv[0] 파일 경로 , [1],[2]... user input
  int listenfd, connfd;
  char hostname[MAXLINE], port[MAXLINE];
  socklen_t clientlen;
  struct sockaddr_storage clientaddr;

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

  listenfd = Open_listenfd(argv[1]); // 듣기 소켓을 오픈
  while (1) { // 무한 서버 루프 실행
    clientlen = sizeof(clientaddr);
    connfd = Accept(listenfd, (SA *)&clientaddr,
                    &clientlen);  // line:netp:tiny:accept  // 반복적으로 연결 요청을 접수
    Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE,
                0); 
    printf("Accepted connection from (%s, %s)\n", hostname, port);
    doit(connfd);   // line:netp:tiny:doit // 트랜잭션 수행
    Close(connfd);  // line:netp:tiny:close
  }
}

요청 정보 및 헤더를 Parse

사용자의 모든 요청은 문자열로 들어온다.
위의 문자열을 기준으로 파싱하여 정적/동적을 구별하고 클라이언트가 원하는 적절한 작업을 수행하고 반환해주어야한다.
ex) 클라이언트의 요청전문

GET /godzilla HTTP/1.1 
Host: *.*.*.*:****
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n
등등

연결 소켓에 쓰면 클라이언트가 읽는다.

소켓은 각 통신의 끝점입니다.
클라이언트는 클라이언트 소켓을 가지고 있고, 서버는 연결 소켓을 가지고 있습니다.

실제로 통신은 소켓에 읽고 쓰는 과정이라는것을 알고있어야 합니다.
1. buf에 응답헤더 문자열을 작성 후
2. clientfd에 buf(응답헤더 문자열)을 입력합니다.

결과적으로 클라이언트는 connfd 소켓의 내용을 읽어오는 방식으로 통신이 이루어짐을 알 수 있습니다.

관련 코드 예시

  sprintf(buf, "HTTP/1.1 200 OK\r\n");
  sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
  sprintf(buf, "%sConnection: close\r\n", buf);
  sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
  sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
  Rio_writen(connfd, buf, strlen(buf)); 

참고) 네트워크 소켓에는 표준I/O가 아닌 Unix I/O를 사용해야 합니다.
Rio_writen은 Unix I/O기반으로 만들어진 함수입니다.

왜 네트워크 통신에서 표준 I/O가 아닌 Unix I/O를 사용해야 하나요?
관련 답변은 준비중입니다.

위의 코드를 완료한다면 아래와 같은 원시적인 html 페이지를 확인 할 수 있습니다.

profile
더 나은 내일을 위해

0개의 댓글