스레드 연결로 동시성 확보해야한다
실제 작동하는 오리진 서버는 단일이긴 하지만
스레드 프록시도 상당히 효과 있다고 한다
클라이언트들 ──(동시)──> [프록시: 스레드 N] ──(각자)──> 오리진 서버들
이런식의 흐름이라
프록시가 여러 연결을 동시에 붙잡고읽기/쓰기, DNS, connect 등을 겹쳐 해서 줄여준단다
대강 찾아보니 여긴 간단하다
그냥 pthread
함수 써서 하면 되니 말이다
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg)
thread: 생성된 스레드 ID가 써질 곳(출력용)
attr: 스레드 속성(스택 크기, detach 상태 등). 기본값 쓰려면 NULL
start_routine: 스레드가 실행할 함수(반드시 void* f(void*)
형태)
arg: start_routine
에 전달될 단일 포인터(필요하면 구조체로 묶어서 전달)
반환: 0 성공, 그 외 에러코드
int pthread_detach(pthread_t thread)
thread: 더 이상 join
하지 않을 스레드를 “분리(detach
)” 상태로
효과: 종료 시 자원 자동 회수
걍 이 2개 함수로 해결 했다
함수 쓰는 건 위에 나온대로만 넣으면 되가지고 별로 할말이 없다
그 대신 사용하는 인자에 주의를 두어야 한다
난 딱 두가지 알아보았다
사실상 과제에서는 차이가 없기에 더 짧은 거 쓰는게 이득이다
int*
단일 인자 방식메인 함수:
Accept
로 받은 connfd
를 malloc(sizeof(int))
에 복사해 스레드에 전달해준다
스레드 함수:
*(int\*)
로 값만 꺼내 쓰고, 그 포인터 한 번만 free
, 처리 끝나면 Close(connfd)
// proxy_threads_intptr.c
#include "csapp.h"
void handle_client(int connfd);
void *thread_main(void *vargp); // [THREAD] 프로토 타입 선언
int main(int argc, char **argv) {
Signal(SIGPIPE, SIG_IGN);
if (argc != 2) { fprintf(stderr, "usage: %s <port>\n", argv[0]); exit(1); }
int listenfd = Open_listenfd(argv[1]);
while (1) {
struct sockaddr_storage clientaddr;
socklen_t len = sizeof(clientaddr);
int connfd = Accept(listenfd, (SA*)&clientaddr, &len);
// [THREAD] 인자로 넘길 connfd를 힙에 “값 복사”
int *arg = Malloc(sizeof(int)); // [THREAD] 포인터 메모리 할당
*arg = connfd; // [THREAD] 연결 fd의 포인터로 지정
pthread_t tid; // [THREAD]
int rc = pthread_create(&tid, NULL, thread_main, arg); // [THREAD]
if (rc != 0) { // [THREAD] 실패 시 누수 방지
Free(arg); // [THREAD] 포인터 할당 메모리 해제
Close(connfd); // [THREAD] 연결 닫기
}
}
return 0;
}
void *thread_main(void *vargp) { // [THREAD] 스레드 함수
Pthread_detach(pthread_self()); // [THREAD] join 안하려고
// [THREAD] int* 경로: 값만 꺼내 로컬에 보관
int connfd = *(int*)vargp; // [THREAD] 스레드 전용 로컬 변수
Free(vargp); // [THREAD] 인자 블록은 여기서 정확히 1회 해제
handle_client(connfd); // main에서 이사옴 1
Close(connfd); // main에서 이사옴 2
return NULL; // main에서 이사옴 3
}
[THREAD] 주석 붙어있는 애들이 추가된 줄들이다
보다시피 그렇게 많이 바뀌지 않는다
client_arg_t*
구조체 인장 방식메인 함수:
connfd
+ 클라이언트 주소를 구조체에 담아 스레드에 전달
스레드 함수:
구조체 포인터를 받아 필드 사용 후 그 구조체 포인터만 free
typedef struct { // [THREAD] 여러 값을 한 번에 안전히 넘기기 위한 구조체
int connfd; // [THREAD] 워커가 처리할 클라이언트 소켓 FD (소유권: 워커)
struct sockaddr_storage addr; // [THREAD] 클라이언트 주소(로깅/필터링 용도; 옵션)
socklen_t addrlen; // [THREAD] 주소 길이
} client_arg_t; // [THREAD] 구조체 이름
static void *thread_main(void *vargp) { // [THREAD] POSIX 요구 시그니처: void* → void*
Pthread_detach(pthread_self()); // [THREAD] join 안하려고
client_arg_t *arg = (client_arg_t*)vargp; // [THREAD] 메인에서 힙에 만든 인자 패키지 수신
int connfd = arg->connfd; // [THREAD] 처리할 FD를 로컬 변수로 복사(소유권 명확화)
free(arg); // [THREAD] 인자 패키지 메모리 해제
handle_client(connfd); // main에서 이사옴 1
Close(connfd); // main에서 이사옴 2
return NULL; // main에서 이사옴 3
}
int main(int argc, char **argv) {
Signal(SIGPIPE, SIG_IGN); // [THREAD] 파이프 끊김으로 전체 프로세스 즉사 방지
if (argc != 2) { fprintf(stderr, "usage: %s <port>\n", argv[0]); exit(1); }
int listenfd = Open_listenfd(argv[1]);
while (1) {
struct sockaddr_storage clientaddr;
socklen_t len = sizeof(clientaddr); // [THREAD] Accept 전에 매회 길이 초기화(필수)
int connfd = Accept(listenfd, (SA*)&clientaddr, &len);
client_arg_t *arg = malloc(sizeof(*arg)); // [THREAD] 워커에 넘길 인자 패키지
arg->connfd = connfd; // [THREAD] 처리할 FD 지정
arg->addr = clientaddr; // [THREAD] 피어 주소 복사(원하면 로깅에 사용)
arg->addrlen = len; // [THREAD] 주소 길이 저장
pthread_t tid; // [THREAD] 생성된 워커의 ID를 받을 변수
Pthread_create(&tid, NULL, thread_main, arg); // [THREAD] 워커 생성 → 메인은 즉시 다음 Accept로
}
return 0;
}
확연히 더 길어진 모습이다
기능은 동일하고 중간에 구조체가 개입한다는 차이가 존재한다
지금은 사실상 무의미하고
클라이언트 IP/포트 로깅, 접근 제어, 통계 등 부가정보가 필요할 때 이런식으로 사용하면 좋다