[Network] 12. IO 멀티플렉싱

2023. 5. 28. 21:21

IO 멀티플렉싱 기반의 서버

멀티 프로세스 서버의 단점

2023.05.28 - [Computer Science/Network Programming] - [Network] 10. 멀티 프로세스 기반의 서버 구현

 

[Network] 10. 멀티 프로세스 기반의 서버 구현

프로세스의 이해와 활용 다중 접속 서버의 구현 방법들 다중접속 서버 : 둘 이상의 클라이언트에게 동시 접속을 허용하여 동시에 둘 이상의 클라이언트에 서비스를 제공하는 서버를 의미한다.

mobuk.tistory.com


프로세스의 빈번한 생성은 성능의 저하로 이어진다.
멀티 프로세스의 흐름을 고려해서 구현해야한다. 이는 구현이 어려워지는 요인 중 하나이다.
프로세스 간 통신이 필요한 상황에서는 서버의 구현이 더 복잡해진다. ( 파이프 사용 )

 

그래서 프로세스를 여러개 만드는 것이 아닌 하나의 프로세스가 다수의 클라이언트에게 서비스를 할 수 있도록 하는 방안이 고안되었다. 이를 IO 멀티 플랙싱이라고 한다.

 

IO 멀티 플랙싱 서버의 구현

select 함수의 기능과 호출 순서

select 함수를 이용하면 배열에 저장된 다수의 파일 디스크립터를 대상으로 여러가지 행동을 취할 수 있다. 

  • 수신한 데이터를 지니고 있는 소켓이 존재하는가?
  • 블로킹 되지 않고 데이터의 전송이 가능한 소켓은 무엇인가?
  • 예외상황이 발생한 소켓은 무엇인가?

select 함수를 호출하기 위해서는 아래와 같은 순서를 지켜야한다.

 

Step 1-1 파일 디스크립터의 설정

  • FD_ZERO(fd_set * fdset)
  • FD_SET(int fd, fd_set * fdset) 
  • FD_CLR(int fd, fd_set * fdset)
  • FD_ISSET(int fd, fd_set *fdset)

 

Step 1-2 검사의 범위 지정

fd_max = 검사할 파일 디스크립터 수

관찰이 되는 파일 디스크립터의 수를 지정한다. 이것은 select 함수의 인자로 들어갈 것이다. 

 

 

Step 1-3 타임아웃 설정

select 함수 호출 이후에 무한정 블로킹 상태에 빠지지 않게 하기 위해 설정한다. 이렇게 설정된 것도 select 함수의 인자로 들어간다. 

#include <sys/time.h>

struct timeval timeout;

timeout.tv_sec = 5;
timeout.tv_usec = 5000;

 

select 함수

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval* timeout);

maxfd : 검사 대상이 되는 파일 디스크립터의 수 (위에서 설정한 fd_max 값)
readset : 수신된 데이터의 존재여부에 관심 있는 파일 디스크립터 정보
writeset : 블로킹 없는 데이터 전송의 가능여부에 관심있는 파일 디스크립터 정보
exceptset : 예외상황의 발생여부에 관심이 있는 파일 디스크립터 정보
timeout : 위에서 설정한 timeout

반환값 : 오류 발생시 -1 타임아웃 발생시 0, 파일스크립터에 변화가 생기면 변화가 발생한 파일 디스크립터의 수가 반환된다.

select 함수 기본 사용 예제

//파일 디스크립터 선언
fd_set reads;

// 파일 디스크립터의 설정
FD_ZERO(&reads);
FD_SET(0, &reads); // stdin 사용

// 검사의 범위 지정
fd_max = 1

//timeout 구조체 선언 및 초기화
struct timeval timeout;

// 아래 두 줄은 select 가 반복될때마다 매번 초기화 해줘야한다. (while문에 함께 사용한다.)
timeout.tv_sec = 5;
timeout.tv_usec = 5000;

//select 함수 호출
result = select(fd_max, &temps, 0, 0, &timeout);

if(result == -1) // error
{
	puts("select() error");
}
else if(result == 0) // timeout
{
	puts("timeout");
}
else // 수신된 데이터가 있는 상태
{
	if(FD_ISSET(0, &temps))
    {
    //데이터 읽기
    }
}

 

멀티 플렉싱 서버의 구현

int main(int argc, char * argv[])
{
	int serv_sock, clnt_sock;
    sturct sockaddr_in serv_adr, clnt_adr;
	socklen_t adr_sz;

	fd_set reads, cpy_reads;
    int fd_max, str_len, fd_num, i;
    char buf[BUF_SIZE]
    struct timeval timeout;
    
    // socket, bind, listen
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));
    
 	if(bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr))==-1)
    	error handling("bind error()");
    if(listen(serv_sock, 5) == -1)
    	error_handling("listen() error");
       
    //파일 디스크립터 옵션 설정 및 탐색 최대 길이 설정
    FD_ZERO(&reads);
    FD_SET(0, &reads);
    fd_max = serv_sock;
    
    while(1)
    {
    	cpy_reads = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;
        
        if((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1)
            break;
        else if (fd_num == 0)
        	continue;
            
        for(i = 0; i< fd_max+1; i++) // 파일 디스크립터 위치 탐색
        {
            if(FD_ISSET(0, &temps))
            {
            	if(i == serv_sock) // 수신된 데이터가 serv_sock에 있다면 연결 요청
                {
                    adr_sz = sizeof(clnt_adr);
                    //accept
                    clnt_sock = accept(serv_sock, (struct sockaddr*)*clnt_adr, &adr_sz);
                    FD_SET(clnt_sock, &reads);
                    if(fd_max<clnt_sock)
                        fd_max = clnt_sock;
                    printf("connected client: %d \n", clnt_sock);
                }
                else  // 수신된 데이터가 클라이언트에 연결된 소켓에 있다면
                {
                    str_len = read(i, buf, BUF_SIZE);
                    if(str_len == 0)
                    {
                        FD_CLR(i, &reads);
                        close(i);
                        printf("closed client: %d\n", i);
                    }
                    else
                    {
                        write(i, buf, str_len);
                    }
            	}
            }
        }   
    }
}

 

select 함수의 가장 큰 단점은 이벤트가 발생한 대상을 찾기 위해 반복문을 구성해야하는 것이다. 

BELATED ARTICLES

more