프로세스
생성은 상당히 많은 대가를 지불해야 하는 연산과정을 거치며, 그만큼 생성 후에도
시스템의 자원을 많이 차지하게 된다. 또한 모든 프로세스들은 서로 독립적인 메모리
공간을 할당 받아서 사용하기 때문에 프로세스간 통신을 하기 위해서는 다소 복잡한
방법을 선택 할 수밖에 없다.(IPC를 복잡한 방법이라고 말하고 있다. 실제로 서로
통신을 해야 하는 프로세스의 수가 많아지면 프로그래밍 하기가 까다로워 진다.)
"멀티플렉싱"이란 여러 개를 묶어서 하나로 만드는 것"이다. 하나로 만들었다는 것은
"그 하나를 공유한다"라는 의미가 되므로 같은 뜻이 된다.
결론적으로,
멀티플렉싱 서버는 연결된 클라이언트 대 프로세스의 비율이 다 대 일 이
된다. 이러한 경우 하나의 프로세스는 입,출력을 담당하게된다. 하지만 클라이언트의 연결 요청의
처리까지도 하나로 묶어버린다.
12-2. select 함수 사용하기
select 함수를 사용하게 되면, 한 곳에 모아놓은 여러 개의 파일
디스크립터를 동시에 관찰할 수 있다. 수신할 데이터를 지니고 있는 파일 디스크립터가
어떤 것들인지, 데이터를 전송할 경우 블로킹되지 않고 바로 전달 가능한 파일
디스크립터는 어떤 것들인지, 그리고 예외가 발생한 파일 디스크립터는 어떤 것들인지 정도가
관찰 내용이 된다.
1) select 함수의 기능과 호출 순서 select
함수를 호출하기 전에 다음의 세가지 준비 단계가 필요하다
첫 번째로
'디스크립터 설정'이다. : select 함수를 사용하게 되면, 여러 개의 파일 디스크립터를
동시에 관찰할 수 있다고 하였다. 파일 디스크립터를 관찰할 수 있다는 말은
결국 소켓을 관찰할 수 있다는 말과 같다.
관찰할 수 있는
항목은 다음과 같다. 수신할 데이터를 지니고 있는 소켓이 존재하는가? 데이터를 전송할
경우 블로킹되지 않는 소켓은 무엇인가? 예외 상황이 발생한 소켓이 있는가? 이렇게
관찰 항목이 셋이기 때문에 세 묶음으로 파일 디스크립터를 준비해 둬야 한다.
파일 디스크립터를 세 묶음으로 모아놓기 위해서 사용되는 것이 fd_set 데이터
타입의 자료형이다. fd_set은 0과 1을 나타내는 비트들의 배열이라고 생각하면 된다.
1로 설정된 비트가 관찰 대상이 되는 파일 디스크립터를 의미한다.
두
번째로 '검사 범위 설정'이다. : select 함수는 열ㅓ 파일 디스크립터를 관찰하고,
그 결과를 전달해 준다. 그렇다면 여러 개의 디스크립터를 확인해야 하는데 이왕이면
확인해야 하는 범위를 설정해 주면, 보다 효율적으로 수핼할 수 있을 것이다.
세 번째로 '타임아웃(timeout) 설정'이다 : 타임아웃을 설정한다는 것은 블록킹 상태를
빠져 나가기 위한 시간 설정을 의미한다.
select 함수는 호출했을 때
관찰 대상들에게서 변화(관찰 항목에 부합되는 소켓의 변화를 의미한다)가 발생해야 리턴하며, 그렇지
않으면 변화가 발생될 때까지 무한정 블로킹 상태에 있게 된다. 그러나 타임아웃을
설정해 놓으면 관찰 대상들에게서 변화가 없더라도 설정 시간이 지나면 무조건 리턴한다.
따라서 무한 대기 상태에 빠지는 것을 피할 수 있다.
다음은
select 함수의 선언이다.
* n :
검사 대상이 되는 파일 디스크립터의 수 * readfds : "입력 스트림에
변화가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달한다. ㅇ기서 입력 스트립에 변화가
발생했다는 것은 수신할 데이터가 있다는 뜻이다. * writefds : "데이터 전송
시, 블로킹되지 않고 바로 전송이 가능한지" 확인하고자 하는 소켓들의 정보를 전달한다. * exceptfds : "예외가 발생했는지" 확인하고자 하는 소켓들의 정보를 전달한다. *
timeout : 함수 호출 후, 무한 대기 상태에 빠지지 않도록 타임-아웃(time-out)을
설정하기 위한 인자를 전달한다. * 리턴 값 : -1이 리턴되는 경우,
오류 발생을 의미한다. 또한 0이 리턴된 경우에는 타임아웃에 의해 리턴되었음을 의미한다.
마지막으로 리턴된 값이 0보다 큰 경우는 변경된 파일 디스크립터의 수를 의미한다.
2) 파일 디스크립터 설정 하기 fd_set 변수를 조작하는데 필요한 몇
가지 매크로 함수이다. ========================================================== 함수 선언
| 기능 ========================================================== FD_ZERO(fd_set * fdset); | fdset 포인터가 가리키는
변수의 모든 비트들을 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
함수는 리턴한다. 파일 디스크립터를 통해 수신할 데이터가 존재하는가?(readfds 포인터가 가리키는 변수에
설정되어 있는 파일 디스크립터 중에서...) : 수신할 데이터가 존재한다는 것은 비교적
쉽게 이해할 수 있다. 일단 입단 입력 버퍼로 데이터가 수신되어서 읽어
들일 데이터가 존재하는 경우라고 생각할 수 있다. 여기서 주의할 것이 한
가지 있다. 소켓을 통해서 들어오는 클라이언트의 연결 요청 또한 수신할 데이터가
존재하는 경우에 해당한다. 연결 요청도 일종의 데이터 전송에 해당되고, 이를 수신하는
것도 데이터를 수신하는 것이기 때문이다.
파일 디스크립터를 통해서 데이터를 전송할
경우 블로킹되지는 않는가?(writefds 포인터가 가리키는 변수에 설정되어 있는 파일 디스크립터 중에서...)
: write 함수와 같은 출력 함수가 블로킹되는 경우는, 출력 버퍼에 아직
전송하지 못한 데이터가 많이 남앙 씨어서, 데이터 전송을 바로 할 수
없는 경우에 발생한다. 따라서 출력 버퍼가 여유 있는 경우에는 블로킹되지 않고,
데이터를 전송할 수 있는 경우가 된다.
파일 디스크립터가 가리키는 소켓에서
예외가 발생하였는가?(exceptfds 포인터가 가리키는 변수에 설정되어 있는 파일 디스크립터 중에서...) :
예외 상황 이라고 해서 여러분이 생각하는 그런 아주 일반적인 예외 상황을
말하는 것이 아니다. TCP 기반의 Out-of-band data 가 수신되었을 경우를 의미하는
것이다.
3) 파일 디스크립터 범위 설정하기 select 함수는 여러 파일
디스크립터를 검사하고, 그 결과를 전달해 준다고 하였다. 그렇다면 select 함수는 여러
개의 파일 디스크립터를 확인해야 하는데, 이왕이면 확인해야 하는 파일 디스크립터의 범위를
제한해 주면, 보다 효율적으로 수행할 수 있을 것이다.
그래서 select
함수의 첫 번째 인자로 검사해야 하는 총 디스크립터의 개 수를 넘겨주게
된다. 그러나 일반적으로 디스크립터는 생성될 때마다 값이 1씩 증가하기 때문에 가장
큰 파일 디스크립터 값에 1을 더해서 인자로 전달하면 된다.
1을
더하는 이유는 디스크립터 값이 0부터 시박하기 때문이다. 따라서 인자로 n이라는 값을
념겨 주게 되면, select 함수는 검사하게 되는 파일 디스크립터의 범위를 0부터
n-1로 설정된다. 따라서 반드시 1을 더해줘야 한다.
4) 타임아웃(time out)
설정하기 다음은 타임아웃을 설정하기 위한 timeval 구조체를 보여 주고 있다.
* tv_sec는 초 단위 설정을 위한 변수이고, tv_usec는
마이크로 초 단위 설정을 위한 변수이다.
5) select 함수
호출 이후 결과 확인 select 함수가 리턴되고 나서 무엇보다도 중요한 것은
결과를 얻는 것이다. 일단 함수 호출이 정상적으로 리턴했다는 것은 파일 디스크립터에
변화가 있었거나, 아니면 타임아웃이 발생했거나 둘 중에 하나이다.
리턴 값이
-1인 경우는 오류발생을 의미한다. 또한 0이 리턴 된 경우에는 타임아웃에 의해
리턴되었음을 의미한다. 즉 0이 리턴된 경우 파일 디스크립터에 아무런 변화도 발생하지
않았다는 의미가 된다.
그러나 리턴된 값이 0보다 큰 경우에는, 변화가
발생한 파일 디스크립터의 수를 의미하게 된다. 예를 들어 수신할 데이터가 존재하는
파일 디스크립터가 두 개 발생했다면, 2가 리턴될 것이다.