멀티...flex
가 아니고 multi plex
이전에 멀티 프로세스 기반의 서버를 알아보았는데 사실 프로세스를 여러 개 만드는 것은 좋지 않다.
프로세스를 생성하는 것 자체가 메모리를 많이 차지하게 되고, 또 프로세스 간의 통신이 필요한 상황에는 서버의 구현이 더 복잡해진다. 이러한 멀티 프로세스 서버의 대안들 중 하나가 바로 IO 멀티플렉싱이다.
하나의 프로세스가 다수의 클라이언트에게 서비스를 할 수 있도록 하는 것이다. 이러기 위해서는 하나의 프로세스가 여러 개의 소켓을 컨트롤 할 수 있어야 한다.
select 함수를 이용하면, 파일 디스크립터(이하 fd)를 배열에 저장해 놓고 동시에 관찰할 수 있다. 그리고 다음과 같은 질문을 던진다.
그리고 select 함수의 호출 방법 및 순서는 다음과 같다.
select 함수는 fd를 하나의 배열로 관리한다고 하였다.
fd_set
형 변수에 select 함수에 전달할 fd의 정보를 하나로 묶어 놓는다.
그리고 다음과 같은 컨트롤 함수들을 이용하여 변수를 컨트롤 할 수 있다.
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로 전달된 파일 디스크립터 정보가 있다면 양수를 반환한다.#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readset, fd_set *writeset,
fd_set *exceptset, const struct timveval *timeout);
파라미터가 좀 많다.
수신된 데이터의 존재여부
에 관심 있는 fd 정보를 fd_set형 변수에 등록해서, 그 변수의 주소값블로킹 없는 데이터 전송의 가능여부
에 관심 있는 fd 정보를 fd_set형 변수에 등록해서, 그 변수의 주소값예외상황의 발생여부
에 관심 있는 fd 정보를 fd_set형 변수에 등록해서, 그 변수의 주소값-1
, timeout에 인한 반환 시 0
, 관심 대상으로 등록된 fd에 변화가 발생하면 0보다 큰 값select 함수호출 이후에는 변화가 발생한 소켓의 디스크립터만 1로 설정되고 나머지는 0으로 초기화 된다.
FD_ZERO(&reads);
FD_SET(sockfd, &reads);
fd_max = sockfd;
기본적인 소켓 생성, 주소 설정, bind, listen 등의 과정은 생략. reads는 fd_set
형 변수
reads를 모두 0으로 초기화 하고 sockfd까지 reads에 추가한다. (연결 요청이 들어왔을 때 accept 해야 하므로 서버의 소켓도 변화를 감시해야 함)
while(1)
{
cpy_reads = reads;
timeout.tv_sec = 5;
timeout.tv_usec = 0;
fd_num = select(fd_max+1, &cpy_reads, 0, 0, &timeout);
if(fd_num == 0)
{
continue;
}
}
select 함수를 호출할 때마다 timeout을 새롭게 명시해줘야 한다.
select의 3, 4번째 인자에 0을 전달하면, 수신된 데이터가 있는지에 대해서만 관찰하는 select 함수가 된 것이다.
for(int i=0;i<fd_max+1;i++)
{
if(FD_ISSET(i, &cpy_reads))
{
if(i == sockfd)
{
int adrsz = sizeof(cliaddr);
cSockfd = accept(sockfd, (struct sockaddr*)&cliaddr, &adrsz);
FD_SET(cSockfd, &reads);
if(fd_max < cSockfd)
fd_max = cSockfd;
printf("Connected client : %d\n, cSockfd);
}
else
{
// read message 등 코드 실행 부분
FD_CLR(i, &reads);
close(i);
printf("Closed Client : %d\n, i);
}
}
}
for문으로 fd_max까지 확인하면서 변화가 생긴 fd가 어디인지 살펴본다.
만약 sockfd에서 생겼다면 새로운 연결 요청이 들어온 것이므로 accept 해 주고
만약 sockfd가 아닌 다른 fd에서 생겼다면 클라이언트와 연결된 소켓이라는 뜻이므로 데이터를 읽어들이는 등의 과정을 수행하고, 닫아야 하는 상황이 생긴다면 FD_CLR
함수와 close
를 통해 삭제 해준다.
select
에 사용할 fd_set 변수는 원본은 그냥 두고 복사본을 집어넣어 사용한다.
fd_set은 비트 단위의 리스트이다. FD_SET
에 의해 소켓이 등록되면 해당 소켓에 매칭되는 비트는 1로 저장된다. 그리고 select 함수는 1로 저장된 것들만 감시한다.
select 함수가 실행되면, 그 0이든 1이든 하는 결과들을 2번째 인자로 넘겨주었던 fd_set 변수에 저장하는데, 데이터가 들어온 소켓은 1, 아니면 0으로 저장하고 나서 그것을 FD_ISSET
함수를 통해 보는 것이다.
만약 이 과정에서 값이 바뀐 fd_set 변수를 그대로 다음 select에 사용하게 되면 0으로 되어 있는 소켓들은 select 함수가 감시하지 않게 된다.
그래서 감지 목록을 유지하기 위해서 반드시 사용하기 전에 fd_set 함수를 복사해놓은 다른 변수를 만들고, 그 복사본 변수를 사용해야 한다.
select 사용하기 전에 timeval 시간을 재설정 해야 한다.
위 코드에서 timeout에 사용되는 timeval을 5초로 잡았다.
이 때, select에서 5초 중 2초만 기다렸다가 감시대상을 확인하고 리턴 하면, timeval 변수에는 5초에서 2초를 뺀 3초를 timeval 변수에 저장한다.
그러므로, 재설정 없이 계속 같은 timeval 변수를 select에 사용할 경우 시간은 점점 차감되어 나중에는 0이 되어 버릴 것이다.