반응형
(0) 서버와 클라이언트 통신
- 서버와 클라이언트의 통신 (CS:APP 그림 11.12)
- 서버
- socket() : 연결 통로 준비
- bind() : 특정IP와 연결
- listen() : 연결 후 요청을 기다리기
- accept() : read/write 또는 receive/send 메소드를 짝으로 사용하여 요청/응답 처리. 단, listen()에서의 스레드와 다른 스레드를 만들어 진행한다.
- 스레드 분리 이유: 서버는 여러 요청을 받아야 하기에, listen() 스레드는(母) 놔두고 계속 기다리게 하고, accept() 스레드는 (子) 요청을 처리하도록 만들어 둠
- 단, 우리 과제에서는 멀티프로세싱까지 다루지는 않음
- 클라이언트
- socket() : 연결 통로 준비
- connect() : 연결. 끝. 스레드 분리하지 않음
- 서버
- HTTP / TCP / IP 통신 등
- IP 위에 → TCP 위에 → HTTP 가 있음. 모두 P. 즉 protocol. 규약=약속
- Physical 레이어에 가까울 수록 밑에 있다고 표현
- IP단으로 갈 수록 하드웨어 환경(와이파이, LTE, 랜선, …) 레이어를 나눠서 설계하는 이유는, 각 단계 별 환경이 변할 수 있고, 다양한 이종의 장치들이 나올 수 있기 때문
- 한번에 설계하면 다 바꿔야 하니까, 레이어를 나눠서 약속을 정해둔 것
- TCP는 패킷 받는 게 보장됨 vs UDP는 패킷 받는 게 보장 안됨. 그냥 쏘는 것
- 요새 동영상 스트리밍 같은 곳에 UDP 많이 씀. 중간에 화질 저하돼도 보는 데 문제 없으니까
- 참고로 11장은 TCP 소켓으로 만들어지게 되어있음. 2kb를 주면 2kb를 받는 구조.
- HTTP가 처음 나왔을 때 뜬 이유: 사람이 읽을 수 있어서
- IP 위에 → TCP 위에 → HTTP 가 있음. 모두 P. 즉 protocol. 규약=약속
00. Tiny 웹서버 구현
1. tiny.c
(1) main() : port 번호를 인자로 받아 클라이언트의 요청이 올 때마다 새로 연결 소켓을 만들어 doit 함수를 호출한다.
/* port번호를 인자로 받는다. */
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);
}
/* argv[1] : port 번호 */
/* 해당 포트 번호에 해당하는 듣기 소켓 식별자를 열어준다. */
listenfd = Open_listenfd(argv[1]);
/* 클라이언트에게서 받은 연결 요청을 accept 한다. */
while (1) {
clientlen = sizeof(clientaddr);
/* connfd : 서버와 연결할 식별자입니다. */
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); // line:netp:tiny:accept
/* Getnameinfo를 호출하면서 hostname과 port가 반환 됨 */
Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
printf("Accepted connection from (%s, %s)\n", hostname, port);
doit(connfd);
Close(connfd); /* 서버 연결 식별자를 닫아준다. */
}
}
(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_t rio;
/* Read reqeust line and headers */
/* 클라이언트가 rio로 보낸 request라인과 헤더를 읽고 분석한다. */
Rio_readinitb(&rio, fd); /* connfd를 연결하여 rio에 저장 */
Rio_readlineb(&rio, buf, MAXLINE); /* rio에 있는 string 한 줄을 모두 buffer에 옮긴다. */
printf("Request headers:\n");
printf("%s", buf); /* print : GET /godzilla.gif HTTP/1.1\0 */
sscanf(buf, "%s %s %s", method, uri, version); /* buf를 Parse. */
/* 만약 method가 GET방식이나 HEAD 방식이 아니라면 clienterror로 연결합니다. */
if (!(strcasecmp(method, "GET") == 0 || strcasecmp(method, "HEAD") == 0)) {
clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
return;
}
/* read_requesthdrs 함수는 헤더까지 반복문을 돌면서 헤더를 출력하는 함수 */
read_requesthdrs(&rio);
/* Parse URI from GET request */
/* if static = 1 */
is_static = parse_uri(uri, filename, cgiargs);
/* filename 유효성 검사 */
if(stat(filename, &sbuf) < 0){
clienterror(fd, filename, "404", "NOT found", "Tiny couldn't find this file");
return;
}
if(is_static){ /* Serve static content */
/* 일반파일인가요? 혹은 읽기 권한이 있나요? 안돼 돌아가 */
if(!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)){
clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file");
return;
}
/* 유효성 검사를 통과한 static이라면 serve_static 실행 */
serve_static(fd, filename, sbuf.st_size, method);
}
else{ /* Serve dynamic content */
if(!(S_ISREG(sbuf.st_mode) || !(S_IXUSR & sbuf.st_mode))){
clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program");
return;
}
/* 유효성 검사를 통과한 dynamic이라면 serve_dynamic 실행 */
serve_dynamic(fd, filename, cgiargs, method);
}
}
(3) clienterror() : 에러메세지와 응답 본체를 서버 소켓을 통해 클라이언트에 보낸다.
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg){
char buf[MAXLINE], body[MAXBUF];
/* Build the HTTP response body */
sprintf(body, "<html><title>Tiny Error</title>");
sprintf(body, "%s<body bgcolor=""fffff"">\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);
/* Print the HTTP response */
sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
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));
/* 에러메세지와 응답 본체를 서버 소켓을 통해 클라이언트에 보낸다. */
Rio_writen(fd, buf, strlen(buf));
Rio_writen(fd, body, strlen(body));
}
(4) parse_uri() : uri를 받아 요청받은 파일의 이름과 요청 인자를 채워준다.
/* uri를 받아 요청받은 filename, cgiargs를 반환한다. */
int parse_uri(char *uri, char * filename, char *cgiargs){
char *ptr;
/* 과제 요건사항 : cgi-bin은 동적파일로 분류하자 */
/* 만약 static content 요구라면, 1을 리턴한다. */
if(!strstr(uri, "cgi-bin")){
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
printf("%s\n",uri);
if (uri[strlen(uri)-1] == '/')
strcat(filename, "home.html");
return 1;
}
else {
ptr = index(uri, '?');
if (ptr){
strcpy(cgiargs, ptr+1);
*ptr = '\0';
}
else{
strcpy(cgiargs, "");
}
strcpy(filename, ".");
strcat(filename, uri);
return 0;
}
}
(5) serve_static() : 클라이언트가 원하는 정적 컨텐츠 디렉토리를 받아온다. 응답 라인과 헤더를 작성하고 서버에게 보낸다. 그 후 정적 컨텐츠 파일을 읽어 그 응답 본체를 클라이언트에 보낸다.
/* 정적 컨텐츠의 디렉토리를 받아 request 헤더 작성 후 서버에게 보낸다. */
void serve_static(int fd, char *filename, int filesize, char *method)
{
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXBUF];
/* Send response headers to cilent */
/* 응답 라인, 헤더 작성 */
get_filetype(filename, filetype); /* find filetype */
sprintf(buf, "HTTP/1.0 200 OK\r\n"); /* write response */
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(fd, buf, strlen(buf));
printf("%s", buf);
if (strcasecmp(method, "HEAD") == 0)
return;
/* Send response body to client */
srcfd = Open(filename, O_RDONLY, 0);
//mmap 함수는 요청한 파일을 가상메모리 영역으로 매핑한다.
//mmap 함수를 호출하면 파일 srcfd의 첫번째 filesize 바이트 주소 srcp에서 시작하는 사적 읽기 허용 가상메모리 영역으로 매
srcp = (char*)Malloc(filesize);
Rio_readn(srcfd, srcp, filesize);
Close(srcfd);
Rio_writen(fd, srcp, filesize); //주소 srcp에서 시작하는 filesizw 바이트를 클라이언트의 연결 식별자로 복사한다,
free(srcp);
}
(6) get_filetype() : filename을 조사해 각각의 식별자에 맞는 MIME 타입을 filetype에 입력해준다.
/* filename을 조사해 각각의 식별자에 맞는 MIME타입을 filetype에 입력해준다. */
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");
else if(strstr(filename,".mp4"))
strcpy(filetype, "video/mp4");
else
strcpy(filetype, "text/plain");
}
(7) serve_dynamic() : 동적 디렉토리를 받을 때, 실행하는 함수이다. 응답라인과 헤더를 작성하고 서버에게 보낸다. CGI 자식프로세스를 fork하고 프로세스 표준 출력을 클라이언트 출력과 연결한다.
/* dynamic content 실행 후 응답라인, 헤더 작성 */
/* CGI 자식 프로세스를 fork 후 프로세스 표준 출력을 클라이언트 출력과 연결 함. */
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method)
{
char buf[MAXLINE], *emptylist[] = {NULL};
/* Return fist part of HTTP respense */
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 */
/* Real server would set all CGI vars here */
setenv("QUERY_STRING", cgiargs, 1);
setenv("REQUEST_METHOD", method, 1);
Dup2(fd, STDOUT_FILENO);
/* filename을 실행시켜줘 Exeve 리턴을 안해 자식이 나 죽어.... 시그널보내 시그널 보내*/
Execve(filename, emptylist, environ);
}
Wait(NULL); /* 신호를 보내면 부모의 Wait이 끝납니다. */
}
/*
* fork에 대해 알아보자
* fork()를 실행하면 부모프로세스와 자식 프로세스가 동시에 실행
* fork()의 반환값이 0 : 자식프로세스라면 if문을 수행
* fork()의 반환값이 0이 아니라면 : 내가 부모프로세스라면 if문을 수행하지 않고 Wait함수로 이동
* Wait() : 부모프로세스가 먼저 도달도 자식 프로세스가 종료될 때까지 기다리는 함수
* if문 안에서 setnv 시스템콜을 수행해 "Query_String"의 값을 cgiargs로 바꿔준다.(우선순위 0순위)
* dup2() : CGI 프로세스 출력을 fd로 복사한다.
* dup2() : 실행 후 STDOUT_FILENO의 값은 fd이다.
* dup2() : CGI 프로세스에서 표준 출력을 하면 바로 출력되지 않고 서버 연결 식별자를 거쳐 클라이언트 함수에 출력
* execuv() : 파일이름이 첫번째 인자인 것과 같은 파일을 실행한다.
*/
(8) read_requesthdrs() : header 출력
void read_requesthdrs(rio_t * rp)
{
char buf[MAXLINE];
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
while(strcmp(buf, "\r\n")){
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
}
return;
}
01. static 결과
(1) 클라이언트 요청
GET / HTTP/1.1
(2) 서버출력
반응형
'Computer Systems' 카테고리의 다른 글
파이썬에서의 배열 (0) | 2023.04.02 |
---|---|
🖥[CSAPP] 11장. 네트워크 프로그래밍 HTTP (0) | 2022.11.07 |
🖥[CSAPP] 3장. 프로그램의 기계수준 표현 (0) | 2022.11.06 |
🖥[CSAPP] 11장. 네트워크 프로그래밍(0) (0) | 2022.11.06 |
🖥[CSAPP] 11장(5). 네트워크 프로그래밍_ 웹서버 (0) | 2022.11.04 |
댓글