이전까지의 포스팅을 통해 WebProxy-Lab에서의 기본 프록시를 구현했다.
2025.05.05 - [크래프톤 정글] - [WebProxy-Lab] proxy 서버 구현하기 Part.2 - 프록시 서버 구현
[WebProxy-Lab] proxy 서버 구현하기 Part.2 - 프록시 서버 구현
이전 포스팅에서 프록시 구현을 위한 설계를 진행했다.2025.05.05 - [크래프톤 정글] - [WebProxy-Lab] proxy 서버 구현하기 Part.1 - 요구사항 확인 및 설계 [WebProxy-Lab] proxy 서버 구현하기 Part.1 - 요구사항
www.gowoong.com
기본 프록시를 구현하고 40점의 점수를 얻을 수 있었다. 이제 요구사항 2번째인 동시성 처리에 대한 내용을 살펴보겠다.
1. 요구사항 확인
proxylab.pdf 에서는 동시성 처리를 다음과 같이 요구하고 있다.
- 여러 클라이언트 요청을 동시에 처리하도록 구현해야 함
- pthread_create로 각 요청마다 쓰레드 생성, detached 모드로 실행
- 쓰레드 안전한 함수(open_clientfd, open_listenfd 등) 사용
즉 이 요구사항에서는 CSAPP 12.5.5의 내용을 확인해야 할 것으로 생각된다. 해당 내용은 이전에 포스팅했던 것을 확인하면 될 것 같다.
2025.05.03 - [Deep Dive/CS] - [Deep Dive] 쓰레드와 병렬 프로그래밍 - 1탄 개념 이해
[Deep Dive] 쓰레드와 병렬 프로그래밍 - 1탄 개념 이해
정글 8주차에 웹서버를 구현하다 보니 쓰레드를 사용해서 병렬 처리를 해야 했다. 그래서 당장 구현을 할 수준으로만 이해를 한 뒤 구현을 끝낸 지금 다시 쓰레드에 대해 깊게 이해를 하려고 한
www.gowoong.com
2. 구현 아이디어
이번 구현 아이디어는 사실 간단 하다고 생각한다. (쓰레드를 생성하는 방법을 잘 안다는 가정 아래)
실제로 코드를 작성하고 수정하는 부분이 매우 정말 짧다.
main 함수를 한 군데 몇 군데 손보고
thread 라는 새로운 함수를 생성하면 된다.
1. 요청 처리용 쓰레드 생성
int *connfdp = malloc(sizeof(int));
*connfdp = connfd;
pthread_create(&tid, NULL, thread, connfdp);
먼저 malloc을 이용해 int 사이즈크기의 공간을 할당한다.
그렇다면 왜 동적할당으로 변수 공간을 설정했을까? 만약 이렇게 하지 않으면 생성된 모든 쓰레드가 connfd의 주소를 공유하게 된다. 그러면 main 루프가 다음 클라이언트 요청을 받아도 connfd값을 바꿔 버릴 수 있게 된다.
즉 main 쓰레드와 worker 쓰레드가 connfd를 공유하게 된 것이 문제가 될 수 있으니 각 요청마다 malloc을 이용해 별도의 변수 공간을 만들어 해당 변수를 사용해 쓰레드를 실행해야 할 것이다.
2. thread 함수 생성
void *thread(void *vargp){
int connfd = *((int *)vargp);
free(vargp);
pthread_detach(pthread_self());
doit(connfd);
Close(connfd);
return NULL;
}
동시성 처리를 위해 위와 같은 함수를 새로 생성해야 한다. 그렇다면 왜 이런 함수를 생성해야 하는지 알아보겠다.
pthread_create()가 요구하는 함수 형식은 다음과 같다.
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
- 쓰레드 생성 시 콜백 함수로 호출할 함수를 넘겨야 하며,
- 이 함수는 반드시 void *(*)(void *) 형식이어야 한다.
- 그래서 thread() 같은 쓰레드 루틴이 반드시 필요하게 된다.
즉 이 함수는 콜백 함수로 호출할 함수를 넘겨야 하는데 이 함수의 형식이 void *(*)(void *) 여야 한다는 것이다. 이 형식의 의미는 인자로 void * 하나를 받아야 하고 반환값도 void * 인 함수에 대한 포인터이다.
우리가 구현했던 doit 함수는가 콜백 함수로 사용할 수 있는 타입을 반환하지 않기 때문에 별도의 thread 함수를 구현했다. 또한 사실 doit() 함수를 수정해서 사용할 수 있을 수 있지만 별도로 분리해서 추 후에 변경이 있을 때 확인이 가능하도록 했다.
thread() 안에서 다음과 같은 중요한 작업을 수행한다.
int connfd = *((int *)vargp); // 소켓 fd 복사
free(vargp); // heap 자원 회수
pthread_detach(pthread_self()); // 스레드 종료 후 자원 자동 해제
doit(connfd); // 핵심 요청 처리
Close(connfd); // 연결 종료
먼저 소켓 fd를 복사한다. 그리고 바로 인자로 들어온 소켓 fd의 자원을 회수한다. 이렇게 하는 이유는 그냥 최대한 빨리 자원을 반환하려는 목적도 있으며 쓰레드에서 공유 자원을 이용해 무엇인가를 수행하는 것보다 지역변수 형태로 쓰레드 안에서 사용하는 것이 빠르고 좋기 때문일 것이다.
쓰레드 종료 시 자원 누수 방지를 위해 detach() 호출
pthread_detach(pthread_self());
- 이 호출은 pthread_join()을 호출하지 않아도 쓰레드의 자원을 커널이 자동으로 회수하게 한다.
- main()에서 detach를 처리하는 건 구조상 더 어렵고 불안정하다고 한다.
- 따라서 쓰레드 루틴 내부에서 처리하는 것이 설계상 훨씬 깔끔하다.
이 후 thread() 내에서 doit()을 호출하고 Close를 진행하면 사실상 요구사항 2번인 동시성 처리의 구현이 끝이 난다.
결과
이제 저장하고 make clean && make를 수행한 뒤 ./driver.sh 를 실행한다.
totalScore: 55/70
기본 점수 40 점에 동시성 점수 15 점을 합쳐 55 점이 나왔다. 이제 마지막 캐시가 남았다. 캐싱 구현은 매우 긴 작업이 될 것 같다. 준비가 되면 다음 포스팅과 함께 돌아오겠다.
'크래프톤 정글' 카테고리의 다른 글
[WebProxy-Lab] proxy 서버 구현하기 Part.4 - 캐시 기능: 구현 (0) | 2025.05.05 |
---|---|
[WebProxy-Lab] proxy 서버 구현하기 Part.4 - 캐시 기능: 개념 정리 (0) | 2025.05.05 |
[WebProxy-Lab] proxy 서버 구현하기 Part.2 - 프록시 서버 구현 (1) | 2025.05.05 |
[WebProxy-Lab] proxy 서버 구현하기 Part.1 - 요구사항 확인 및 설계 (2) | 2025.05.05 |
Malloc Lab 회고 (0) | 2025.04.30 |