select(2)

/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select(2)함수는 socket / pipe 등에서 동시에 여러개의 I/O를 대기할 경우에 특정한 fd에 blocking되지 않고 

 I/O를 할 수 있는 상태인 지를 모니터링하는 함수입니다. 이를 통하여 I/O 가능한 fd이면, I/O 함수를 호출하면 됩니다.

 

 select(2)함수는 일반파일에서는 사용할 일은 거의 없으며, 주로 socket통신이나 pipe등에서 사용합니다. 통신에서 read(2) 또는 recv(2)를 할 때에 peer쪽에서 데이터 전송을 하지 않으면 read / recv 함수가 데이터 전송을 할 때까지 block됩니다. 그런데, 만약 하나의 프로그램에서 여러개의 socket/pipe에서 데이터 읽기를 해야하는 경우에는 어느 fd에서 데이터가 먼저 올지 아무도 모르기 때문에 fd를 순서대로 읽기를 시도한다면 첫번째 fd에 상대방이 데이터 전송을 하지 않게 되면 나머지 fd에 대해서는 상대가 데이터를 전송했는 데도 불구하고 처리를 하지 못하는 문제가 발생합니다.

이 select(2)함수는 여러개의 fd를 동시에 모니터링하다가 한개라도 읽을(주로 읽을 때에 문제가 됨) 수 있는 상태가 되면 blocking을 해제하며 FD_ISSET()를 통하여 읽기/쓰기 준비가 된 fd를 찾을 수 있게 할 수 있습니다.

select(2)함수를 통해서 read(2) / recv(2)에 대한 timeout도 설정할 수 있습니다. 특정시간이 지나도 읽을 수 없으면 오류처리하거나 다른 작업을 할 수 있게 도와줍니다.

 

readfds, writefds, exceptfds, timeout 값들은 select 함수가 호출이 끝나면 내용이 변경되므로 다시 select()함수를 호출할 때에는 timeout값과 readfds, writefds, exceptfds값들을 재설정해야 합니다. 

 

이 함수는 사용상의 불편한 점과 동시에 대기하고 있는 fd가 많으면 급격하게 성능이 떨어지는 문제 등으로 인하여 poll(2) 등의 함수로 대체되고 있습니다. 그러나 아주 오래된 소스와의 하위 호환성과 오래전부터 사용해온 습관때문에 아직도 select(2)를 많이 사용하고 있습니다.

 

 

파라미터

nfds
    -  FD_SET()함수로 설정한 fd값들 중에서 가장 큰 fd값 + 1을 설정해야 합니다.
readfds
    - FD_SET(fd, readfds)로 설정한 fd들이 데이터를 읽을 수 있는 상태일 때까지 대기하게 하고, 
    select(2) 함수가 return된 후에는 readfds에 읽을 수 있는 상태에 있는 fd를 제외하고 
    모두 clear됩니다.

    - FD_ISSET(fd, readfds)으로 읽을 수 있는 상태에 있는 fd인지를 확인하여 
      0이 아닌값이 return되면 읽을 수 있는 상태에 있는 fd입니다.
writefds
    - FD_SET(fd, writefds)로 설정한 fd들이 데이터를 쓸 수 있는 상태일 때까지 대기하게 하고, 
      select(2) 함수가 return된 후에는 writefds에 쓸 수 있는 상태에 있는 fd를 제외하고 
      모두 clear됩니다.

    - FD_ISSET(fd, writefds)으로 쓸 수 있는 상태에 있는 fd인지를 확인하여 
      0이 아닌값이 return되면 쓸 수 있는 상태에 있는 fd입니다.
exceptfds
    - FD_SET(fd, exceptfds)로 설정한 fd들이 오류 상태일 때까지 대기하게 하고, 
      select(2) 함수가 return된 후에는 오류상태가 아닌 fd들은 exceptfds에서 모두 clear됩니다.

    - FD_ISSET(fd, exceptfds)으로 오류 상태에 있는 fd인지를 확인하여 
      0이 아닌값이 return되면 오류 상태인 fd입니다.
timeout
    - readfds, writefds, exceptfds 들이 timeout 시간동안 모두 상태변화가 없으면 
      select함수를 종료시킵니다.
    -  timeout이 NULL이면 timeout이 없이 계속 대기합니다. 
       struct timeval은 sys/time.h에 선언되어 있으며, 마이크로초 단위의 timeout을 설정을 지원합니다.

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

 timeout의 구조체는 const가 아니므로 내부적으로 소요된 시간만큼 count down되며, 
 timeout 시간이 지나면 tv_sec, tv_usec 이 각각 0이 됩니다. 
 만약 timeout이 되기전에 select가 return 되었다면 소요된 시간 만큼 차감되어
 tv_sec, tv_usec에 저장됩니다.

 

RETURN

0보다 큼
    - readfds, writefds, exceptfds에 대한 처리가능 상태인 fd의 전체 건수를 return 합니다.
    
0
    - timeout이 발생하였으며, 처리 가능 상태인 fd가 하나도 없습니다.


-1
    - 오류가 발생하였으며, 상세한 오류 내용은 errno에 저장됩니다.

 EBADF : 설정된 fd 중에서 유효하지 않은 fd가 있음. (한개라도 있으면)
 EINTR : signal이 catch되어 select()가 중단됨
 EINVAL : nfds 가 -값이거나 timeout의 tv_sec, tv_usec값이 잘못 설정됨.
 ENOMEM : 처리를 위한 내부 메모리 할당이 실패함.

 


활용 예제

 

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>

int main(void)
{
    fd_set rfds;
    struct timeval tv;
    int retval;
    int sock, sock1, sock2;
    int max_fd = -1;
    char buffer[4096];
    int size;

    ......

    if((sock = socket(....)) == -1) {
        return 1;
    }

    if(bind(sock, ....) == -1) {
        return 1;
    }

    ......

    sock1 = accept(...);
    sock2 = accept(...);

    ......

    FD_ZERO(&rfds);
    FD_SET(sock1, &rfds);

    max_fd = sock1;

    FD_SET(sock2, &rfds);

    //가장 큰 fd값을 알아야 함.
    if(max_fd < sock2) { 
        max_fd = sock2;
    }



    /* timeout을 5초로 설정함 */
    memset(&tv, 0x00, sizeof(tv));
    tv.tv_sec = 5;

    retval = select(max_fd + 1, &rfds, NULL, NULL, &tv);

    if (retval == -1) {
        fprintf(stderr, "select error: %s\n", strerror(errno));
    } else if (retval == 0) {
        fprintf(stderr, "timeout이 발생하였습니다.\n");
    } else {
        if(FD_ISSET(sock1, &rfds) == 0) {
            if((size = read(sock1, buffer, 4096)) > 0) {
                buffer[size] = 0x00;
                printf("SOCK1: %s\n", buffer);
           }
        }

        if(FD_ISSET(sock2, &rfds) == 0) {

            if((size = read(sock2, buffer, 4096)) > 0) {

                buffer[size] = 0x00;

                printf("SOCK2: %s\n", buffer);

            }

        }

    }

    return 0;
}

 


see also : Socket 통신과 Socket 응용

 

 

 

 

블로그 이미지

사용자 자연&사람

행복한 개발자 programmer since 1995.

댓글을 달아 주세요