컴퓨터 시스템 tiny 서버

2025. 5. 7. 15:09개발

Tiny Web Server 분석 (CS:APP 기반)

이 글에서는 CS:APP 책 11장 (네트워크 프로그래밍) 내용을 기반으로 Tiny Web Server의 전체 동작 흐름을 분석


1. 전체 동작 흐름 요약

  1. main() 함수에서 서버 소켓을 열고(Open_listenfd()), 클라이언트의 연결을 기다림.
  2. 클라이언트가 접속하면 Accept()를 호출해 새 연결을 받고, doit() 함수에 전달.
  3. doit() 함수는 HTTP 요청을 파싱하고, 정적(static)인지 동적(dynamic)인지 판별.
  4. 정적 콘텐츠면 serve_static(), 동적 콘텐츠면 serve_dynamic() 호출.
  5. 응답을 보낸 후 연결을 Close()로 종료.

 2. 클라이언트 연결 흐름 (main)

int listenfd = Open_listenfd(argv[1]);
while (1) {
    clientlen = sizeof(clientaddr);
    connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
    Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
    doit(connfd);
    Close(connfd);
}

📍 clientaddrstruct sockaddr_storage 구조체입니다.
이는 IPv4와 IPv6를 모두 포괄할 수 있는 범용 주소 구조체로, OS가 자동으로 알맞게 채워줍니다.

struct sockaddr_storage {
    sa_family_t ss_family; // 주소 체계 (AF_INET, AF_INET6)
    char __ss_padding[_SS_PADSIZE];
};

이 주소를 문자열로 바꾸기 위해 Getnameinfo()를 사용합니다. 클라이언트의 IP, 포트를 hostname, port로 저장합니다.


3. doit() 함수 – 요청 파싱 및 라우팅

Rio_readinitb(&rio, fd);
Rio_readlineb(&rio, buf, MAXLINE);
sscanf(buf, "%s %s %s", method, uri, version);

1. 요청 메서드(GET), URI, 버전 파싱
2. parse_uri()를 통해 정적/동적 여부 판단
3. stat() 함수로 파일 존재 여부 확인
4. 파일의 읽기/실행 권한 확인 후 응답 처리


4. parse_uri() – 정적 vs 동적 콘텐츠 구분

if (!strstr(uri, "cgi-bin")) {
    strcpy(cgiargs, "");
    strcpy(filename, ".");
    strcat(filename, uri);
    if (uri[strlen(uri) - 1] == '/')
        strcat(filename, "home.html");
    return 1;
} else {
    ptr = strchr(uri, '?');
    ...
    return 0;
}

- URI에 cgi-bin이 포함되어 있으면 동적 콘텐츠로 간주하고 CGI 실행
- 포함되어 있지 않으면 정적 콘텐츠 (파일 그대로 반환)


5. serve_static() – 정적 파일 응답

파일 타입을 판별해 Content-type 헤더를 설정하고, 파일을 mmap()으로 메모리에 매핑 후 전송합니다.

srcfd = Open(filename, O_RDONLY, 0);
srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
Rio_writen(fd, srcp, filesize);
Munmap(srcp, filesize);

정적 콘텐츠: 이미지, html, 텍스트 등을 그대로 읽어서 전송
📌 파일 확장자에 따라 get_filetype()으로 MIME 결정


 6. serve_dynamic() – CGI 프로그램 실행

CGI 실행을 위해 환경변수를 설정하고, Dup2()로 표준 출력을 클라이언트 소켓으로 리다이렉션한 후 Execve()로 실행합니다.

if (Fork() == 0) {
    setenv("QUERY_STRING", cgiargs, 1);
    Dup2(fd, STDOUT_FILENO);
    Execve(filename, emptylist, environ);
}
Wait(NULL);

이때 실행되는 CGI는 adder 같은 프로그램이며, 실행 결과가 그대로 클라이언트에게 출력됩니다.


 7. clienterror() – 오류 응답 처리

존재하지 않는 파일, 권한 문제, 미지원 메서드 등의 오류에 대해 HTML 에러 메시지를 생성하고 전송합니다.

HTTP/1.0 404 Not Found
Content-type: text/html
Content-length: ...

에러도 하나의 완전한 HTTP 응답이기 때문에, 상태 라인, 헤더, 바디 모두 포함됩니다.


 8. CS:APP 책과의 연결 – 11장 내용 정리

  • 11.4.1: 소켓 생성과 바인딩socket(), bind(), listen()Open_listenfd() 안에서 처리
  • 11.4.3: 클라이언트 연결 처리Accept()로 연결 수락, sockaddr_storage 사용
  • 11.5.1: HTTP 요청 파싱 – 요청 라인과 헤더 분석
  • 11.5.3: 정적/동적 콘텐츠 응답 – 파일 처리 vs CGI 실행

정리

  • Tiny Web Server는 HTTP 1.0 기반의 간단한 웹 서버로, 정적 파일과 CGI 실행을 모두 지원합니다.
  • sockaddr_storage는 IP 버전에 상관없이 클라이언트 주소를 담을 수 있어 범용적입니다.
  • 소켓 흐름, MIME 처리, CGI 실행 등 실제 웹 서버 작동 원리를 배우기에 훌륭한 예제입니다.

이 Tiny 서버를 기반으로 멀티스레드/캐시/프록시 등으로 확장할 수 있습니다.

 

tiny 웹서버 코드구현

https://github.com/applepc24/tiny_web-proxy/blob/main/tiny/tiny.c