[TIL/크래프톤 정글] DAY 59

배재준·2025년 5월 7일

크래프톤 정글 - TIL

목록 보기
52/93
post-thumbnail

2025.05.07

TIL(TODAY I LEARN)


  • 오늘한 내용 : C - 네트워크 프로그래밍 - tiny web server 숙제문제 구현

  • WEEK08: BSD소켓, IP, TCP, HTTP, file descriptor, DNS


숙제 문제

11.6

  1. 요청라인 요청 헤더 echo

    void read_requesthdrs(rio_t *rp)
    {
      char buf[MAXLINE];
    
      Rio_readlineb(rp, buf, MAXLINE);
      while(strcmp(buf, "\r\n")){
        printf("Header: %s", buf);  // 버리기 전에 echo!
        Rio_readlineb(rp, buf, MAXLINE);
      }
      return;
    }
  2. 정적 컨텐츠 요청

    ./tiny 8080 > tiny.log // 좌측 명령어로 로그를 파일로
  3. HTTP의 버전 결정

    Request headers:
    GET /godzilla.gif **HTTP/1.1**
  4. 헤더가 갖는 의미를 결정

    • 요청 헤더 분석
    헤더 이름의미
    Host: localhost:8080요청 대상의 호스트 이름과 포트 번호를 지정합니다. HTTP/1.1부터 필수 헤더입니다.
    Connection: keep-alive커넥션을 닫지 않고 계속 유지하려는 요청입니다. 여러 요청을 한 커넥션으로 처리하기 위함입니다.
    Cache-Control: max-age=0캐시된 응답을 허용하지 않고 항상 최신 정보를 원한다는 의미입니다.
    sec-ch-ua브라우저의 사용자 에이전트 브랜드 정보를 나타냅니다. 보안상의 이유로 User-Agent를 보완하려는 목적입니다.
    sec-ch-ua-mobile: ?0모바일 브라우저인지 아닌지를 나타냅니다 (?0은 데스크탑).
    sec-ch-ua-platform: "Windows"브라우저가 실행 중인 플랫폼(OS)을 나타냅니다.
    Upgrade-Insecure-Requests: 1가능한 경우 HTTP → HTTPS로 업그레이드 요청을 의미합니다.
    User-Agent브라우저 이름, 버전, 플랫폼 정보 등 클라이언트 식별 정보입니다.
    Accept클라이언트가 받을 수 있는 MIME 타입들입니다. 서버가 응답 형식을 결정할 때 참고합니다.
    Sec-Fetch-Site요청 출처가 같은 사이트인지, 다른 도메인인지 나타냅니다 (none: 직접 입력).
    Sec-Fetch-Mode요청 방식: navigate, cors, no-cors 등. 보안 요청 구분용입니다.
    Sec-Fetch-User: ?1사용자 상호작용에 의해 발생한 요청인지 여부 (?1은 yes).
    Sec-Fetch-Dest: document이 요청의 목적지가 어떤 리소스인지 (document: HTML 페이지).
    Accept-Encoding클라이언트가 지원하는 압축 방식입니다 (gzip, br 등).
    Accept-Language선호하는 언어 설정입니다 (ko-KR, en-US 등). 콘텐츠 언어 최적화용입니다.

11.7 MPG 파일 처리

//serve_static 내부 get_filetpye에 추가
else if (strstr(filename, ".mpg"))
		strcpy(filetype, "video/mpeg");

11.8 SIGCHILD

 //main 함수 내부
 // 11.8 자식 죽었을 때 핸들러 등록 
 // 핸들러는 항상 시그널이 발생하기 전에 미리 등록
 Signal(SIGCHLD, sigchild_handler); 
void sigchild_handler(int sig)
{
  /*
  waitpid(-1, ...): 모든 자식 프로세스를 대상으로
  WNOHANG: 종료된 자식만 회수, 없으면 즉시 리턴 (비블로킹)
  > 0: 성공적으로 자식 하나를 회수하면 루프 계속
  */
  while (waitpid(-1, NULL, WNOHANG) > 0)
    ;
}

//Wait(NULL) 지워주기
main()
├── Signal(SIGCHLD, sigchild_handler); ← 자식 죽으면 알림 받도록 등록
├── Open_listenfd(...)                ← 클라이언트 대기 준비
└── while(1)
     └── Accept()doit()serve_dynamic()
                     ├── Fork() → 자식이 execve() 후 exit
                     └── 부모는 대기하지 않고 다음 요청 처리

sigchild_handler()
└── waitpid(..., WNOHANG) → 종료된 자식들을 비동기로 회수

11.9 malloc으로 변환

srcfd = Open(filename, O_RDONLY, 0);
    // 11.9
    // srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
    srcp = (char *)malloc(sizeof(filesize));
    Rio_readn(srcfd,srcp,filesize);
    Close(srcfd);
    Rio_writen(fd, srcp, filesize);
    // Munmap(srcp, filesize);
    free(srcp);
}
방식원리메모리복사 여부
mmap파일을 가상 메모리에 직접 매핑OS가 페이지 단위로 관리복사 없이 바로 사용 (제로카피처럼)
malloc + rio_readn사용자 메모리에 직접 복사힙에 메모리 할당파일 내용을 명시적으로 복사
  • 파일을 읽는 방식이 다른 것 뿐, 클라이언트에 보내는 건 같다.

11.10 adder.html

<html>
  <head><title>Adder Form</title></head>
  <body>
    <h2>CGI Adder Test</h2>
    <form action="/cgi-bin/adder" method="GET">
      <p>x = <input type="text" name="x"></p>
      <p>y = <input type="text" name="y"></p>
      <input type="submit" value="Add">
    </form>
  </body>
</html>

11.11 HEAD method 지원

항목설명
헤더HTTP 응답의 메타데이터 (타입, 길이 등)
본문실제 리소스 내용 (HTML, 이미지, 동영상 등)
HEAD 요청 시서버가 헤더는 보내고, 본문은 생략
GET 요청 시서버가 헤더 + 본문 모두 전송
  1. doit()에서 method 검사 확장

    int is_head = 0;
    
    if (strcasecmp(method, "GET") && strcasecmp(method, "HEAD")) {
        clienterror(fd, method, "501", "Not implemented",
                    "Tiny does not implement this method");
        return;
    }
    
    if (!strcasecmp(method, "HEAD"))
        is_head = 1;
    
  2. serve_static()에서 본문 전송 조건 분기 설정

    void serve_static(int fd, char *filename, int filesize, int is_head)
    {
        int srcfd;
        char *srcp;
        char filetype[MAXLINE];
        char buf[MAXBUF];
    
        get_filetype(filename, filetype);
        sprintf(buf, "HTTP/1.0 200 OK\r\n");
        sprintf(buf, "%sServer: Tiny Web Server\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(fd, buf, strlen(buf));
    
        if (is_head) return;  // 본문 생략!
    
        srcfd = Open(filename, O_RDONLY, 0);
        srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
        Close(srcfd);
        Rio_writen(fd, srcp, filesize);
        Munmap(srcp, filesize);
    }
  3. serve_static()에 int is_head 인자 추가

    serve_static(fd, filename, sbuf.st_size, is_head);

11.12 POST method지원

  1. doit()에서 POST method 인식
is_post = !strcasecmp(method, "POST");
  1. read_requesthdrs()에서 Content-Length 추출
void read_requesthdrs(rio_t *rp, int *content_length)
{
  char buf[MAXLINE];
  Rio_readlineb(rp, buf, MAXLINE);
  while (strcmp(buf, "\r\n")) {
    if (strncasecmp(buf, "Content-Length:", 15) == 0)
      *content_length = atoi(buf + 15);
    printf("Header: %s", buf);
    Rio_readlineb(rp, buf, MAXLINE);
  }
}
  • Content-Length: 헤더에서 본문 길이를 읽는다.
  1. POST 본문 읽어서 cgiargs에 저장
//doit() 에 추가
if (is_post) {
  Rio_readnb(&rio, cgiargs, content_length);
  cgiargs[content_length] = '\0';  // null-terminate
}

11.13 SIGPIPE & EPIPE 처리

클라이언트가 연결을 끊었는데,

Tiny 서버가 그걸 모르고 계속 write()를 시도하면 생기는 오류

이때 발생하는 두 가지 현상:

현상설명
write() 호출 → -1 리턴, errno = EPIPE프로세스는 살아있지만 에러 상태
SIGPIPE 시그널 발생기본 동작은 프로세스 비정상 종료
//main()에 추가
Signal(SIGPIPE, SIG_IGN);  // 11.13: Broken pipe 무시

0개의 댓글