♣ fd_set 구조체
fd_set 구조체는 File Descriptor (이하 FD) 를 저장하는 구조체이다.
안에 내용을 보면 그냥 배열로 여기면 편하다.
구조체 내용은 OS마다 조금씩 다른 듯 하다.
내가 쓰는 Ubuntu 14 에선 /usr/include/sys/select.h 에서 위 구조체를 확인할 수 있었다.
♣ FD_SET
fd_set 구조체에 2와 5의 FD 를 저장한다고 하면,
두번째 비트와 다섯번째 비트가 1로 변경된다.
값 저장은 FD_SET 매크로를 쓴다.
fd_set testFds ;
FD_SET(2, &testFds) ;
FD_SET(5, &testFds) ;
위 testFds 구조체의 배열값을 출력해 보면,
0 1 0 0 1 0 0 0 0 ~~~
위와 같이 2번째와 5번째 비트가 1 로 세팅된다.
♣ FD_ZERO
언급했듯이 fd_set 구조체의 주요 변수는 배열이다.
fd_set testFds ; 와 같이 선언만 하고 안에 배열값을 찍어보면 쓰레기값이 있을 수 있다.
이에 구조체를 초기화 해야 하며 아래와 같이 한다.
FD_ZERO(&testFds) ;
위에 FD_SET 예제에서도 변수 선언 후, 초기화를 해 주어야 한다.
fd_set testFds ;
FD_ZERO(&testFds)
FD_SET(2, &testFds) ;
FD_SET(5, &testFds) ;
♣ FD_CLR
FD_SET(2, &testFds) ;
FD_SET(3, &testFds) ;
FD_SET(5, &testFds) ;
위와 같이 입력 후, testFds 는 아래와 같이 세팅된다.
0 1 1 0 1 0 0 0 0 ~~~
이 때 만약 3번 FD 를 지우고 싶으면 FD_CLR 매크로를 쓴다.
FD_CLR(3, &testFds) ;
결과는 아래와 같다. 세번째 값이 0으로 초기화 되었다.
0 1 0 0 1 0 0 0 0 ~~~~
♣ FD_ISSET
이 매크로는 아래 select 함수와 함께 설명한다.
♣ select()
select 함수는 fd_set 구조체에 할당된 FD 의 이벤트가 발생하면
이를 감지하고 어떤 FD 이벤트가 발생했는지 알려준다.
ex)
fd_set testFds ;
FD_SET(2, &testFds) ;
FD_SET(5, &testFds) ;
select(6, &testFds, NULL, NULL, NULL) ;
이는 FD 이벤트는 6 미만값만 체크하며,
6 미만 중 2와 5 FD 의 이벤트가 발생했을 때만 깨어난다.
만약 2번 FD 의 이벤트가 발생하여 select 함수가 깨어나고,
select 함수 이후에 testFds 를 찍어보면,
0 1 0 0 0 0 0 0 0 ~~~~~~~
식으로 값이 변경된다. -> 이걸 몰라 한참 헤맸다.
즉 testFds 에 2, 5 FD 를 저장한 후,
저장된 FD (2,5) 중 이벤트가 발생하면,
해당 이벤트만 1 로 남기고 모두 0 으로 세팅한다.
이제 어떤 FD의 이벤트가 발생했는지 체크하여 FD_ISSET 매크로를 사용한다.
if(FD_ISSET(2, &testFds))
{
// 2번 FD 가 발생했다.
}
if(FD_ISSET(5, &testFds))
{
// 5번 FD 가 발생했다.
}
위와 같이 해당 FD에 대한 후처리를 진행하면 된다.
♣ 틀 정립
select 함수는 위 예제처럼 testFds 를 변경시킨다.
그럼 select 함수 전이나 후로 매번 testFds 를 다시 세팅해 주어야 한다.
이에 아래와 같은 틀이 될 것이다.
fd_set readFds ;
FD_ZERO(&readFds) ;
FD_SET(2, &readFds) ;
FD_SET(5, &readFds) ;
while(1)
{
// select 함수에서 fd_set 이 변경되니 원본 대신 복사본을 select 함수에 전달한다.
checkFds = readFds ;
printf("sleep - select()\n") ;
select(6, &checkFds, NULL, NULL, NULL) ;
printf("wake up - select()\n") ;
if(FD_ISSET(2, &checkFds))
{
// 2번 FD 이벤트에 대한 사후 처리.
}
if(FD_ISSET(5, &checkFds))
{
// 5번 FD 이벤트에 대한 사후 처리.
}
}
♣ 정리
이런 저런 코드를 보면서 원본 (readFds) 를 checkFds 로 복사하는 이유를 몰랐는데
select() 함수 내에서 fd_set 구조체 값을 변경하기 때문이다.
이를 위와 같이 정리하며 아래 내용이 빠져있다.
select() 함수의 리턴값 처리
select() 함수의 첫번째 인자 max FD 값의 처리
select() 함수의 세번째, 네번째 인자 writeFds, exceptFds 에 대한 처리.
select() 함수의 다섯번째 인자 타이머에 대한 처리.
출처: https://hahaite.tistory.com/290 [Hosang's Homepage]
SELECT
|
===== 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 함수 호출시 전달되는 파일 디스크립터의 정보를 소켓뿐 아니라 파일을 나타내는 경우에도
전달 가능한다.
출처: https://yms2047.tistory.com/entry/select-함수-사용법 [Oh~ Happy!]