- Synchronous Blocking I/O
: 지금까지의 서버는 하나의 클라이언트에 대해서만 요청을 처리할 수 있다.
: 하나의 클라이언트 요청이 끝나기 전까지 다음 요청을 처리할 수 없다.
=> Iteration Server를 도입해야한다. Multi Process Model
- 7_server
: fork()를 통한 프로세스 생성
: 독립된 주소를 갖으므로 안정적이지만, 프로세스 복제에 대한 오버헤드가 크고, Context Switching 비용이 높다.
: 데이터를 공유하기 위해 별도의 작업이 필요하다. (IPC)#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <linux/tcp.h> #include <arpa/inet.h> #include <string.h> #include <signal.h> // signal #include <sys/wait.h> // wait() // 4. 시그널은 중첩되서 발생하지 않는다. - 루프 도입 // 5. 시그널 핸들러는 비동기적으로 동작합니다. // 핸들러 내부에서 블록킹 연산을 절대 수행하면 안됩니다. // - waitpid void handleClient(int csock); void handleChildProcess(int signo) { printf("Cleanup child Prcess!\n"); // while (wait(0) > 0) while (waitpid(-1, 0, WNOHANG) > 0) ; } int main() { // 3. 자식 프로세스의 상태를 수거해야 한다. // -> 좀비 프로세스로 인한 메모리 누수를 방지할 수 있다. // signal handler 등록 // sigaction signal(SIGCHLD, handleChildProcess); int sock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(4000); saddr.sin_addr.s_addr = INADDR_ANY; int option = true; socklen_t optlen = sizeof(option); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen); int ret = bind(sock, (struct sockaddr *)&saddr, sizeof saddr); if (ret != 0) { perror("bind()"); return -1; } listen(sock, 5); // 하나의 클라이언트만 요청을 처리할 수 있다. // => Iteration Server를 도입해야 한다. while (1) { struct sockaddr_in caddr; socklen_t size = sizeof(caddr); int csock = accept(sock, (struct sockaddr *)&caddr, &size); char *client_ip = inet_ntoa(caddr.sin_addr); printf("클라이언트 %s 가 접속되었습니다.\n", client_ip); // 하나의 클라이언트 요청이 끝나기 전까지 다음 요청을 처리할 수 없다. // 1) pid = fork() - 리턴값을 통한 부모 / 자식 분기 // - 자식 프로세스 : 0 // - 부모 프로세스 : 자식 프로세스 pid // 2) fork()가 수행되었을 경우, 사용하지 않는 핸들을 닫아야 한다. pid_t pid = fork(); if (pid == 0) handleClient(csock); close(csock); } close(sock); } #include <stdlib.h> void handleClient(int csock) { int n; char buf[1024]; while (1) { n = read(csock, buf, sizeof(buf)); if (n <= 0) { printf("연결 종료!\n"); break; } n = write(csock, buf, n); } // 연결 종료 close(csock); exit(0); }
Multi Thread Model
- 8_server
: pthread 도입
: kernel 영역을 제외한 복제로 스레드 생성에 대한 오버헤드가 낮고, Context Switching 비용도 낮다. 또 주소를 공유하기 때문에 자원공유가 편리하다.
:하나의 Thread가 문제가 발생할 경우 전체 Process가 죽을 수 있다.// # g++ 8_server.cc -o server -lpthread #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <linux/tcp.h> #include <arpa/inet.h> #include <string.h> #include <signal.h> // signal #include <sys/wait.h> // wait() #include <pthread.h> // 멀티 프로세스 모델은 안전성은 뛰어나지만, 반면에 // 주소 공간의 분리에 의한 데이터 공유가 힘듭니다. // - IPC를 사용해야 한다. // => 스레드 모델을 도입하자. (pthread) void* handleClient(void* arg); int main() { int sock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in saddr; saddr.sin_family = AF_INET; saddr.sin_port = htons(4000); saddr.sin_addr.s_addr = INADDR_ANY; int option = true; socklen_t optlen = sizeof(option); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen); int ret = bind(sock, (struct sockaddr *)&saddr, sizeof saddr); if (ret != 0) { perror("bind()"); return -1; } listen(sock, 5); while (1) { struct sockaddr_in caddr; socklen_t size = sizeof(caddr); int csock = accept(sock, (struct sockaddr *)&caddr, &size); char *client_ip = inet_ntoa(caddr.sin_addr); printf("클라이언트 %s 가 접속되었습니다.\n", client_ip); pthread_t thread; pthread_create(&thread, 0, handleClient, &csock); // 종료 처리를 해야 한다. - join, detach // exit(0); 는 프로세스의 종료 -> return 으로 변경 // ★ 스레드가 온전히 종료할수 있도록 만드는 것이 중요 // 스레드도 결과적으로 fork 를 사용하므로 스레드에 관한 종료가 필요 join 또는 detach // join vs detach? // pthread_join을 통하여 정상적으로 종료된 보조스레드의 자원을 정상적으로 반환시켜주며, // ptrhead_detach함수를 통하여 스레드가 종료시 자원을 정상적으로 반환시켜주도록 하는 함수로 // 메모리릭을 방지할 수 있다. // - pthread_detach // 생성된 thread에서 pthread_detach를 하면 join할 필요없이 생성된 thread가 할일을 다 마치고 // pthread_exit를 하는 순간 자원이 OS에 반납(보장됨) // --> detach(떼어내다), 생성된 스레드를 메인 스레드에서 분리시킨다. // - 호출방법 : 선호출, 후호출 2가지 방식 // 선호출 : int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); // 후호출 : pthread_detach(p_thread); // --> detach를 하려면 선호출로 하는것을 추천한다. // why? 후호출을 하기전에 thread가 종료된다면 메모리릭 발생(희박한 가능성이지만...) // - pthread_join // 보조 스레드의 종료까지 대기해주는 함수(즉, 메인스레드의 종료 대기) // int pthread_join(pthread_t th, void **thread_return); // 첫번째 아규먼트 th는 기다릴(join)할 쓰레드 식별자이며, 두번째 아규먼트 thread_return은 쓰레드의 리턴(return) 값이다. thread_return 이 NULL 이 아닐경우 해다 포인터로 쓰레드 리턴 값을 받아올수 있다. pthread_detach(thread); } close(sock); } #include <stdlib.h> void* handleClient(void* arg) { int csock = *(int*)arg; int n; char buf[1024]; while (1) { n = read(csock, buf, sizeof(buf)); if (n <= 0) { printf("연결 종료!\n"); break; } n = write(csock, buf, n); } // 연결 종료 close(csock); return 0; }
- 7_client ~ 8_client#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdio.h> #include <string.h> #include <linux/tcp.h> int main() { int sock = socket(PF_INET, SOCK_STREAM, 0); struct sockaddr_in addr = {0, }; addr.sin_family = AF_INET; addr.sin_port = htons(4000); addr.sin_addr.s_addr = inet_addr("0.0.0.0"); int option = true; socklen_t optlen = sizeof(option); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen); int ret = connect(sock, (struct sockaddr*)&addr, sizeof addr); if (ret != 0) { printf("접속 실패!!\n"); perror("connect()"); return -1; } while (1) { char buf[1024]; scanf("%s", buf); ret = write(sock, buf, strlen(buf)); if (ret <= 0) break; ret = read(sock, buf, sizeof buf); if (ret <= 0) break; write(1, buf, ret); putchar('\n'); } close(sock); }
'Programing > Server Model' 카테고리의 다른 글
서버 모델 - Event Driven I/O (0) | 2016.02.22 |
---|---|
서버 모델 - I/O multiplexing (0) | 2016.02.22 |
서버 모델 - I/O 모델 개요 (0) | 2016.02.22 |
서버 모델 - 리눅스 소켓 프로그래밍(3) (0) | 2016.02.22 |
서버 모델 - 리눅스 소켓 프로그래밍(2) (0) | 2016.02.18 |