티스토리 뷰

서버가 여러 클라이언트를 처리하기 위한 방법 중 입출력 다중화를 사용하는 방법을 알아보자.

입출력 다중화란 여러 입출력 대상이 있을 때 그 대상들을 검사하며 입출력이 필요할 때마다 작업을 수행하는 것을 말한다. 이 방법은 멀티 프로세싱, 멀티 스레딩 방법과 다르게 싱글 프로세스, 싱글 스레드에서도 동작한다. 하지만 완전히 동시에 처리하지는 못하는 단점을 가지고 있다.






select() 함수

리눅스는 select() 함수로 입출력 다중화를 구현할 수 있다. select()는 지정한 범위의 file discripter중 FD_SET으로 1로 set되어있는 파일을 순회하면서 변화가 있는 file discripter를 반환한다.

파일에 변화가 있다면 그대로 1로 두고, 변화가 없다면 0으로 set

멀티 플랙싱이 가능하게 한다.

* 멀티 플렉싱 : 하나의 프로세스로 여러 클라이언트와 통신하는 방법


file discripter

- select()를 이해하기 위해서는 리눅스에서 파일을 관리하는 방법을 알아야 한다. 리눅스는 파일을 file discripter로 관리하며, 리눅스 시스템에서 파일을 열면 해당 파일에 대한 정보를 담은 구조체를 가리키는 포인터들이 table 형태로 저장되는데 이 table의 색인 번호가 바로 file discripter다.

- 리눅스는 소켓도 파일로 다루기 때문에 편리하게 사용할 수 있다.

- 리눅스에서 0, 1, 2번 파일은 기본설정으로 지정되어있으며 임의의 파일들은 3부터 fd를 부여받는다. 소켓도 파일로 다루기 때문에 소켓을 생성하면 3 이상의 fd(file discripter)를 부여 받는다.


fd set

크기가 1024인 '비트 필드' 이며 0과 1값을 가진다.


int select(int n,    // 검사할 파일 디스크립터 번호 중 가장 큰 값+1 (배열인자로 접근하기 때문에)

fd_set* readfds,    // 읽기를 검사할 fd_set

fd_set* writefds,    // 쓰기를 검사할 fd_set

fd_set* exceptfds,    // 예외를 검사할 fd_set

const struct timeval* timeout);    // 검사하는 시간에 제한을 둔다. NULL이면 무한정 대기한다.

// return값 : 0 <= 변경이 생긴 파일의 수, -1 = 실패


select()의 핵심은 fd_set을 제어하는 것이다. fd_set은 파일 디스크립터 번호를 배열로 가지는 '비트 필드'구조체이다. select()는 fd_set을 순회하며, fd_set값이 1인 파일에 대해 읽기 또는 쓰기를 검사하고 변화가 생긴 fd_set을 1로 한다. ( 없다면 당연히 0으로 한다. )

fd_set을 제어하는 매크로 함수는 다음과 같다.


 FD_ZERO(*fds)

 fd_set을 초기화한다. 모든 값을 0으로 한다.

 FD_SET(fdnum, *fds)

 fdnum(파일 번호)을 fd_set에서 1로 한다. select()는 이 파일을 검사한다.

 FD_ISSET(fdnum, *fds)

 fdnum이 fd_set에서 1인지 검사한다.

 FD_CLR(fdnum, *fds)

 fd_set에서 fdnum을 제거한다. 더이상 관리할 필요가 없을 때 사용한다.



이제 select()로 소켓들의 입출력을 관리하는 방법을 알아보자.

먼저 7개의 소켓을 만들었고, 각 소켓은 3~9까지 fdnum을 부여받았다고 가정하자. 그럼 fd_set의 모습은 다음과 같을 것이다.


fd_set* readfds

 fdnum

 0

 1

 2

 3

 4

 5

 6

 7

 8

 9

 bit

 0 0 0 0 0 0 0 0 0 0



이제 3,5,8 소켓에 대해 읽기를 검사하기 위해 3, 5, 8번을 등록한다.


FD_SET(3, &readfds);

FD_SET(5, &readfds);

FD_SET(8, &readfds);


fd_set의 모습은 다음처럼 된다.


fd_set* readfds

 fdnum

 0

 1

 2

 3

 4

 5

 6

 7

 8

 9

 bit

 0 0 0 1 0 1 0 0 1 0


select()로 fd_set을 검사한다.

nfds가 9인 이유는 검사할 비트가 배열 번호상 9번비트 까지이기 대문이다. 


select(9, &readfds, (fd_set*)0, (fd_set*)0, NULL);


예를들어 3번과 5번에서 변화가 생겼다면 fd_set의 모습은 다음처럼 변한다.


fd_set* readfds

 fdnum

 0

 1

 2

 3

 4

 5

 6

 7

 8

 9

 bit

 0 0 0 1 0 1 0 0 0 0

* fd_set은 이전 상태를 유지하지 않으므로 다시 또 3, 5, 8번 소켓을 검사하고 싶다면 다시 FD_SET으로 등록시키고 사용해야 한다. 


select로 모든 소켓 중에 뭔가 변화가 있는 소켓이 있는지 알아냈으니 이제 FD_ISSET으로 어떤 소켓에 변화가 있는지 검사를 할 수 있다


FD_SET(int fd, fd_set* fdset) // fdsetfd번째 인자를 1set (fd번 소켓을 1로 올린다는 소리)

FD_ISSET(int fd, fd_set* fdset) // fdsetfd번째 인자 값을 return


위 예에서 3번과 5번 소켓에 변화가 있었으니

read_fds의 fd_arrya는 [0][0][0][1][0][1][0] ...

위처럼 구성이 되어있을 것이다


그러므로 

FD_ISSET(3, &read_fds) 와 FD_ISSET(5, &read_fds)의 값은 1이 될 것이고

반복문을 돌면서 소켓 0번부터 최대 소켓번호 n 까지 순회하며 read_fds를 검사하면 어떤 소켓이 데이터를 수신 받았는지 알 수 있다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함