[WEEK07] 네트워크 - Tiny.c, Proxy.c (코드&주석)

novxerim·2021년 12월 25일
1

SW-Jungle

목록 보기
35/59

[11.11 과제 테스트 사진(telnet(클라이언트)에서 GET요청하기)]


Tiny.c

(컴퓨터시스템 교재 11.11 숙제까지 끝마친 상태)

편하게 보려면 여기

/* $begin tinymain */
/*
 * tiny.c - A simple, iterative HTTP/1.0 Web server that uses the
 *     GET method to serve static and dynamic content.
 *
 * Updated 11/2019 droh
 *   - Fixed sprintf() aliasing issue in serve_static(), and clienterror().
 */

// 1. 입력 : ./tiny 8000
// 2. aws : 15.164.94.35:8000 접속

#include "csapp.h"

void doit(int fd);
void echo(int connfd);
void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *filename, char *cgiargs);
void serve_static(int fd, char *filename, int filesize, char *method);
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);

int main(int argc, char **argv) { // argc : 옵션의 개수, argv : 옵션 문자열의 배열(내가 입력하는 스트링 : ./tiny 8000) 
  // 인자 갯수 모를때.. (int argc, char **argv)세트로 씀 // argv가 스트링의 배열이고 *argv일때 0번째 스트링을 가리킴, 스트링이 char *
  int listenfd, connfd;
  char hostname[MAXLINE], port[MAXLINE]; // 크기를 모르니까 맥스로 받음
  socklen_t clientlen;
  struct sockaddr_storage clientaddr;

  /* Check command line args */
  if (argc != 2) { // (1)/.tiny port(2) 2개 
    fprintf(stderr, "usage: %s <port>\n", argv[0]); // 내가 입력한 str address // argv[0] : ./tiny
    exit(1); // ./tiny만 입력하면 argc = 1개라서 에러 => 에러메시지 : ./tiny <port>
  }

  /* Open_listenfd 함수를 호출해서 듣기 소켓을 오픈한다. 인자로 포트번호를 넘겨준다. */
  // Open_listenfd는 요청받을 준비가된 듣기 식별자를 리턴한다 = listenfd // fd : 012 기본 들어있고, 3부터 들어옴
  listenfd = Open_listenfd(argv[1]); /* 듣기 소켓 오픈 */ // argv[1] : 8000 포트를 넣음 arg의 1번 엘레멘트가 포트인 것
  while (1) { /* 무한 서버 루프 실행 */
    clientlen = sizeof(clientaddr); // accpet 함수 인자에 넣기위한 주소길이를 계산 (addr받은건 없지만 일단 사이즈 계산..)

    /* 반복적으로 연결 요청을 접수 */
    // accept 함수는 1. 듣기 식별자, 2. 소켓주소구조체의 주소, 3. 주소(소켓구조체)의 길이를 인자로 받는다.
    connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);  // line:netp:tiny:accept /* 반복적으로 연결 요청 접수*/
            // Accept하면서 동시에 clientaddr 등을 받아옴
    // Getaddrinfo는 호스트(서버) 이름: 호스트 주소, 서비스 이름: 포트 번호 의 스트링 표시를 소켓 주소 구조체로 변환 (모든 프로토콜에 대해)
    // Getnameinfo는 위를 반대로 소켓 주소 구조체에서 스트링 표시로 변환.
    Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
                    // clentaddr, clentlen을 받아서 hostname~ 으로 변환
    printf("Accepted connection from (%s, %s)\n", hostname, port); // hostname,   port
    // IP:port 치고 브라우저 들어가면 커맨드창에 Accepted connection from (143.248.229.77, 58064) 라고 뜸.
    /* 숙제 11.6-A*/
    // echo(connfd);
    // Close(connfd);
    // connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
    // Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
    // doit(connfd);   // line:netp:tiny:doit /* 트랜잭션 수행 */
    // Close(connfd);
    // connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
    // Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
    doit(connfd);  // line:netp:tiny:close 
    Close(connfd); /* 자신 쪽의 연결 끝(소켓)을 닫음 */
  }
}

/* 숙제 11.6-A */
void echo(int connfd) {
  size_t n;
  char buf[MAXLINE];
  rio_t rio; // 저자가 만든 패키지. 시스템 수준의 입출력 리눅스에서 쓰는 unix I/O와 비슷.
  // 리오 패키지를 쓰는 이유 : Rio 패키지는 짧은 카운트(short count)를 자동으로 처리한다 (Unix I/O 와 차이) .
  // short count가 발생할 수 있는 네트워크 프로그램 같은 응용에서 편리하고 안정적이고 효율적인 I/O 패키지이다
  /* 보통 short counts는 다음과 같은 상황일 때 발생한다.
    EOF (end-of-file)을 읽는 도중 만난 경우
    text lines 을 터미널로부터 읽어올 때 (예측이 힘듦)
    네트워크 소켓 통신 시 */
  Rio_readinitb(&rio, connfd); // connfd와 &rio를 연결한다
  while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) { // &rio를 buf에 복사하고 그 사이즈 = n
    printf("server received %d bytes\n", (int)n);
    Rio_writen(connfd, buf, n); // buf를 connfd에 쓴다 (클라에서 줌)
    // readline을 하면 rio안의 내용이 사라져서 buf에 넣어놓고 connfd출력하는데에 쓰고 다시 원래대로 돌려줌(writen)
  }
}

void doit(int fd) { /* 한 개의 HTTP 트랜잭션을 처리 */
  int is_static; 
  struct stat sbuf;
  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
  char filename[MAXLINE], cgiargs[MAXLINE];
  rio_t rio; // rio_readlineb를 위해 rio_t 타입(구조체)의 읽기 버퍼를 선언

  /* Read request line and headers */  
  /* Rio = Robust I/O */
  // rio_t 구조체를 초기화 해준다.
  Rio_readinitb(&rio, fd); // 요청 라인 읽어들임 // rio버퍼와 fd. 서버의 connfd를 연결시킨다
  Rio_readlineb(&rio, buf, MAXLINE); // 요청 라인 읽고 분석 // rio에 있는 string한 줄을 버퍼로 다 옮긴다
  printf("Request headers:\n");
  printf("%s", buf); // 우리가 요청한 자료를 표준 출력 해준다 (godzilla)
  sscanf(buf, "%s %s %s", method, uri, version);
  printf("Get image file uri : %s\n", uri); // 추가코드
                      // GET이거나 HEAD도 아닐 때 /* 숙제 11.11 */
  if (strcasecmp(method, "GET") && strcasecmp(method, "HEAD")) { // 같으면 0반환이라 if문 안들어감 // Tiny는 GET메소드만 지원. 만약 다른 메소드(like POST)를 요청하면. strcasecmp : 문자열비교.
    clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method"); // 에러메시지 보내고, main루틴으로 돌아온다
    return; // 그 후 연결 닫고 다음 요청 기다림. 그렇지 않으면 읽어들이고
  }

  read_requesthdrs(&rio); // GET method라면(0) 그건 읽고, 다른 요청 헤더들은 무시 (그냥 프린트한다)

  /* Parse URI from GET request */
  is_static = parse_uri(uri, filename, cgiargs); // URI를 파일 이름과, 비어있을 수도 있는 CGI인자 스트링 분석하고 // 정적이면 1 // 파일네임 여기서 prase_uri를 통해 만들어냄 (매개변수처럼..담는 그릇)
  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)) { // 이 파일이 보통 파일이라는 것과 읽기 권한을 갖고 있는지를 검증한다.
      // sbuf.st_mode : sbuf의 내용 중 st_mode의 값(어떤 타입의 파일인지)
      clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file");
      return;
    }
    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;
    }
    serve_dynamic(fd, filename, cgiargs, method); // 만일 그렇다면 진행해서 동적 컨텐츠를 클라이언트에게 제공한다
  }  
}

/* HTTP 응답을 응답 라인에 적절한 상태 코드와 상태 메시지와 함께 클라이언트에 보내며, 
 * 브라우저 사용자에게 에러를 설명하는 응답 본체에 HTML 파일도 함께 보낸다. */
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
  char buf[MAXLINE], body[MAXBUF];

  /* Build the HTTP response boy */ 
  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);

  /* 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));
}

/* 요청 헤더를 읽고 무시한다 */
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_readlineb는 \n을 만날 때 멈춘다
    Rio_readlineb(rp, buf, MAXLINE);
    printf("%s", buf);
    // 멈춘 지점까지 출력하고 다시 while
  }
  return;
}

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, ""); // CGI 인자 스트링을 지우고
    strcpy(filename, "."); // URI를 ./index.html같은 상대 리눅스 경로 이름으로 바꾼다
    strcat(filename, uri);
    //결과 cgiargs = "" 공백 문자열, filename = "./~~ or ./home.html

    if (uri[strlen(uri)-1] == '/') // 만일 URI가 '/'로 끝난다면
      strcat(filename, "home.html"); // 기본 파일 이름(home.html)을 filename에 추가한다
    return 1;
  }

  // uri 예시: dynamic: /cgi-bin/adder?first=1213&second=1232 
  else { /* Dynamic content */ // 반면, 만일 이 요청이 동적 컨텐츠를 위한 것이라면
    ptr = index(uri, '?'); // 모든 CGI 인자들을 추출하고 //index 함수는 문자열에서 특정 문자의 위치를 반환한다.
    
    // ? 가 존재한다면
    if (ptr) {
        // 인자로 주어진 값들을 cgiargs 변수에 넣는다
        strcpy(cgiargs, ptr+1);
        *ptr = '\0'; // ptr 초기화
    }
    else
      strcpy(cgiargs, ""); // 없으면 안넣는다
    
    strcpy(filename, "."); // 나머지 URI 부분을 상대 리눅스 파일 이름으로 변환한다
    strcat(filename, uri); // 이어붙이는 함수. 파일네임에 uri를 붙인다
    return 0;
  }
}


/* 지역 파일의 내용을 포함하고 있는 본체를 갖는 HTTP 응답을 보낸다 */
void serve_static(int fd, char *filename, int filesize, char *method) {
  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)); // 요청한 파일의 내용을 연결 식별자 fd로 복사해서 응답 본체를 보낸다 // 버퍼를 옮김
  /* 서버 쪽에 출력 */
  printf("Response headers:\n"); 
  printf("%s", buf);

  if (!strcasecmp(method, "HEAD")) // 같으면 0(false). 다를 때 if문 안으로 들어감
    return; // void 타입이라 바로 리턴해도 됨(끝내라)

  /* Send response body to client */
  srcfd = Open(filename, O_RDONLY, 0); // 열려고 하는 파일의 식별자 번호 리턴. filename을 오픈하고 식별자를 얻어온다
                                // ㄴ 0 : 읽기 전용이기도하고, 새로 파일을 만드는게 아니니 Null처럼 없다는 의미..없어도 됨
  // srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); // mmap : 요청한 파일을 가상메모리 영역으로 매핑한다
  // // mmap 호출시 위에서 받아온 모든 요청 정보들(srcfd)을 전부 매핑해서 srcp로 받는다(포인터)
  // Close(srcfd); // srcfd 내용을 메모리로 매핑한 후에 더 이상 이 식별자 필요X, 파일을 닫는다. 안 닫으면 메모리 누수 치명적
  // Rio_writen(fd, srcp, filesize); // 실제로 파일을 클라이언트에게 전송. // srcp내용을 fd에 filesize만큼 복사해서 넣는다
  // Munmap(srcp, filesize); // 매핑된 srcp 주소를 반환한다. 치명적인 메모리 누수를 피한다 
  // // mmap-munmap은 malloc-free처럼 세트
  
  /* 숙제문제 11.9 */ // 실행시 위에 srcp부터 ~ Munmap 까지 주석처리 할 것
  fbuf = malloc(filesize); //filesize 만큼의 가상 메모리(힙)를 할당한 후(malloc은 아무것도 없는 빈 상태에서 시작) , Rio_readn 으로 할당된 가상 메모리 공간의 시작점인 fbuf를 기준으로 srcfd 파일을 읽어 복사해넣는다.
  Rio_readn(srcfd, fbuf, filesize); // srcfd 내용을 fbuf에 넣는다(버퍼에 채워줌)
  Close(srcfd); // 윗줄 실행 후 필요 없어져서 닫아준다 // 양 쪽 모두 생성한 파일 식별자 번호인 srcfd 를 Close() 해주고
  Rio_writen(fd, fbuf, filesize); // Rio_writen 함수 (시스템 콜) 을 통해 클라이언트에게 전송한다 
  // fbuf를 fd에다가 넣는다(fbuf는 사실 포인터. 걔를 밀면서 writen, 미는 애는 새로 선언한 usrbuf)
  free(fbuf); // Mmap은 Munmap, malloc은 free로 할당된 가상 메모리를 해제해준다.
}

/*
 * 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"); // home.html에서 video src 추가
  else
    strcpy(filetype, "text/plain");
}

void serve_dynamic(int fd, char *filename, char *cgiargs, char *method) {
  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));  // fd에 버퍼 넣는다
  sprintf(buf, "Server: Tiny Web Server\r\n");
  Rio_writen(fd, buf, strlen(buf));

  if (!strcasecmp(method, "HEAD")) // 같으면 0(false). 다를 때 if문 안으로 들어감
    return; // void 타입이라 바로 리턴해도 됨(끝내라)

  // 응답의 첫 번째 부분을 보낸 후, 새로운 자식 프로세스를 fork한다
  if (Fork() == 0) { /* Child */ 
    /* Real server would set all CGI vars here */
    // cgiargs에 arguments 2개가 들어있음 (parse에서 물음표 기준으로 2개로 나눴음 strcpy)
    setenv("QUERY_STRING", cgiargs, 1); // 환경변수 설정. QUERY_STRING 환경변수를 요청 URI의 CGI인자들로 초기화한다
    Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */ // 자식의 표준 출력을 연결 파일 식별자로 재지정
    Execve(filename, emptylist, environ); /* Run CGI program */ // 그 후 CGI프로그램 로드 후 실행
  }
  Wait(NULL); /* Parent waits for and reaps child */ // 부모는 자식이 종료되어 정리되는 것을 기다리기 위해 wait 함수에서 블록된다(대기하는 함수)
}

Proxy.c (Cache)

편하게 보기

#include <stdio.h>
#include "csapp.h"

// Proxy part.3 - Cache
/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define LRU_MAGIC_NUMBER 9999
// Least Recently Used
// LRU: 가장 오랫동안 참조되지 않은 페이지를 교체하는 기법

#define CACHE_OBJS_COUNT 10

/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr =
    "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 "
    "Firefox/10.0.3\r\n";
static const char *requestline_hdr_format = "GET %s HTTP/1.0\r\n";
static const char *endof_hdr = "\r\n";
static const char *host_hdr_format = "Host: %s\r\n";
static const char *conn_hdr = "Connection: close\r\n";
static const char *prox_hdr = "Proxy-Connection: close\r\n";

static const char *host_key = "Host";
static const char *connection_key = "Connection";
static const char *proxy_connection_key = "Proxy-Connection";
static const char *user_agent_key = "User-Agent";

void *thread(void *vargsp);
void doit(int connfd);
void parse_uri(char *uri, char *hostname, char *path, int *port);
void build_http_header(char *http_header, char *hostname, char *path, int port, rio_t *client_rio);
int connect_endServer(char *hostname, int port, char *http_header);

// cache function
void cache_init();
int cache_find(char *url);
void cache_uri(char *uri, char *buf);

void readerPre(int i);
void readerAfter(int i);

typedef struct 
{
  char cache_obj[MAX_OBJECT_SIZE];
  char cache_url[MAXLINE];
  int LRU; // least recently used 가장 최근에 사용한 것의 우선순위를 뒤로 미움 (캐시에서 삭제할 때)
  int isEmpty; // 이 블럭에 캐시 정보가 들었는지 empty인지 아닌지 체크

  int readCnt;  // count of readers
  sem_t wmutex;  // protects accesses to cache 세마포어 타입. 1: 사용가능, 0: 사용 불가능
  sem_t rdcntmutex;  // protects accesses to readcnt
}cache_block; // 캐쉬블럭 구조체로 선언


typedef struct
{
  cache_block cacheobjs[CACHE_OBJS_COUNT];  // ten cache blocks
  // int cache_num; // 캐시(10개) 넘버 부여
  /* feedback : cache_num 사용 되는 곳 없음. 삭제해도 무방 */
}Cache;

Cache cache;


int main(int argc, char **argv) {
  int listenfd, connfd;
  socklen_t clientlen;
  char hostname[MAXLINE], port[MAXLINE];
  pthread_t tid;
  struct sockaddr_storage clientaddr;

  cache_init(); 

  if (argc != 2) {
    // fprintf: 출력을 파일에다 씀. strerr: 파일 포인터
    fprintf(stderr, "usage: %s <port> \n", argv[0]);
    exit(1);  // exit(1): 에러 시 강제 종료
  }
  Signal(SIGPIPE, SIG_IGN); // 특정 클라가 종료되어있다고 해서 남은 클라에 영향가지않게 그 한쪽 종료됐다는 시그널을 무시해라.
  /* 클라이언트를 여러개 받고 서버랑 연결하는데, 만약 정상적인 커넥션과 클로즈를 한다면 소켓을 받으면서 다 닫는 것 까지가 프로세스 과정인데,
    그건 정상적인 과정이니 문제가 안생김. but 클라이언트에서 정상적이지 않은 종료를 해서 소켓이 자기 혼자 닫히거나 사라졌을 때
    서버에서 그 소켓에 접근하려고 할 때 그 소켓에 대해 writen하려고 할 때 response를 보낼 수 있음. 
    그러면 시그널에서 잘못됐다는 시그널을 보내는데 그 보내는 시그널은 받으면 원래는 프로세스가 전체 종료가 됨.
    하지만 이 프로세스는 현재 다른 여러 클라이언트들과도 연결되어있는 상태기 때문에 하나 종료됐다고 해서 다 꺼버리면 안되니까
    그런 시그널을 무시해라, 라는 함수. SIG_IGN : signal ignore */

  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);
    printf("Accepted connection from (%s %s).\n", hostname, port);

    // 첫 번째 인자 *thread: 쓰레드 식별자 / 두 번째: 쓰레드 특성 지정 (기본: NULL) / 세 번째: 쓰레드 함수 / 네 번째: 쓰레드 함수의 매개변수
    Pthread_create(&tid, NULL, thread, (void *)connfd);
    // doit(connfd);
    // Close(connfd);
  }
  return 0;
}

void *thread(void *vargsp) {
  int connfd = (int)vargsp;
  Pthread_detach(pthread_self());
  doit(connfd);
  Close(connfd);
}

void doit(int connfd) {
  int end_serverfd;

  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
  char endserver_http_header[MAXLINE];
  char hostname[MAXLINE], path[MAXLINE];
  int port;
  
  // rio: client's rio / server_rio: endserver's rio
  rio_t rio, server_rio;

  Rio_readinitb(&rio, connfd);
  Rio_readlineb(&rio, buf, MAXLINE);
  sscanf(buf, "%s %s %s", method, uri, version);  // read the client reqeust line

  if (strcasecmp(method, "GET")) {
    printf("Proxy does not implement the method");
    return;
  }
  
  char url_store[100]; // 아직 doit 함수 ㅎㅎ 
  strcpy(url_store, uri); // doit으로 받아온 connfd가 들고있는 uri를 넣어준다

  // the url is cached?
  int cache_index;
  // in cache then return the cache content
  // cache_index정수 선언, url_store에 있는 인덱스를 뒤짐(chche_find:10개의 캐시블럭) 뒤져서 나온 인덱스가 -1이 아니면
  if ((cache_index=cache_find(url_store)) != -1) { // 아니면 -> 내가 url_store에 들어있는 캐쉬인덱스에 접근을 했다는 것 
    readerPre(cache_index); // 캐시 뮤텍스를 풀어줌 (열어줌 0->1)
    Rio_writen(connfd, cache.cacheobjs[cache_index].cache_obj, strlen(cache.cacheobjs[cache_index].cache_obj));
    // 캐시에서 찾은 값을 connfd에 쓰고, 캐시에서 그 값을 바로 보내게 됨
    readerAfter(cache_index); // 닫아줌 1->0 doit 끝
    return;
  }
  
  // parse the uri to get hostname, file path, port
  parse_uri(uri, hostname, path, &port);

  // build the http header which will send to the end server
  build_http_header(endserver_http_header, hostname, path, port, &rio);

  // connect to the end server
  end_serverfd = connect_endServer(hostname, port, endserver_http_header);
  if (end_serverfd < 0) {
    printf("connection failed\n");
    return;
  }

  Rio_readinitb(&server_rio, end_serverfd);

  // write the http header to endserver
  Rio_writen(end_serverfd, endserver_http_header, strlen(endserver_http_header));

  // recieve message from end server and send to the client
  char cachebuf[MAX_OBJECT_SIZE];
  int sizebuf = 0;
  size_t n; // 캐시에 없을 때 찾아주는 과정?
  while ((n=Rio_readlineb(&server_rio, buf, MAXLINE)) != 0) {
    // printf("proxy received %ld bytes, then send\n", n);
    sizebuf += n;
    /* proxy거쳐서 서버에서 response오는데, 그 응답을 저장하고 클라이언트에 보냄 */
    if (sizebuf < MAX_OBJECT_SIZE) // 작으면 response 내용을 적어놈
      strcat(cachebuf, buf); // cachebuf에 but(response값) 다 이어붙혀놓음(캐시내용)
    Rio_writen(connfd, buf, n);
  }
  Close(end_serverfd);

  // store it
  if (sizebuf < MAX_OBJECT_SIZE) {
    cache_uri(url_store, cachebuf); // url_store에 cachebuf 저장
  }
}

void build_http_header(char *http_header, char *hostname, char *path, int port, rio_t *client_rio) {
  char buf[MAXLINE], request_hdr[MAXLINE], other_hdr[MAXLINE], host_hdr[MAXLINE];
  
  // request line
  sprintf(request_hdr, requestline_hdr_format, path);

  // get other request header for client rio and change it
  while (Rio_readlineb(client_rio, buf, MAXLINE) > 0) {
    if (strcmp(buf, endof_hdr) == 0)
      break;  // EOF
    
    if (!strncasecmp(buf, host_key, strlen(host_key))) {
      strcpy(host_hdr, buf);
      continue;
    }

    if (strncasecmp(buf, connection_key, strlen(connection_key))
        &&strncasecmp(buf, proxy_connection_key, strlen(proxy_connection_key))
        &&strncasecmp(buf, user_agent_key, strlen(user_agent_key))) {
        strcat(other_hdr, buf);
      }
  }
  if (strlen(host_hdr) == 0) {
    sprintf(host_hdr, host_hdr_format, hostname);
  }
  sprintf(http_header, "%s%s%s%s%s%s%s",
          request_hdr,
          host_hdr,
          conn_hdr,
          prox_hdr,
          user_agent_hdr,
          other_hdr,
          endof_hdr);
  return;
}

// Connect to the end server
inline int connect_endServer(char *hostname, int port, char *http_header) {
  char portStr[100];
  sprintf(portStr, "%d", port);
  return Open_clientfd(hostname, portStr);
}

// parse the uri to get hostname, file path, port
void parse_uri(char *uri, char *hostname, char *path, int *port) {
  *port = 80;
  char *pos = strstr(uri, "//");

  pos = pos!=NULL? pos+2:uri;

  char *pos2 = strstr(pos, ":");
  // sscanf(pos, "%s", hostname);
  if (pos2 != NULL) {
    *pos2 = '\0';
    sscanf(pos, "%s", hostname);
    sscanf(pos2+1, "%d%s", port, path);
  } else {
    pos2 = strstr(pos, "/");
    if (pos2 != NULL) {
      *pos2 = '\0';  // 중간에 끊으려고
      sscanf(pos, "%s", hostname);
      *pos2 = '/';
      sscanf(pos2, "%s", path);
    } else {
      scanf(pos, "%s", hostname);
    }
  }
  return;
}

void cache_init() {
  cache.cache_num = 0; // 맨 처음이니까
  int i;
  for (i=0; i<CACHE_OBJS_COUNT; i++) {
    cache.cacheobjs[i].LRU = 0; // LRU : 우선 순위를 미는 것. 처음이니까 0
    cache.cacheobjs[i].isEmpty = 1; // 1이 비어있다는 뜻

    // Sem_init : 세마포어 함수 
    // 첫 번째 인자: 초기화할 세마포어의 포인터 / 두 번째: 0 - 쓰레드들끼리 세마포어 공유, 그 외 - 프로세스 간 공유 / 세 번째: 초기 값
    //    뮤텍스 만들 포인터 / 0 : 세마포어를 뮤텍스로 쓰려면 0을 써야 쓰레드끼리 사용하는거라고 표시하는 것이 됨 / 1 : 초깃값 
    // 세마포어는 프로세스를 쓰는 것. 지금 세마포어를 쓰레드에 적용하고 싶으니까 0을 써서 쓰레드에서 쓰는거라고 표시, 나머지 숫자를 프로세스에서 쓰는거라는 표시.
    Sem_init(&cache.cacheobjs[i].wmutex, 0, 1); // wmutex : 캐시에 접근하는 것을 프로텍트해주는 뮤텍스
    Sem_init(&cache.cacheobjs[i].rdcntmutex, 0, 1); // read count mutex : 리드카운트에 접근하는걸 프로텍트해주는 뮤텍스
    // ㄴ flag 지정
    cache.cacheobjs[i].readCnt = 0; // read count를 0으로 놓고 init을 끝냄
  }
}

void readerPre(int i) { // i = 해당인덱스
  // 내가 받아온 index오브젝트의 리드카운트 뮤텍스를 P함수(recntmutex에 접근을 가능하게) 해준다
  /* rdcntmutex로 특정 readcnt에 접근하고 +1해줌. 원래 0으로 세팅되어있어서, 누가 안쓰고 있으면 0이었다가 1로 되고 if문 들어감 */
  P(&cache.cacheobjs[i].rdcntmutex); // P연산(locking):정상인지 검사, 기다림 (P함수 비정상이면 에러 도출되는 로직임)
  cache.cacheobjs[i].readCnt++; // readCnt 풀고 들어감
  /* 조건문 들어오면 그때서야 캐쉬에 접근 가능. 그래서 만약 누가 쓰고있어도 P, readCnt까지는 할 수 있는데 +1이 되니까 1->2가 되고 
    그러면 캐시에 접근을 못하게 됨. but readerAfter에서 -1 다시 내려주기때문에 0, 1, 0 에서만 움직임 */
  if (cache.cacheobjs[i].readCnt == 1)
    P(&cache.cacheobjs[i].wmutex); // write mutex 뮤텍스를 풀고(캐시에 접근)
  V(&cache.cacheobjs[i].rdcntmutex); // V연산 풀기(캐시 쫒아냄) / read count mutex
}

void readerAfter(int i) {
  P(&cache.cacheobjs[i].rdcntmutex);
  cache.cacheobjs[i].readCnt--;
  if (cache.cacheobjs[i].readCnt == 0)
    V(&cache.cacheobjs[i].wmutex);
  V(&cache.cacheobjs[i].rdcntmutex);
}

// int cache_find(char *url) {
//   int i;
//   for (i=0; i<CACHE_OBJS_COUNT; i++) {
//     readerPre(i); /* if문 중간에 멈출 필요 없음 */
//     if ((cache.cacheobjs[i].isEmpty == 0) && (strcmp(url, cache.cacheobjs[i].cache_url) == 0))
//       break;
//     readerAfter(i);
//   }
//   if (i >= CACHE_OBJS_COUNT)
//     return -1;
//   return i;
// }

/* feedback : if문 중간에 멈출 필요 없음 */
int cache_find(char *url) {
  int i;
  for (i = 0; i < CACHE_OBJS_COUNT; i++) {
    readerPre(i);
    if (cache.cacheobjs[i].isEmpty == 0 && strcmp(url, cache.cacheobjs[i].cache_url) == 0) {
      readerAfter(i);
      return i;
    }
    readerAfter(i);
  }
  return -1;
}

int cache_eviction() { // 캐시 쫒아내기
  int min = LRU_MAGIC_NUMBER;
  int minindex = 0;
  int i;
  for (i=0; i<CACHE_OBJS_COUNT; i++) {
    readerPre(i);
    if (cache.cacheobjs[i].isEmpty == 1) {
      minindex = i;
      readerAfter(i);
      break;
    }
    if (cache.cacheobjs[i].LRU < min) {
      minindex = i;
      min = cache.cacheobjs[i]. LRU;
      readerAfter(i);
      continue;
    }
    readerAfter(i);
  }
  return minindex;
}

void writePre(int i) {
  P(&cache.cacheobjs[i].wmutex);
}

void writeAfter(int i) {
  V(&cache.cacheobjs[i].wmutex);
}

// update the LRU number except the new cache one
// void cache_LRU(int index) {
//   int i;
//   for (i=0; i<index; i++) { // ex) 5 일때 5 이하 다 내려줌  /* feedback : index 반으로 나눌 필요 없음 */
//     writePre(i);
//     if (cache.cacheobjs[i].isEmpty == 0 && i != index)
//       cache.cacheobjs[i].LRU--;
//     writeAfter(i);
//   }
//   i++;
//   for (i; i<CACHE_OBJS_COUNT; i++) { // 5부터 10 사이 수도 내려줌
//     writePre(i);
//     if (cache.cacheobjs[i].isEmpty == 0 && i != index) {
//       cache.cacheobjs[i].LRU--; // 이미 찾은 애는 9999로 보냈으니 그 앞에있는 애들 인덱스 -1씩 내려준다
//     }
//     writeAfter(i);
//   }
// }

/* feedback : index 반으로 나눌 필요 없음 */
void cache_LRU(int index) {
  int i;
  for (i = 0; i < CACHE_OBJS_COUNT; i++) {
    if (i == index) { continue; }
    writePre(i);
    if (cache.cacheobjs[i].isEmpty == 0) {
      cache.cacheobjs[i].LRU--;
    }
    writeAfter(i);
  }
}

// cache the uri and content in cache
void cache_uri(char *uri, char *buf) {
  int i = cache_eviction(); // 빈 캐시 블럭을 찾는 첫번째 index
  
  writePre(i);

  strcpy(cache.cacheobjs[i].cache_obj, buf);
  strcpy(cache.cacheobjs[i].cache_url, uri);
  cache.cacheobjs[i].isEmpty = 0;
  cache.cacheobjs[i].LRU = LRU_MAGIC_NUMBER; // 가장 최근에 했으니 우선순위 9999로 보내줌
  cache_LRU(i); // 나 빼고 LRU 다 내려.. 난 9999니까

  writeAfter(i);
}

adder.c

편하게 보기

/*
 * adder.c - a minimal CGI program that adds two numbers together
 */
/* $begin adder */

// tiny서버 실행시킨 후 http://15.164.94.35:8000/cgi-bin/adder?1&7 접속

#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); // url에 우리가 직접 쳤을 때 적용되는 함수
  // strcpy(arg2, p+1);
  // n1 = atoi(arg1);
  // n2 = atoi(arg2);
  sscanf(buf, "first=%d", &n1);
  sscanf(p+1, "second=%d", &n2);
}

  /* 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 */

adder.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Tiny Server</title>
</head>
<body>
<form action="/cgi-bin/adder" method="GET">
  <p>first number: <input type="text" name="first"/></p>
  <p>second number: <input type="text" name="second"/></p>
  <input type="submit" value="Submit"/>
</form>
</body>
</html>

나머지 코드는 여기로!

profile
블로그 이전했습니다. https://yerimi11.tistory.com/

0개의 댓글