posted by 방랑군 2012. 1. 19. 22:05

파일 디스크립터의 변화를 확인하는 함수
기본적으로 blocking 함수(확인할 파일 디스크립터에 변화가 생길 때까지 무한 대기)

멀티플렉싱 서버를 구현하기 위한 방법으로 select 함수가 가장 많이 사용되는 방법이고 윈도우즈 시스템에서도 동일한 이름으로 동일한 기능을 하는 함수를 제공하고 있으니 이식성에서도 높은 점수를 줄 수 있다

select 함수를 사용하게 되면, 한 곳에 모아놓은 여러 개의 파일 디스크립터를 동시에 관찰할 수 있다.
수신할 데이터를 지니고 있는 파일 디스크립터가 어떤 것들인지, 데이터를 전송할 경우 블로킹되지 않고 바로 전달 가능한 파일디스크립터는 어떤 것들인지, 그리고 예외가 발생한 파일 디스크립터는 어떤 것들인지 정도가 관찰 내용이 된다.

select 함수의 기능과 호출 순서

-- select 함수 사용 순서 --
1. 디스크립터 설정
2. 검사 범위 설정
3. 타임 아웃 설정
4. select 함수 호출
5. 결과 확인
-------------------------



===== 디스크립터 설정 =====

1) 파일 디스크립터 설정
  - 변화를 확인할 파일 디스크립터들을 한 묶음으로 모아둔다.
  - 파일 디스크립터를 모아두는 비트단위 자료형 fd_set 이용
  - 3가지 변화(수신 데이터 존재유무, 데이터 송신 가능여부, 소켓의 예외상황 발생여부)별로 파일 디스크립터들을 구분지어 모아둠



fd_set 자료형 관련 함수
 FD_ZERO(fd_set * fdset); //fd_set 초기화 함수
 FD_SET(int fd, fd_set * fdset); //해당 파일디스크립터 fd를 1로 셋
 FD_CLR(int fd, fd_set * fdset); //해당 파일디스크립터 fd를 0으로 셋
 FD_ISSET(int fd, fd_set * fdset); //해당 파일디스크립터 fd가 1인지 확인


===== 검사범위 설정 =====

2) 검사할 파일 디스크립터의 범위 지정
  - 검사해야할 파일 디스크립터의 개수를 전달.
  - 가장 큰 파일 디스크립터 값에 1을 더함(파일 디스크립터 값이 0부터 시작하므로)


===== 타임아웃 설정 =====

3) 타임 아웃 설정
  - select 함수가 blocking되는 것을 피하기 위해 타임 아웃을 설정함. 

===== select 함수의 원형 ===== 

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

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

   리 턴 값    의    미
   -1    오류 발생
    0    타임 아웃
    0보다 큰 수    변화발생 파일 디스크립터 수

- n : 검새 대상이 되는 파일 디스크립터의 수

- readfds : 이 리스트에 있는 식별자들은 시스템에 의해 즉시 입력이 가능한지 확인된다. 즉, 입력 가능으로 인해 반환된 식별자에 대해 recv()는 블로킹되지 않는다.

- writefds : 이 리스트에 있는 식별자들은 시스템에 의해 즉시 출력이 가능한지 확인된다, 즉 출력 가능으로 인해 반환된 식별자에 대해 send()는 블로킹되지 않는다.

- excepfds : 이 리스트에 있는 식별자들은 시스템에 의해 예상되는 예외 사항이나 에러가 발생했는지 확인된다. TCP소켓에서 발생할 수 있는 이러한 예상되는 예외 사항의 한 예로 상대방이 데이터 전송 중에 TCP연결을 끊었을 때가 있다. 이런 경우, 다음에 이어지는 읽기 또는 쓰기는 실패하고 ECONNRESET에러를 나타낸다.

- timeout : 함수 호출 후, 무한 대기 상태에 빠지지 않도록 타임-아웃(time-out)을 설정하기 위해
 인자를 전달한다.

※ select 함수 호출시 전달되는 파일 디스크립터의 정보를 소켓뿐 아니라 파일을 나타내는 경우에도
   전달 가능한다.


===== 결과 확인 =====


fd_set자료형에 파일디스크립터 0(stdin), 3의 변화를 확인하기 위해 설정




Select() 호출하면 변화가 생긴 파일디스크립터는 1로 세팅됨(그림에서 파일디스크립터 3이 변화가 발생한 것을 확인할 수 있음)

- 변화가 확인된 해당 파일디스크립터와 데이터 통신을 진행하면 된다.


[ 파일 디스크립터 범위 설정하기 ]

select 함수는 여러 파일 디스크립터를 검사하고, 그 결과를 전달해 준다. 
select 함수는 여러 개의 파일 디스크립터를 확인해야 하는데, 이왕이면 확인해야 하는 파일 디스크립터의 범위를 제한해 주면, 보다 효율적으로 수행할 수 있다.
그래서 select 함수의 첫 번째 인자로 검사해야 하는 총 디스크립터의 개수를 넘겨주게 된다.
그러나 일반적으로 디스크립터는 생성될 때마다 값이 1씩 증가하기 때문에 가장 큰 파일 디스크립터 값에 1을 더해서 인자로 전달하면 된다.
1을 더하는 이유는 디스크립터 값이 0부터 시작하기 때문이다. 따라서 인자로 n이라는 값을 넘겨주게 되면 select 함수는 검사하게 되는 파일 디스크립터의 범위를 부터 n-1로 설정된다.
따라서 반드시 1을 더해줘야 한다.


[ 타임아웃 (time out) 설정하기 ]

타임아웃을 설정하기 위한 timeval 구조체

struct timeval
{
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
}
예를 들어서 tv_sec가 3이고 tv_usec가 500000 이면 타임아웃은 3.5초로 설정된다. 
이렇게 설정한 timeval 구조체 변수의 포인터를 select 함수의 마지막 인자(timeout 인자)로 넘겨주게 되면 파일 디스크립터에 아무런 변화가 없더라도 3.5초가 지나면 무좋건 리턴하게 된다. 만약에 타임 아웃을 설정해 주지 않을 경우, NULL 포인터를 인자로 전달하면 된다.


[ select 함수 호출 이후 결과 확인 ]
select 함수가 리턴되고 나서 무엇보다도 중요한 것은 결과를 얻는 것이다. 일단 함수 호출이 정상적으로 리턴했다는 것은 파일 디스크립터에 변화가 있엇거나, 아니면 타임아웃이 발생했거나 둘 중에 하나이다. 

리턴 값이 -1인 경우는 오류발생을 의미한다. 또한 0이 리턴 된 경우에는 타임아웃에 의해 리턴되었음을 의미한다. 즉 0이 리턴된 경우 파일 디스크립터에 아무런 변화도 발생하지 않았다는 의미가 된다. 
그러나 리턴된 값이 0보다 큰 경우에는, 변화가 발생한 파일 디스크립터의 수를 의미하게 된다.
예를 들어 수신할 데이터가 존재하는 파일 디스크립터가 두 개 발생했다면, 2가 리턴될 것이다.

-------- select 호출 전 / 후 --------

==select 호출 전 ==
fd0  fd1  fd2  fd3  fd4
| 1 | 0 | 0 | 1 | 0 | 0 |...........................

==select 호출 후==
fd0  fd1  fd2  fd3  fd4
| 0 | 0 | 0 | 1 | 0 | 0 |...........................

select 함수의 예)

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

#define BUFSIZE 30

int main(int argc, char *argv[])
{
  fd_set reads, temps;
  int result;

  char message[BUFSIZE];
  int str_len;
  struct timeval timeout;

  FD_ZERO(&reads);  //0으로 초기화
  FD_SET(0&reads);  //파일디스크립터 0(stdin) 설정

  
/*timeout.tv_sec = 5;
   * timeout.tv_usec = 100000;*/

          //잘못된 timeout 설정
          
  while(1)
  {
    temps = reads;  //원본 보존 위해 복사본 temp를 이용

    timeout.tv_sec = 5;  //timeout 설정
    timeout.tv_usec = 0;

    result = select(1&temps, 00&timeout);
    if(result == -1)
    {
      puts("select(): 오류 발생");
      exit(1);
    }

    else if(result == 0)  //timeout에 의한 return
    {
      puts("select(): 시간이 초과되었습니다.");
    }

    else
    {
      if(FD_ISSET(0&temps));
      {
          str_len = read(0, message, BUFSIZE);
          message[str_len] = 0;
          fputs(message, stdout);
      }
    }
  }
  
  return 0;
}



[결과]



이미 주비해둔 fd_set 변수를 임시 변수에 복사해 두고 있다. 여기에는 그럴만한 이유가 있다. select 함수 호출이 끝나면 변화가 생긴 파일디스크립터 위치를 제외한 나머지 위치의 비트들이 0으로 초기화된다. 따라서 원본 변수를 직접 select 함수의 인자로 전달해 버리면 또 다시 변수를 설정하는 과정을 거쳐야 한다.
이를 막기 위해서 원본은 보존 하고 임시 변수에 원본을 복사해서 이것을 가지고 select 함수를 호출한다. 일반적인 select 함수 사용이니 반드시 기억해야한다.
그리고 timeout 변수가 반복문 내부에 존재하게 되는데... select 함수를 호출하기 전에 매번 타임 아웃을 재 설정 하게 되어 있는 것이다. 다시 비효율적인 방법으로 생각할수도있으나 select 함수 호출이 끝나게 되면 타임아웃이 발생하기 까지 남아 있던 시간이 timeval 구조체 변수에 저장된다.
따라서 timeval 구조체 변수를 한번만 설정하고 계속해서 select 함수를 호출한다면 나중에는 0이 된다.
select 함수를 호출하고 만약 콘솔로부터 입력된 데이터가 있다면 0보다 큰 수가 리턴될 것이며, 입력이 없어서 타임아웃이 발생하는 경우에는 0이 리턴될것이다.

'강좌 > C#' 카테고리의 다른 글

.Net single-instance application without activation  (0) 2012.01.21
C# 사운드 재생  (1) 2012.01.21
멀티플렉싱(multiplexing)과 select()함수  (0) 2012.01.19
C# 멀티코어 프로그래밍  (0) 2012.01.19
[C#] Template Method 패턴  (0) 2012.01.19