Chapter 12 IO 멀티플렉싱

시캉·2024년 7월 4일
0

ft_irc

목록 보기
10/13

12-1 IO 멀티플렉싱 기반의 서버

멀티프로세스 서버의 단점과 대안

  • 멀티 프로세스 서버 : fork()를 통해 클라이언트에 대응 프로세스를 실행하여 여러 클라이언트들의 접속을 다루는 방식.
    -> 많은 연산, 메모리 공간 사용, IPC 통신의 복잡함.

단일 프로세스로 연결을 진행하는 방식이 IO 멀티플렉싱 서버

멀티플렉싱 단어의 이해

  • 하나의 통신채널을 통해서 둘 이상의 데이터를 전송하는데 사용되는 기술
  • 물리적 장치의 효율성을 높이기 위해서 최소한의 물리적인 요소만 사용해서 최대한의 데이터를 전달하기 위해 사용되는 기술
    즉 멀티플렉싱 서버는 단 하나의 프로세스를 통해 여러 클라이언트와의 연결을 다루게 된다.

12-2 select 함수의 이해와 서버의 구현

select 함수의 기능과 호출순서

select함수를 이용하면 여러 개의 파일 디스크립터들을 관찰할 수 있다.

  • 수신한 데이터를 지니는 소켓이 있는지
  • 논 블로킹으로 데이터의 전송이 가능한 소켓이 무엇인지
  • 예외상황이 발생한 소켓이 무엇인지

를 볼 수 있고, 상황이 발생한 경우를 가리켜 "이벤트" 가 발생했다고 한다.
위 세 항목(수신, 전송, 예외)에 따라서 구분하여 발생하는 이벤트에 따라 우리는 클라이언트에게 어떤 반응을 해줄것인지를 서버에서 세팅하면 되는 것.

이러한 select()는 다음 과정을 거친다.

파일 디스크립터의 설정 | 검사의 범위 지정 | 타임 아웃 설정
-> select 함수 호출 -> 호출 결과 확인.

파일 디스크립터 설정

관찰 대상인 파일 디스크립터를 설정해주는 부분이 필요하다.
fd_set 자료형을 이용하여 해당 파일 디스크립터가 관찰대상임을 설정해주면 된다.

다음 함수들을 이용하여 fd_set형 변수의 비트를 다룰 수 있다.

  • FD_ZERO(fd_set * fdset) : 인자로 전달된 주소의 fd_set형 변수의 모든 비트를 0으로 초기화
  • FD_SET(int fd, fd_set * fdset) : 매개변수 fdset으로 전달된 주소의 변수에 매개변수 fd로 전달된 파일 디스크립터 정보를 등록한다.
  • FD_CLR(int fd, fd_set * fdset) : 매개변수 fdset으로 전달된 주소의 변수에서 매개변수 fd로 전달된 파일 디스크립터 정보를 삭제한다.
  • FD_ISSET(int fd, fd_set * fdset) : 매개변수 fdset으로 전달된 주소의 변수에 매개변수 fd로 전달된 파일 디스크립터 정보가 있다면 양수를 반환. (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 : 검사 대상이 되는 파일 디스크립터 수
  • readset : fd_set 형 변수에 수신된 데이터의 존재여부에 관심있는 파일 디스크립터 정보를 모두 등록해서 그 변수의 주소값을 전달
  • writeset : fd_set 형 변수에 블로킹 없는 데이터 전송의 가능여부에 관심있는 파일 디스크립터 정보를 모두 등록해서 그 변수의 주소 값을 전달한다.
  • exceptset : fd_set 형 변수에 예외상황의 발생여부에 관심이 있는 파일 디스크립터 정보를 모두 등록해서 그 변수의 주소 값을 전달한다.
  • timeout : select 함수 호출 이후에 무한정 블로킹 상태에 빠지지 않도록 타임아웃을 설정하기 위한 인자를 전달한다. 변화가 발생하지 않아도 해당 시간이 지나면 결과를 리턴.(NULL 시 타임아웃 기능 off)
  • 반환값 : 오류발생 시 -1, 타임아웃 시 0, 관심 대상으로 등록된 파일 디스크립터에 해당 관심에 관련된 변화가 발생하면 0보다 큰 값이 반환되는데, 이 값은 변화가 발생한 파일 디스크립터 수를 의미함.

select 함수호출 이후의 결과 확인

  • select 반환값이 양수라면, 그 수 만큼 파일 디스크립터에 변화가 생겼다는 것임.
  • 전달한 fd_set 변수에서 변화가 생긴 fd만 1로 비트가 설정되어 해당 디스크립터에서 변화가 있음을 알게됨

멀티플렉싱 서버 구현

select()를 통해 함수 사용법을 바탕으로 멀티플렉싱 서버를 구현.

앞서 에코 서버를 구성했던 내용에서, listen() 이후 select()를 사용하여 어떤 fd가 입력을 받는지 이벤트 발생을 확인하고, listen()중인 fd에 발생 시, 새로 소켓 연결(accept())을 진행해주고, 그 외에 클라이언트 파일 디스크립터라면 메시지를 읽어오는 형식으로 구현하면 된다.

// 앞서 서버 소켓의 생성, 세팅 진행
...
if (bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
	error("bind() error");
if (listen(serv_sock, 5) == -1)
	error("listen() error");

FD_ZERO(&reads);
FD_SET(serv_sock, &reads);
fd_max=serv_sock;

while(1)
{
	cpy_reads=reads; // fd_set copy. 원본 유지를 위해 진행하는 select의 사용법 일부.
	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 (i=0; i<fd_max+1; i++)
	{
		if (FD_ISSET(i, &cpy_reads))
		{
			if(i==serv_sock) // connect request!
			{
				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("Connect client: %d \n", clnt_sock);
		}
		else
		{
			std_len = read(i, buf, BUF_SIZE);
			if(str_len==0) // close request
			{
				FD_CLR(i, &reads);
				close(i);
				printf("closed client: %d\n", i);
			}
			else
			{
				write(i, buf, str_len);
			}
		}
	}
}
close(serv_sock);
...
  • fd_set형 변수 reads를 cpy_reads로 복사하는 과정. select가 끝나고 나면 변화가 생긴 파일 디스크립터 위치를 제외한 나머지 위치 비트들은 0으로 초기화된다. 따라서 원본 유지를 위해 복사의 과정을 거쳐야함.
  • timeout 구조체 또한 반복적으로 초기화해준다. select 이후 구조체의 값이 타임아웃이 발생하기까지 남았던 시간으로 바뀌기 때문이다.
  • 서버 소켓이 0번 인덱스로 fd배열에 들어가있다. 서버 소켓에 이벤트가 발생했다는 것은 연결요청이 있다는 것.
  • 읽어들인 데이터의 길이를 통해 EOF가 들어온건지, 읽기가능한 데이터가 왔는지 확인하여 상황에 맞게 분기.

마치며

12장에서는 IO 멀티플렉싱 서버를 구성하는 방식을 살펴보았다. 10, 11장을 건너 뛴 이유는 ft_irc 자체에서 IO 멀티플렉싱 서버를 구현할 것을 요구하고 있었기에, 급한 부분 먼저 확인하여 읽어보았다.

서브젝트 내에서도 poll(), epoll(), kqueue() 등 여러 함수들 중 하나를 선택하게 되어있었는데, 책에서는 select를 다루고 있었다. 하지만 전반적인 동작은 다른 함수들과 동일하다. 이벤트가 발생하는 파일 디스크립터를 찾고 그 디스크립터에 발생한 이벤트를 확인하여 처리하는 과정은 내가 프로젝트를 진행하면서 구현했던 과정과 동일하여 특히 재밌는 장이었다.

추후 epoll()에 대한 내용이 책에 다루어지는 것 같았는데, 무슨 차이가 있는지 확인하고 kqueue나 poll 등 멀티플렉싱을 돕는 함수들이 어떤 차이가 있는지 확인해봐야겠다.

0개의 댓글

관련 채용 정보