W07)WebProxy

HiroPark·2023년 4월 20일
0

Jungle

목록 보기
8/10

7주차는 웹 서버를 구현해보고, 이 웹 서버에 대한 프록시 기능을 하는 프록시서버를 구현하는 주였다.

전체 코드

무엇을 해야했는가

처음에는 네트워크 공부를해야하나... 했는데, CSAPP 11장을 여러번 읽고 나니 무엇을 해야할지 감이 왔다.
위의 사진에서 보이듯, 인터넷 어플리케이션은 user code - kernel code - hardware의 3단계의 깊이 구조를 가진다.

이중, 내가 직접 코드로 구현해야 할 것은 user code 단에서 소켓 인터페이스를 이용하여서 클라이언트와 서버간에서 요청과 응답을 주고받는 웹 서버였다.

소켓은 각 연결의 엔드포인트(종단점)를 의미하며 (address:port)의 구조로 이뤄져있다.

이 소켓을 다뤄서 네트워크 애플리케이션을 만들기 위해서 Unix의 I/O 함수들과 함께 사용되는 함수들의 집합이 "소켓 인터페이스"이다.

소켓 인터페이스

전체적인 개괄은 이렇게 생겼다.
그림에서 보이듯, socket,bind,connect등 open** fd 로 감싸져 있는 함수들은 Open 함수를
사용하면 됐기때문에 실제로 사용할 일이 있지는 않았다.

이들을 적절히 잘 배합해서 서버를 만들기만 하면 된다!

tiny

역할

  • 요청을 듣다가, 요청이 들어오면 accept하여 연결 식별자 생성
  • 요청을 분석
  • 분석한 내용을 바탕으로 연결 식별자에 정적 파일 OR CGI 프로그램을 돌린 값을 써준다

tiny.c 코드

proxy

앞서는 유저와 tiny서버 만이 존재해서, 그 둘끼리만 요청과 응답이 오고갔다면, proxt 서버는 유저와 tiny 사이에 자리잡아서, 요청을 대신 받아서 서버로 forwarding하고, 이에대한 응답도 대신 받아서 클라이언트에게 forwarding 하는 역할을 한다.

이 과정에서 추가해야 했던것이

  • conccurent: 여러 요청을 동시에 처리하는 기능
  • 웹 서버의 응답을 캐싱하는 기능
    이었다.

conccurent 서버의 경우 Pthread_create를 이용해서 각 요청을 처리하는 쓰레드를 생성해줌으로써 해결할 수 있었다.

caching

이 부분이 문제였는데, 프록시서버 내부에 자체적으로 서버의 응답 값을 저장해주고, 동일 리소스에 대한 요청에는 캐시의 값을 리턴해주어야 했다.

문제는, 무슨 자료구조를 써야할지부터가 어려웠다.
각 cached object를 저장하는 구조체와, 해당 구조체를 이어놓은 연결리스트를 사용해야 했다.

typedef struct cache_object {
    struct cache_object* next;
    char* id; // 유저는 파일이름으로 찾자나....
    void* data;
    int length;
} cache_object;

typedef struct cache_list {
    cache_object* start;
    cache_object* end;
    unsigned int left_space;
    int readcnt; // 현재 읽고 있는 사람수
    sem_t r; // r은 readcnt에 접근하는 세마포어 
    sem_t w; // w는 크리티컬 섹션에 대한 접근 제어    
    sem_t serviceQueue; // request의 순서를 저장

} cache_list;

이 연결리스트 - 캐시 - 에 대해서 LRU의 eviction(쫓아내는) policy를 적용해야 했기에, 캐시의 최대 크기가 초과돼서 캐시를 비워줘야 할때마다 연결리스트의 맨 앞에서부터 지워가는 방식을 적용해야 했다.
또한 추가되는 object는 연결리스트의 뒤에 넣어주었고, 값을 읽은것도 사용한 것으로 치기때문에 읽었을시에는 연결 리스트의 맨 뒤로 옮겨주었다.

synchronization

여러 쓰레드가 동시에 캐시에 접근하는 일이 발생하면, 이들의 동시적인 접근을 어떻게 조정해줄 것인가가 synchronization문제였다.

일단, 바이너리 세마포어인 mutex를 이용해서, 임계 영역을 잠궈주는 방식을 사용했다.

여럿이 읽고, 쓰려할때, reader와 writer 사이의 우선순위를 어떻게 조정해줄 것인가가 관건이었는데, CSAPP 12.5.5 절에서는 여럿의 reader를 허용하고, writer는 reader들이 다 읽을때까지 기다리는 reader - 선호 방식에 대한 코드가 있어서 이를 사용했다.

그런데, 이 방식을 사용하면, 한명의 reader라도 있으면 writer가 무한정 대기해야하는 starvation 문제가 발생하기 때문에 , 또 다른 해결책을 강구해보았다.

https://en.wikipedia.org/wiki/Readers–writers_problem

그래서 해당 링크의 3번째 해결책을 적용해보았다.
이는 밑과 같이 reader / writer의 각 진입점에 대한 뮤텍스를 적용해줌으로써

 P(&cache->serviceQueue);
    P(&cache->r); // a. readcnt를 사용하기 위해 잠그고
    cache->readcnt++;
    if (cache->readcnt == 1) { // 첫번째 reader야
        P(&cache->w); // reader가 한명이라도 존재하는 동안 writer는 못씀 
    }
    V(&cache->serviceQueue);
    V(&cache->r); // b. 다시 풀어줌

reader와 writer가 공평하게 기회를 (FIFO로 세마포어가 들어오면 FIFO에 근접하게 처리되게) 얻을 수 있게 처리해주는 코드입니다

profile
https://de-vlog.tistory.com/ 이사중입니다

0개의 댓글