본문 바로가기
책/TCPIP 소켓프로그래밍

12. I/O Multiplexing

by 맑은청이 2020. 4. 28.
728x90
반응형

멀티프로세스 서버는 프로세스마다 별도의 메모리 공간을 유지하기 때문에 상호간에 데이터 교환에 복잡한 방법을 택할 수 밖에 없습니다. 즉 IPC가 좀 복잡한 통신 방법인거죠. 이 대안 법이 'IO 멀티플렉싱' 입니다.  

 

IO 멀티플렉싱 서버는 프로세스의 생성을 동반하지 않으면서 다수의 클라이언트에게 서비스를 제공할 수 있습니다. 

 

 

멀티플렉싱의 단어의 이해가 필요 합니다.

'하나의 통신 채널을 통해 둘 이상의 데이터(시그널)를 전송하는데 사용되는 기술' 입니다. 

 

시(time)분할 멀티플렉싱 기술

-> 여러명에서 대화를 오고가는 느낌

주파수(frequency) 분할 멀티플렉싱 기술

-> 여러명이 한번에 이야기 하는데 목소리로 구분 하는 느낌 

 

 

 

-select 함수의 이해

Select 함수는 멀티플렉싱 서버 구현에 있어 가장 대표적인 방법입니다. 윈도우에서도 동일한 이름을 쓰기 때문에 이식성에도 좋습니다. 

 

select 함수가 관찰 가능한 항목

1.수신한 데이터르 지니고 있는 소켓 존재하는가?

2.블로킹되지 않고 데이터의 전송이 가능한 소켓은 무엇인가?

3.예외상황이 발생한 소켓은 무엇인가?

 

 

Select의 호출 순서

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

1-2. 검사의 범위 지정

1-3. 타임아웃의 설정

 

2. select 함수의 호출

3. 호출 결과 확인

 

 

그럼 자세히 살펴보도록 하겠습니다. 

 

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

select 함수를 사용하면 여러 개의 파일 디스크립터를 동시에 관찰할 수 있습니다. 이는 소켓의 관찰로도 해석할 수 있습니다. 먼저 관찰하고자 하는 파일 디스크립터를 모아야하는데 이 때 관찰항목(수신,전송,예외)에 따라 구분해서 모아야합니다. 

이때 세 묶음으로 묶을 때 사용되는 변수가 fd_set형 변수입니다. 이는 0과 1로 표현되는 비트단위 배열이라고 생각하시면 됩니다. 

 

이 배열의 비트가 1로 설정되면 해당 파일 디스크립터가 관찰 대상임을 의미합니다. 

직접 등록하는 게 아니기 때문에 다음 매크로 함수들로 통해 이루어 집니다.  

 

1.FD_ZERO(fd_set * fdset) : 모든 비트 0으로 초기화

2.FD_SET(int fd, fd_set* fdset) : fd 주소에 정보 등록 0 -> 1

3.FD_CLR(int fd, fd_set* fdset) : fd 주소에 정보 삭제 1 -> 0 

4.FD_ISSET(inf fd, fd_set* fdset) : 정보가 있으면 양수 반환 -> 호출결과 확인하는 용도

 

#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);

-> 성공 시 0 이상, 실패 시 -1 반환

 

maxfd 파일 디스크립터의 수
readset '수신된 데이터 존재 여부'에 관심있는 파일 디스크립터 정보를 모두 등록해 변수 주소 값 전달
writeset '블로킹 없는 데이터 전송 여부'에 관심있는 파일 디스크립터 정보를 모두 등록해 변수 주소 값 전달
exceptset '예외 상황 발생 여부'에 관심있는 파일 디스크립터 정보를 모두 등록해 변수 주소 값 전달
timeout 무한정 블로킹 상태에 빠지지 않도록 타임 아웃 설정
반환 값

오류 : -1 /타임아웃 반환 : 0 /변환 발생 : 변화 발생한 파일 디스크립터의 수

 

1-2 . 검사 범위 지정

디스크립터 값이 생성될 때 마다 가장 큰 파일 디스크립터 값에 1씩을 더해서 인자로 전달하면 됩니다. 

1-3. 타임 아웃 설정

마지막 매개변수 

struct timeval{
	long tv_sec;
    long tv_usec; // microseconds
    }

 

사실 select 함수는 관찰중인 파일 디스크립터에 변화가 생겨야 반환을 합니다. 만약 변화가 생기지 않으면 블로킹 상태에 머물게 됩니다. 이를 막기 위해 타임아웃을 지정하는 것 입니다. 

 

select 함수 호출이 완료되고 나면 1로 설정된 모든 비트가 다 0을 변경되지만 변화가 발생한 파일 디스크립터에 해당하는 비트만 그대로 1로 남아있게 됩니다. (이는 XOR 개념과 비슷합니다)

 

 

select 함수 호출 예제 확인

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

#define BUF_SIZE 30	

int main(int argc, char *argv[]){
	fd_set reads, temps;
    //select 함수 호출 후에 변화가 있는 비트를 제외하고 0으로 초기화 되기 때문에 
    //원본 유지를 위해 복사의 과정을 거쳐야한다.
	int result,str_len;
	char buf[BUF_SIZE];
	struct timeval timeout;


	FD_ZERO(&reads);
	FD_SET(0,&reads);

/*
	tiemout.tv_sec = 5;
    timeout.tv_usec = 0;
    
*/
//select 함수 이후로 계속 변함으로 계속 초기화를 해주어야한다. 
	while(1){
		temps = reads;
		timeout.tv_sec = 5;
		timeout.tv_usec = 0;
		result = select(1,&temps,0,0,&timeout);	
		if(result == -1){
			puts("select() error!");
			break;
		}
		else if(result ==0){
			puts("Time Out!");
		}else{
			if(FD_ISSET(0,&temps)){//지정한 파일 디스크립터에 들어왔는지 확인 
				str_len = read(0,buf,BUF_SIZE);
				buf[str_len]=0;
				printf("message from console %s",buf);
			}
		}
	}
	return 0;
}

 

멀티플렉싱 서버의 구현

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 100
void error_handling(char *buf);

int main(int argc,char *argv[]){
	int serv_sock,clnt_sock;
	struct sockaddr_in serv_adr,clnt_adr;
	struct timeval timeout;
	fd_set reads,cpy_reads;

	socklen_t adr_sz;
	int fd_max,str_len,fd_num, i;
	char buf[BUF_SIZE];
	if(argc!=2){
		printf("Usage: %s<port>\n",argv[0]);
		exit(1);
	}

	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(serv_sock,&reads);
    //데이터수신 여부 관찰 대상에 서버 소켓이 포함.
    //연결 요청도 데이터 전송을 통해 
    //수신된 데이터 존재
    //'연결 요청이 있었다' 로 해석
    
	fd_max = serv_sock;

	while(1){
		cpy_reads = reads;//store first reads
		timeout.tv_sec = 5;
		timeout.tv_usec = 5000;
		
		if((fd_num=select(fd_max+1,&cpy_reads,0,0,&timeout))==-1)
			break;
		if(fd_num==0)
			continue;
            
            
        //변화가 있으면 for 문 수행     
		for(i=0;i<fd_max +1 ;i++){
			if(FD_ISSET(i,&cpy_reads)){//변화가 있는 파일 디스크립터 찾는 if 문
				if(i==serv_sock){
					adr_sz = sizeof(clnt_adr);
					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);
					}
				}
			}
		}
	}
	close(serv_sock);
	return 0;
}

void error_handling(char * buf){
	fputs(buf,stderr);
	fputc('\n',stderr);
	exit(1);
}
728x90
반응형