목차
개요
Tiny라고 부르는 작지만 동작하는 웹 서버입니다. 이는 프로세스 제어, Unix I/O, 소켓 인터페이스, HTTP 와 같은 많은 개념들을 결합했습니다.
fd = file descriptor 파일 식별자
함수
- 기초 선언
- main
- doit
- clienterror
- read_requesthdrs
- parse_uri
- get_filetype
- serve_static
- serve_dynamic
- adder.c
0. 기초 선언
/* $begin tinymain */
/*
* tiny.c - A simple, iterative HTTP/1.0 Web server that uses the
* GET method to serve static and dynamic content.
*
*/
#include "csapp.h"
void doit(int fd);
void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *filename, char *cgiargs);
void serve_static(int fd, char *filename, int filesize);
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *filename, char *cgiargs);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
/* MAXLINE, MAXBUF == 8192 */
1. main
/* 설명 1.1 */
// 입력 ./tiny 8000 / argc = 2, argv[0] = tiny, argv[1] = 8000
int main(int argc, char **argv)
{
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);
}
/* Open_listenfd 함수를 호출해서 듣기 소켓을 오픈한다. 인자로 포트번호를 넘겨준다. */
// Open_listenfd는 요청받을 준비가된 듣기 식별자를 리턴한다 = listenfd
// 설명 1.2
listenfd = Open_listenfd(argv[1]);
/* 전형적인 무한 서버 루프를 실행*/
while (1)
{
//accpet 함수 인자에 넣기위한 주소길이를 계산
clientlen = sizeof(clientaddr);
/* 반복적으로 연결 요청을 접수 */
// accept 함수는 1. emetl tlrqufwk, 2. 소켓주소구조체의 주소, 3. 주소(소켓구조체)의 길이를 인자로 받는다.
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); // line:netp:tiny:accept
// Getaddrinfo는 호스트 이름: 호스트 주소, 서비스 이름: 포트 번호의 스트링 표시를 소켓 주소 구조체로 변환
// Getnameinfo는 위를 반대로 소켓 주소 구조체에서 스트링 표시로 변환.
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
}
}
설명 1.1:
main 함수에 인자가?
main함수에서 첫 번째 매개변수 argc는 옵션의 개수이며 argv는 옵션 문자열의 배열
int main(int argc, char * argv[] (== char **argv) )
It is fundamental to c that char** x and char* x[] are two ways of expressing the same thing. Both declare that the parameter receives a pointer to an array of pointers. Apart from the use of sizeof
출처:
https://dojang.io/mod/page/view.php?id=696
설명 1.2
소켓 및 소켓 인터페이스 및 소켓 주소 구조체
소켓이란?
소켓(Socket)이란 네트워크상에서 동작하는 프로그램 간 통신의 종착점(Endpoint)입니다.
각 소켓은 인터넷 주소와 16비트 정수 포트*로 이루어진 소켓 주소를 가지며 이것은 address : port 로 나타낸다.
*위의 포트는 소프트웨어 포트로 이는 네트워크 스위치와 라우터에서의 하드웨어 포트와는 관련이 없다.
open_listenfd는 소켓 인터페이스 ( 네트워크 응용을 만들기 위한 Unix I/O 함수들과 함께 사용되는 함수들의 집합 figure 11.12 참조) 의 getaddrinfo, socket, bind, listen의 역할들을 한번에 수행한다.
인터넷 소켓 주소는 sockaddr_in 타입의 16바이트 구조체에 저장된다.
connect, bind, and accept 함수는 프로토콜에 특화된 소켓 주소 구조체를 가리키는 포인터를 필요로 한다.
어떤 종류의 소켓 주소 구조체라도 받아들일 수 있도록 소켓 함수들이 sockaddr 구조체로의 포인터를 기대하도록하고, 응용이 프로토콜에 특화된 구조체로의 모든 포인터를 이 포괄적인 구조체로 캐스팅(형변환?)하도록 정의한다.
CSAPP 11.4
리눅스/유닉스 시스템에서는 소켓(socket)의 통신 대상을 지정하기 위해 '주소(address)'를 사용한다.
이 '주소' 라는 것을 저장하거나 표현하는데 사용하는 구조체가 바로, 본 포스팅에서 설명하고자 하는 'struct sockaddr' 이다.
bind( ), connect( ) 와 같은 함수들이 2번째 매개변수로써 바로 이 'struct sockaddr *addr' 을 받는다.
이 struct sockaddr은 기본 형태이고, 주소체계(Address family)값에 따라서 구조체를 형변환 해서 사용하면 편리하다.
즉, 일반적으로 개발을 하다보면 struct sockaddr_in, struct sockaddr_un, struct sockaddr_in6 등의 구조체와 상호 형변환을 해서 사용하게 된다.
출처: https://techlog.gurucat.net/292 [하얀쿠아의 이것저것 만들기 Blog]
기본 소켓 주소 구조체 sockaddr는 sa_data에 Ip와 port 주소등이 함께 들어있기에 이를 구분하기 위해 socketaddr_in으로 캐스팅(형변환)을 해 사용하곤 한다.
sockaddr 구조체는 소켓의 주소를 담는 기본 구조체 역할을 한다.
속된말로, '소켓 주소의 와꾸를 잡는 녀석' 쯤으로 이해하면 쉽다.
sa_family : 주소체계를 구분하기 위한 변수이며, 2 bytes 이다. 참고로, u_short는 unsigned short를 말한다.
sa_data : 실제 주소를 저장하기 위한 변수다. 14 bytes 이다.
즉, 이 구조체는 16 bytes 의 와꾸(틀, 크기)를 잡아주는 녀석이다.
주소체계는 AF_INET, AF_INET6, AF_UNIX, AF_LOCAL, AF_LINK, AF_PACKET 등이 있고 sockaddr_in 은 AF_INET 만을 지원한다.
추가적인 소켓 주소 구조체에 대해 궁금하다면 ↓
출처: https://techlog.gurucat.net/292 [하얀쿠아의 이것저것 만들기 Blog]
2. doit
void doit(int fd)
{
int is_static;
struct stat sbuf;
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
// rio 설명 2.1
// rio_readlineb를 위해 rio_t 타입(구조체)의 읽기 버퍼를 선언
rio_t rio;
/* Read request line and headers */
/* Rio = Robust I/O */
// rio_t 구조체를 초기화 해준다.
Rio_readinitb(&rio, fd);
/* Rio_readlineb(그림 10.8)를 통해 요청 라인을 읽어들이고, 분석한다. 70 +4 line */
Rio_readlineb(&rio, buf, MAXLINE);
printf("Request headers:\n");
printf("%s", buf);
//설명 2.2
sscanf(buf, "%s %s %s", method, uri, version);
/* Tiny 는 GET method 만 지원하기에 클라이언트가 다른 메소드 (Post 같은)를
*요청하면 에러메세지를 보내고, main routin으로 돌아온다
* 79 +4 line
*/
//strcmp() 문자열 비교 함수,
//strcasecmp() 대소문자를 무시하는 문자열 비교 함수
//strncasecmp() 대소문자를 무시하고, 지정한 길이만큼 문자열을 비교하는 함수
if (strcasecmp(method, "GET"))
{
clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
return;
}
/* GET method라면 읽어들이고, 다른 요청 헤더들을 무시한다. */
read_requesthdrs(&rio);
/* Parse URI form GET request */
/* URI 를 파일 이름과 비어 있을 수도 있는 CGI 인자 스트링으로 분석하고, 요청이 정적 또는 동적 컨텐츠를 위한 것인지 나타내는 플래그를 설정한다. */
is_static = parse_uri(uri, filename, cgiargs);
/* 만일 파일이 디스크상에 있지 않으면, 에러메세지를 즉시 클라아언트에게 보내고 메인 루틴으로 리턴*/
if (stat(filename, &sbuf) < 0)
{
clienterror(fd, filename, "404", "Not found", "Tiny couldn't find this file");
return;
}
/* Serve static content */
if (is_static)
{
/* 동적 컨텐츠이고 (위if), 이 파일이 보통 파일인지, 읽기 권한을 가지고 있는지 검증한다. */
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))
{
clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read this file");
return;
}
/* 그렇다면 정적 컨텐츠를 클라이언트한테 제공. */
serve_static(fd, filename, sbuf.st_size);
}
else /* Serve dynamic content */
{
/* 실행 가능한 파일인지 검증*/
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))
{
clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read this file");
return;
}
/* 그렇다면 동적 컨텐츠를 클라이언트에게 제공 */
serve_dynamic(fd, filename, cgiargs);
}
}
설명 2.1
Unix I/O 그리고 RIO (Robust input output) 란
우선 unix I/O 의 개념들
리눅스에서 파일은 연속된 m개의 바이트다.
네트워크, 디스크, 터미널 같은 모든 I/O 디바이스들은 파일로 모델링 된다.
모든 입력과 출력은 해달 파일을 읽거나 쓰는 형식으로 수행된다.
- 읽기 연산은 현재 파일 위치 k에서 시작해서 n > 0 바이트를 파일에서 메모리로 복사하고 k를 n만큼 증가시킨다.
k > m(파일의 최대 길이) 의 읽기 연산을 수행하면 end-of-file EOF라고 알려진 조건이 발생하며 이것은 응용프로그램에서 감지 할 수 있다. 하지만 실제 파일 끝에서 "EOF 문자" 가 명시적으로 존재하는 것은 아니다.
- 쓰기 연산은 현재 파일 위치 k에서 시작해서 n >0 바이트를 메모리에서 파일로 복사하고 k를 갱신시킨다.
리눅스의 파일 타입 혹은 종류
파일의 타입은 여러개로 나뉜다.
- Regular file : 일반 파일로, 2진수, text 등 데이터를 담고 있다. OS에서 이 파일을 보면, 정확한 포맷은 알지 못한 채, 그저 "일련의 바이트"라고 생각한다고 한다.
- Directory file : 문서 파일이다. 파일의 이름과 주소를 담고 있다.
- Character special and block special file : 말그대로 문자/블록 특수 파일이다. 사용자와 프로그램이 하드웨어(ex 키보드)등으로 소통하게 해주는 파일이다. 간단히 설명하자면, 키보드로 입력된 입력값과, 모니터에 나타낼 블록 단위의 값을 담고 있다고 보면 된다.
- Pipe : 로컬 프로세스 간 커뮤니케이션 용도로 사용된다.
- Socket : 네트워크 커뮤니케이션 용도로 사용된다.
- link : 바로가기라고 생각하면 편하다. 원본 파일을 가리키는 포인터의 역할을 한다.
출처: https://suhwanc.tistory.com/131
간단한 리눅스 파일 종류 설명 ↓
https://linuxconfig.org/identifying-file-types-in-linux
Rio , Robust I/O 라고 부르는 I/O 패키지.
Rio 패키지는 짧은 카운트(short count)를 자동으로 처리한다 (Unix I/O 와 차이) .
짧은 카운트가 발생할 수 있는 네트워크 프로그램 같은 응용에서 편리하고 안정적이고 효율적인 I/O 패키지이다
짧은 카운트란?
일부의 경우에 unix io의 read, write 함수는 응용이 요청하는 것보다 더 적은 바이트를 전송한다. 이를 짧은 카운트라고 명시한다. 짧은 카운트는 에러를 나타내는 것은 아니다!
보통 short counts는 다음과 같은 상황일 때 발생한다.
- EOF (end-of-file)을 읽는 도중 만난 경우
- text lines 을 터미널로부터 읽어올 때 (예측이 힘듦)
- 네트워크 소켓 통신 시
- 20바이트만 가지고 있는 파일의 현재 파일 위치에서 읽으려 할떄, 이 파일에서부터 50바이트를 한번에 읽으려고 할경우 read 의 경우는 short count 20을 리턴할 것이다. 추후 read는 계속해서 short count 0을 리턴하며 EOF 임을 알릴 것이다.
- 만일 열린 파일이 터미널과 연결되어 있으면(키보드 혹은 디스플레이) 각 read 함수는 한번에 한개의 텍스트 줄을 전송할 것이며, 텍스트 중의 길이와 동일한 short count를 리턴할 것이다.
- 네트워크의 경우 내부버퍼링 제약과 긴 네트워크 지연시간 때문에 read 와 write 모두 short count를 리턴할 수 있다.
반면 절대 발생하지 않는 경우도 있는데 아래와 같은 상황일 때이다.
- 디스크 파일을 읽고 쓸 때 (크기가 정해져 있는 상황)
RIO의 2가지 형태
1) 2진 데이터로 이루어진 Unbuffered input / output (버퍼 없는 입력 및 출력 함수)
-> 아래에선 rio_readn, rio_writen 함수로 이를 처리할 것이다.
소켓 인터페이스 기반 네트워크 응용프로그램의 개요를 보면 알 수 있지만.
rio_writen 은 서버 -> 클라이언트, 클라이언트 -> 서버로 데이터를 전송 할 때 사용한다.
2) 2진 데이터와 텍스트로 이루어진 Buffered input (버퍼를 사용하는 입력 함수)
-> 아래에선 rio_readlineb, rio_readnb 함수로 설명할 것이다.
* 특히 2번 buffered는 그 전 (unix, standard)에서 하지 못하는 스레드 관리를 해주니 자세히 보도록 하자.
Rio_readlineb 함수는 텍스트 줄을 파일 rp(종료 새 줄 문자를 포함해서)에서 부터 읽고, 읽은 것들을 메모리 위치 usrbuf로 복사하고, 읽은 텍스트 라인을 널(0) 문자로 바꾸고 종료시킨다. rio_readlineb 함수는 최대 maxlen-1개의 바이트를 읽는다. maxlen-1를 넘는 텍스트 라인들은 잘내서 널 문자로 종료시킨다.
그리고 '\n' 개행 문자를 만날 경우 maxlen 전에 break 된다.!
이는 위에서 rio_writen를 통해 서버면 클라이언트, 클라이언트면 서버에서 보내진 정보를 읽을 때 사용한다.
추가적인 RIO 함수 설명 ↓
https://suhwanc.tistory.com/131
struct rio_t와 rio_readinitb 의 코드
설명 2.2
sscanf 는 뭔가요
#include <stdio.h> // C++ 에서는 <cstdio>
int sscanf(const char* str, const char* format, ...);
문자열에서 형식화 된 데이터를 읽어온다.
str 에서 데이터를 형식 문자열(format)에서 지정하는 바에 따라 읽어와 그 데이터를 뒤에 부수적인 인자들이 가리키는 메모리 공간에 저장하게 된다.
이 때, 데이터가 저장되는 방식 역시 형식 문자열에 의해 결정된다.
C 문자열로 sscanf 함수가 데이터를 얻어올 문자열이다 C 문자열로 sscanf 함수가 데이터를 얻어올 문자열이다.
https://haruhiism.tistory.com/30
format 인자의 구체적인 설명부터는 아래 링크 참조 ↓
https://modoocode.com/67
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
//생략...
Rio_readlineb(&rio, buf, MAXLINE);
sscanf(buf, "%s %s %s", method, uri, version);
//생략..
//
printf("Request headers:\n");
printf("%s", buf);
//버퍼 출력
Request headers:
GET /mp4sample.mp4 HTTP/1.1
method == 'GET'
uri == '/mp4sample.mp4'
version == 'HTTP/1.1'
Rio_readlineb를 통해 요청 라인을 읽고 buf에 담았고,
이 buf를 sscanf를 통해 공백을 기준으로 method, uri, version으로 분리해 각각의 인자에 담는다.
strcasecmp는 또 뭐죠
//strcmp( str1, str2 ) 문자열 비교 함수,
//strcasecmp() 대소문자를 무시하는 문자열 비교 함수
//strncasecmp() 대소문자를 무시하고, 지정한 길이만큼 문자열을 비교하는 함수
출처:
3. clienterror
/* 명백한 오류에 대해서 클라이언트에게 보고하는 함수.
* HTTP응답을 응답 라인에 적절한 상태 코드와 상태 메시지와 함께 클라이언트에게 보낸다.
*
*/
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
char buf[MAXLINE], body[MAXBUF];
/* BUILD the HTTP response body */
/* 브라우저 사용자에게 에러를 설명하는 응답 본체에 HTML도 함께 보낸다 */
/* HTML 응답은 본체에서 컨텐츠의 크기와 타입을 나타내야하기에, HTMl 컨텐츠를 한 개의 스트링으로 만든다. */
/* 이는 sprintf를 통해 body는 인자에 스택되어 하나의 긴 스트리잉 저장된다. */
sprintf(body, "<html><title>Tiny Error</title>");
sprintf(body, "%s<body bgcolor="
"ffffff"
">\r\n",
body);
sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
sprintf(body, "%s<hr><em>The Tiny Web Server</em>\r\n", body);
// body 배열에 차곡차곡 html 을 쌓아 넣어주고
/* Print the HTTP response */
sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
/* Rio_writen 그림 10.3 */
//굳이 보내고 쌓고 보내고 쌓고가 아니라 위에 body처럼 쭉 쌓아서 한번에 보내도 되지않을까?
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-type: text/html\r\n");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
// buf에 넣고 보내고 넣고 보내고
// sprintf로 쌓아놓은 길쭉한 배열을 (body, buf)를 보내준다.
Rio_writen(fd, buf, strlen(buf));
Rio_writen(fd, body, strlen(body));
}
sprintf는?
#include <stdio.h> // C++ 에서는 <cstdio>
int sprintf(char* str, const char* format, ...);
간단히 설명하자면
str라는 문자열 배열에 format(형식 문자열)에서 지정한 방식 대로 ... 에 들어갈 인자들을 넣어준다.
좀더 간단히는 ↓참고
https://dojang.io/mod/page/view.php?id=352
4. read_requesthdrs
/* Tiny는 요청 헤더 내의 어떤 정보도 사용하지 않는다
* 단순히 이들을 읽고 무시한다.
*/
void read_requesthdrs(rio_t *rp)
{
char buf[MAXLINE];
Rio_readlineb(rp, buf, MAXLINE);
/* strcmp 두 문자열을 비교하는 함수 */
/* 헤더의 마지막 줄은 비어있기에 \r\n 만 buf에 담겨있다면 while문을 탈출한다. */
while (strcmp(buf, "\r\n"))
{
//rio 설명에 나와있다 싶이 rio_readlineb는 \n를 만날때 멈춘다.
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
// 멈춘 지점 까지 출력하고 다시 while
}
return;
}
요청 헤더에 대한 자세한 설명.
https://goddaehee.tistory.com/169
5. parse_uri
/* Parse URI form GET request */
/* URI 를 파일 이름과 비어 있을 수도 있는 CGI 인자 스트링으로 분석하고, 요청이 정적 또는 동적 컨텐츠를 위한 것인지 나타내는 플래그를 설정한다. */
// URI 예시 static: /mp4sample.mp4 , / , /adder.html
// dynamic: /cgi-bin/adder?first=1213&second=1232
int parse_uri(char *uri, char *filename, char *cgiargs)
{
char *ptr;
/* strstr 으로 cgi-bin이 들어있는지 확인하고 양수값을 리턴하면 dynamic content를 요구하는 것이기에 조건문을 탈출 */
if (!strstr(uri, "cgi-bin")) /* Static content */
{
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
//결과 cgiargs = "" 공백 문자열, filename = "./~~ or ./home.html
// uri 문자열 끝이 / 일 경우 허전하지 말라고 home.html을 filename에 붙혀준다.
if (uri[strlen(uri) - 1] == '/')
strcat(filename, "home.html");
return 1;
}
// uri 예시: dynamic: /cgi-bin/adder?first=1213&second=1232
else /* Dynamic content */
{
//설명 5.1
//index 함수는 문자열에서 특정 문자의 위치를 반환한다.
ptr = index(uri, '?');
//?가 존재한다면
if (ptr)
{
// 인자로 주어진 값들을 cgiargs 변수에 넣는다.
strcpy(cgiargs, ptr + 1);
/* ptr 초기화 */
*ptr = '\0';
}
else
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
return 0;
}
}
strstr, strcpy, strcat 은 또 뭐야.
1. strstr()
문자열 안에서 문자열로 검색하기!
strstr(대상문자열, 검색할문자열)
- char *strstr(char * const _String, char const * const _SubString);
- 문자열을 찾았으면 문자열로 시작하는 문자열의 포인터를 반환, 문자열이 없으면 NULL을 반환
https://dojang.io/mod/page/view.php?id=371
2. strcpy()
문자열 복사하기!
- strcpy(대상문자열, 원본문자열);
- char *strcpy(char *_Dest, char const *_Source);
- 대상문자열의 포인터를 반환
https://dojang.io/mod/page/view.php?id=358
3. strcat()
문자열은 strcat 함수를 사용하여 서로 붙일 수 있으며 함수 이름은 문자열을 연결시키다(string concatenate)에서 따왔습니다(string.h 헤더 파일에 선언되어 있습니다).
- strcat(최종문자열, 붙일문자열);
- char *strcat(char *_Destination, char const *_Source);
- 최종 문자열의 포인터를 반환
설명 5.1
HTTP 요청
http 요청은 요청 라인 (시작줄), 요청 헤더 (read_requesthdrs), 헤더 리스트들을 종료하는 빈텍스트 줄이 따라온다.
method URI version
ex) GET / HTTP/1.1 , GET /mp4sample.mp4 HTTP/1.1
클라이언트는 프로그램 인자들을 GET 요청일 경우 URI를 통해 서버에 전달된다.
'?' 문자는 파일 이름과 인자를 구분하며, 각 인자는 '&' 문자로 구분한다.
빈칸은 인자들 사이에 허용되지 않으며, "%20" 스트링으로 표시해야한다.
6. get_filetype
/*
* get_filetype - Derive file type from filename
* strstr 두번쨰 인자가 첫번째 인자에 들어있는지 확인
*/
void get_filetype(char *filename, char *filetype)
{
if (strstr(filename, ".html"))
strcpy(filetype, "text/html");
else if (strstr(filename, ".gif"))
strcpy(filetype, "image/gif");
else if (strstr(filename, ".png"))
strcpy(filetype, "image/png");
else if (strstr(filename, ".jpg"))
strcpy(filetype, "image/jpeg");
/* 11.7 숙제 문제 - Tiny 가 MPG 비디오 파일을 처리하도록 하기. */
else if (strstr(filename, ".mp4"))
strcpy(filetype, "video/mp4");
else
strcpy(filetype, "text/plain");
}
HTML 서버가 처리 할 수 있는 파일 타입을 이 함수를 통해 제공한다. 11.7의 경우 비디오 파일 유형중 mp4인 파일을 요청할 경우 반환 할 수 있게끔 추가했다.
7. serve_static
void serve_static(int fd, char *filename, int filesize)
{
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXBUF], *fbuf;
/* Send response headers to client */
get_filetype(filename, filetype);
sprintf(buf, "HTTP/1.0 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);
/* writen = client 쪽에 */
Rio_writen(fd, buf, strlen(buf));
/* 서버 쪽에 출력 */
printf("Response headers:\n");
printf("%s", buf);
/* Send response body to client */
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);
/* 숙제문제 11.9 */
fbuf = malloc(filesize);
Rio_readn(srcfd, fbuf, filesize);
Close(srcfd);
Rio_writen(fd, fbuf, filesize);
free(fbuf);
}
unix i/o 에서 Open 함수는 열려고하는 파일의 식별자 번호를 리턴하는데 이를 srcfd 에 담는다.
1. 주석 처리된 첫 번쨰 경우 Mmap 함수를 이용해 바로 메모리를 할당하며 srcfd의 파일 값을 배정한다.
2. malloc의 경우 filesize 만큼의 가상 메모리를 할당한 후 , Rio_readn 으로 할당된 가상 메모리 공간의 시작점인 fbuf를 기준으로 srcfd 파일을 읽어 복사해넣는다.
3. 양 쪽 모두 생성한 파일 식별자 번호인 srcfd 를 Close() 해주고
4. Rio_writen 함수 (시스템 콜) 을 통해 클라이언트에게 전송한다.
5. 마지막으로 Mmap은 Munmap, malloc은 free로 할당된 가상 메모리를 해제해준다.
8. serve_dynamic
void serve_dynamic(int fd, char *filename, char *cgiargs)
{
char buf[MAXLINE], *emptylist[] = {NULL};
/* Return first part of HTTP response */
sprintf(buf, "HTTP/1.0 200 OK\r\n");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Server: Tiny Web Server\r\n");
Rio_writen(fd, buf, strlen(buf));
if (Fork() == 0) /* Child process 생성 - 부모 프로세스(지금) 을 복사한*/
{
/* Real server would set all CGI vars here */
setenv("QUERY_STRING", cgiargs, 1); // 환경변수 설정
Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client, 자식 프로세스의 표준 출력을 연결 파일 식별자로 재지정 */
Execve(filename, emptylist, environ); /* Run CGI program */
}
Wait(NULL); /* Parent waits for and reaps child 부모 프로세스가 자식 프로세스가 종료될떄까지 대기하는 함수*/
}
9. adder.c
/*
* adder.c - a minimal CGI program that adds two numbers together
*/
/* $begin adder */
#include "csapp.h"
int main(void)
{
char *buf, *p;
char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
int n1 = 0, n2 = 0;
/* Extract the two arguments */
if ((buf = getenv("QUERY_STRING")) != NULL)
{
p = strchr(buf, '&');
*p = '\0';
strcpy(arg1, buf);
strcpy(arg2, p + 1);
n1 = atoi(strchr(arg1, '=') + 1);
n2 = atoi(strchr(arg2, '=') + 1);
}
/* Make the response body */
// content 인자에 html body 를 담는다.
sprintf(content, "QUERY_STRING=%s", buf);
sprintf(content, "Welcome to add.com: ");
sprintf(content, "%sTHE Internet addition portal.\r\n<p>", content);
sprintf(content, "%sThe answer is: %d + %d = %d\r\n<p>",
content, n1, n2, n1 + n2);
sprintf(content, "%sThanks for visiting!\r\n", content);
/* Generate the HTTP response */
printf("Connection: close\r\n");
printf("Content-length: %d\r\n", (int)strlen(content));
printf("Content-type: text/html\r\n\r\n");
// 여기까지 가 헤더
// 여기부터 바디 출력
printf("%s", content);
fflush(stdout);
exit(0);
}
/* $end adder */
GET /cgi-bin/adder?first=123&second=234 HTTP/1.1
라는 식의 헤더 시작줄이 들어오기 떄문에 adder.c 에서 n1, n2 값을 추출하기위해 atoi(strchr()) 을 사용해"=" 부터 읽는다.
'sw사관학교 정글 2기 > 컴퓨터 시스템' 카테고리의 다른 글
임시저장 (0) | 2021.09.28 |
---|---|
유용한 링크 (0) | 2021.09.27 |
네트워크 - Protocol, TCP/IP, OSI 계층, TCP, UDP, HTTP 등등 (0) | 2021.09.16 |
malloc - 동적 메모리 할당기 - 소스 코드 해석 (0) | 2021.09.11 |
c - 비트 연산자, 매크로 (0) | 2021.09.10 |
댓글