클라이언트가 send/write로 data를 보내면 서버 kernel의 socket 버퍼에 data가 쌓이고, 이후 서버 프로세스에게 read 이벤트를 보내 소켓 버퍼의 데이터를 읽을 수 있음을 알린다.
이후 서버 프로세스는 recv/read를 통해 해당 data를 kernel 버퍼에서 유저 프로세스 버퍼로 복사해온다.
커널이 fd를 감시할 때 필요한 함수들(select, poll, epoll, kqueue, iocp)이 존재하고 해당 함수를 통해 I/O 멀티플렉싱 모델 구현이 진행된다.
해당 포스팅에선 select()에 대해 소개한다.
입출력 함수(read/write/recv/send)를 호출했을 경우 데이터를 커널 버퍼에서 유저 프로세스 버퍼로 옮겨오는 과정에 대한 비동기인 AIO 는 본 포스팅에서 다루지 않는다.
select()
fd_set 이라는 fd 비트 배열에 등록하고 다음과 같은 이벤트가 발생했을 경우 확인하는 방식
수신, 전송, 예외에 따라서 구분해서 모아야 한다.
#include<sys/select.h>#include<sys/time.h>intselect(
int maxfd, // 검사 대상이 되는 fd 수
fd_set* readset, // 수신 데이터 감지 fd_set
fd_set* writeset, // 블로킹 없고 데이터 전송 가능 감지 fd_set
fd_set* exceptset, // 예외 상황 발생 여부 감지 fd_setconststruct timeval* timeout); // 무한정 blocking 되지 않도록 timeout 설정
fd에 변화가 생기지 않으면 무한정 blocking 되는데, 이를 막기 위해 마지막 인자로 넘긴 시간마다 select()가 반환하게 한다. (반환 값이 0이면 타임아웃)
이벤트 종류
- 수신 이벤트 : 소켓이 수신한 데이터를 지니고 있음
- 전송 이벤트 : 소켓이 blocking 되지 않으면서 데이터의 전송이 가능한 상황이 됨
- 예외 이벤트 : 소켓이 예외 상황이 발생함
fd 관리 및 fd_set 구조체
fd 하나당 0과 1의 상태로 표현하는 fd_set 구조체가 존재한다.
프로그래머가 직접 fd_set에 비트 값을 넣을 필요는 없고, 매크로 함수 활용하면 된다.
FD_ZERO(fd_set* fdset) // 0으로 초기화FD_SET(int fd, fdset* fdset) // fd 등록FD_CLR(int fd, fdset* fdset) // fd 삭제FD_ISSET(int fd, fdset* fdset) // 해당 fd가 등록되어 있다면 참 반환
FD_ISSET은 select()의 호출 결과를 확인하는 용도로 사용된다.
select()가 이벤트 감지 시, 서버 socket이면 새로운 client 등록, 아니면 해당 socket에 대한 이벤트 처리를 해주면 된다.