
void doit(int clientfd)
{
int serverfd;
char request_buf[MAXLINE], response_buf[MAXLINE];
char method[MAXLINE], uri[MAXLINE], path[MAXLINE], hostname[MAXLINE], port[MAXLINE];
char *srcp, filename[MAXLINE], cgiargs[MAXLINE];
rio_t request_rio, response_rio;
/* Request 요청 라인 읽기 Client -> Proxy */
Rio_readinitb(&request_rio, clientfd); // 클라이언트의 요청을 읽기 위해 rio와 fd 연결
Rio_readlineb(&request_rio, request_buf, MAXLINE); // 요청 라인 읽기
printf("Request headers:\n %s\n", request_buf);
sscanf(request_buf, "%s %s", method, uri); // 요청 라인에서 method, uri를 읽어서 지역 변수 `method` `uri`에 할당
parse_uri(uri, hostname, port, path);
sprintf(request_buf, "%s %s %s\r\n", method, path, "HTTP/1.0"); // end server에 전송 요청 & 라인 수정
// 요청 메소드가 GET | HEAD가 아닌 경우 예외 처리
if (strcasecmp(method, "GET") && strcasecmp(method, "HEAD"))
{
clienterror(clientfd, method, "501", "Not implemented", "Tiny does not implement this method");
return;
}
// end server 소켓 생성
serverfd = is_local_test ? Open_clientfd(hostname, port) : Open_clientfd("52.79.234.188", port);
if (serverfd < 0)
{
clienterror(serverfd, method, "502", "Bad Gateway", "Failed to establish connection with the end server");
return;
}
/* Request 요청 라인 전송 Proxy -> Server */
Rio_writen(serverfd, request_buf, strlen(request_buf));
/* Request 요청 헤더 읽기 & 전송 Client -> Proxy -> Server */
read_requesthdrs(&request_rio, request_buf, serverfd, hostname, port);
/* Response 라인 읽기 & 전송 Server -> Proxy -> Client */
Rio_readinitb(&response_rio, serverfd); // 서버의 응답을 담을 버퍼 초기화
Rio_readlineb(&response_rio, response_buf, MAXLINE); // 응답 라인 읽기
Rio_writen(clientfd, response_buf, strlen(response_buf)); // 클라이언트에 응답 라인 보내기
/* Response 헤더 읽기 & 전송 Server -> Proxy -> Client */
int content_length;
while (strcmp(response_buf, "\r\n"))
{
Rio_readlineb(&response_rio, response_buf, MAXLINE);
if (strstr(response_buf, "Content-length")) // 응답 바디 수신에 사용하기 위해 바디 사이즈 저장
content_length = atoi(strchr(response_buf, ':') + 1);
Rio_writen(clientfd, response_buf, strlen(response_buf));
}
/* Response 바디 읽기 & 전송 Server -> Proxy -> Client */
if (content_length)
{
srcp = malloc(content_length);
Rio_readnb(&response_rio, srcp, content_length);
Rio_writen(clientfd, srcp, content_length);
free(srcp);
}
}
while (1)
{
clientlen = sizeof(clientaddr);
printf("Main thread: Waiting for connection...\n"); // 메인 스레드 상태 로그
fflush(stdout);
// 주의: connfd를 루프 지역 변수로 바로 사용하면 레이스 컨디션 발생 가능!
// 동적 할당을 사용하여 각 스레드에 고유한 connfd 복사본을 전달해야 함.
int *connfd_ptr = malloc(sizeof(int)); // 1. connfd를 저장할 메모리 동적 할당
if (connfd_ptr == NULL)
{
fprintf(stderr, "Malloc error\n");
continue; // 메모리 할당 실패 시 다음 연결 시도
}
*connfd_ptr = Accept(listenfd, (SA *)&clientaddr, &clientlen); // 2. Accept 결과를 할당된 메모리에 저장
if (*connfd_ptr < 0)
{ // Accept 실패 시
free(connfd_ptr); // 할당된 메모리 해제
// 에러 로그 또는 기타 처리 (Accept 래퍼가 종료시킬 수도 있음)
continue;
}
// 클라이언트 정보 가져오기 (선택 사항)
char client_hostname[MAXLINE], client_port[MAXLINE];
Getnameinfo((SA *)&clientaddr, clientlen, client_hostname, MAXLINE, client_port, MAXLINE, 0);
printf("Main thread: Accepted connection from (%s, %s) on fd %d\n", client_hostname, client_port, *connfd_ptr);
fflush(stdout);
// 새로운 스레드 생성
pthread_t tid;
// 3. 스레드 생성 함수(thread_routine)를 호출하고, connfd_ptr을 인자로 전달
Pthread_create(&tid, NULL, thread_routine, connfd_ptr);
// !!! 중요 !!!
// Main 스레드는 clientfd(여기서는 *connfd_ptr)를 닫으면 안 됩니다!
// 새로 생성된 스레드가 doit 함수를 실행하고 난 뒤 직접 닫아야 합니다.
// Main 스레드는 스레드 생성 후 바로 다음 Accept()로 돌아갑니다.
}
}
| 구분 | Sequential Proxy (순차적) | Thread per connection (연결당 스레드) |
|---|---|---|
| 처리 방식 | 클라이언트 요청 하나씩 순서대로 처리 | 각 클라이언트 연결마다 별도 스레드를 생성하여 동시에 처리 |
| 동시성 | 없음 (단일 작업 흐름) | 높음 (다중 작업 흐름) |
| 대기 시간 | 앞선 요청이 끝나야 다음 요청 처리 (클라이언트 대기 발생) | 여러 요청 동시 처리 (클라이언트 대기 시간 감소) |
| 자원 활용 | 시스템 자원 활용도 낮음 | 시스템 자원 활용도 높음 (병렬 처리) |
| 복잡도 | 단순 | 상대적으로 복잡 (스레드 관리, connfd 안전 전달 필요) |
| 성능 (부하 시) | 요청 많아지면 느려짐 | 요청 많아도 비교적 빠른 응답 유지 (단, 스레드 한계 존재) |
make 파일 안쓰고 일일히 컴파일하던 내자신....