• 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);
    }
    


+ Recent posts