Week 05 ~ 07 : 전산학 프로젝트/SW정글 Week 07 : Web Server

[Web Server] 4. web proxy 구현 part II (thread를 이용한 concurrent requests 처리)

정글러 2021. 12. 23. 00:06
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include "csapp.h"
 
/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
 
void doit(int fd);
int parse_uri(char *uri, char *hostname, char *path, int *port);
void makeHTTPheader(char *http_header, char *hostname, char *path, int port, rio_t *client_rio);
void *thread_routine(void *connfdp);
 
int main(int argc, char **argv)
{
    int listenfd;
    char hostname[MAXLINE], port[MAXLINE];
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;
    pthread_t tid;
    if (argc != 2)
    {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(1);
    }
 
    listenfd = Open_listenfd(argv[1]);
    while (1)
    {
        // sever main에서 일련의 처리를 하는 대신 스레드를 분기
        // 각 연결에 대해 이후의 과정은 스레드 내에서 병렬적으로 처리되며
        // main은 다시 while문의 처음으로 돌아가 새로운 연결을 기다린다
        clientlen = sizeof(clientaddr);
        // 이때 각 스레드는 모두 각각의 connfd를 가져야 하기 때문에, 연결마다 메모리를 할당하여 포인팅한다
        int *connfdp = Malloc(sizeof(int));
        *connfdp = Accept(listenfd, (SA *)&clientaddr, &clientlen);
        Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
        printf("Accepted connection from (%s, %s)\n", hostname, port);
        // thread_routine를 실행하는 thread 생성
        // 연결마다 고유한 connfdp를 thread_routine의 입력으로 가져간다
        Pthread_create(&tid, NULL, thread_routine, connfdp);
    }
    return 0;
}
 
void *thread_routine(void *connfdp)
{
    // 각 스레드별 connfd는 입력으로 가져온 connfdp가 가리키던 할당된 위치의 fd값
    int connfd = *((int *)connfdp);
    // 스레드 종료시 자원을 반납하고
    Pthread_detach(pthread_self());
    // connfdp도 이미 connfd를 얻어 역할을 다했으니 반납한다
    Free(connfdp);
    doit(connfd);
    Close(connfd);
    return NULL;
}
 
// 이후 각 스레드의 수행은 seqential과 같으니 생략
cs

터미널을 하나 더 켜듯이 스레드를 하나 더 만드는 것으로 병렬 처리가 가능하다.

주의해야할 점이 둘 있다면

 

1. 각 스레드마다 각각의 fd를 가져야한다. 스레드의 상한이 유한하게 예측이 된다면 미리 그만큼의 배열을 할당하면 될 것이고, 지금처럼 상한이 예측이 안된다면 malloc으로 동적 할당하여(각 스레드에 진입하면 free해야 함) 고유성을 확보해야 할 것이다.

 

2. malloc을 다 쓰면 free로 반납하듯이 각 스레드도 detach로 자원을 반납해야 한다. 스레드를 쓰는 용도에 따라 자원을 남겨야 할 경우도 있겠지만, proxy server에서는 이미 역할을 끝낸 연결의 잔해를 굳이 남겨 자원을 차지할 필요는 없을 것이다.

 

 

벌써 12시인데 part 3 캐시를 끝낼 수 있을까...