네트워크 프로그래밍
12. IO 멀티플렉싱
IO 멀티플렉싱 기반의 서버
다중 접속 서버를 어떻게 구현하는가
멀티프로세스 서버의 단점과 대안
- 새로운 프로세스를 생성할 때 마다 많은 자원이 소요된다.
=> IO멀티플렉싱 서버를 쓰자!
TCP
서버의 단점
- 클라이언트가 figget에서 차단되고, 읽을때 데이터가 누락될 수 있음
- 데이터를 보내고 받는 것은 독립적이어야 한다.
멀티플렉싱이란?
하나
의 통신채널을 통해 둘 이상의 데이터를 전송하는 기술
최소
한의 요소만 사용해 최대
한의 데이터를 전달하는 기술
마치 실 전화기 3개를 연결할 때 하나의 라인당 연결된 컵을 쓰지않고 하나의 실로 3개의 컵을 연결하는 것과 같다
멀티프로세스에선 여러개의 프로세스가 클라이언트와 통신했다. 그럼 하나의 프로세스로 여러개의 클라이언트들과 어떻게 통신할까?
select함수와 서버 구현
- select 함수를 사용하면 멀티플렉싱 서버를 구현할 수 있다.
select 함수 기능, 호출 순서
select 함수로 관찰할 수 있는 항목
- 수신한 데이터를 지니는 소켓이 존재하는가?
- 블로킹되지 않고 데이터의 전송이 가능한 소켓은 뭔가?
- 예외상황이 발생한 소켓은 뭔가?
기능
- 커널에 여러 이벤트 중 하나가 발생할 때까지 대기하도록 지시
- 특정 이벤트가 실행되었을 때, 특정 시간이 지났을 때 프로세스를 깨운다
호출 순서
- 파일디스크립터 설정, 검사 범위 지정, 타임 아웃 설정
- select 호출
- 호출 결과 확인
복잡하다..
파일 디스크립터 설정
select를 사용하면 여러개의 파일 디스크립터(소켓)를 동시에 관찰 할 수 있다.
=> 관찰하고자 하는 디스크립터를 항목에 따라 정해야한다.
fd_set을 통해 관찰하고자 하는 디스크립터를 정한다
fd_set
- 0과 1로 이루어진 비트 배열
- 비트가 1이면
관찰의 대상임
을 뜻함
매크로 함수
- FD_ZERO(fd_set* fdset)
- 인자로 전달된 fd_set의 모든 비트를 0으로 초기화
- FD_SET(int fd,fd_set* fdset)
- fdset에 해당 fd를 등록, 관찰 대상을 추가
- FD_CLR(int fd, fd_set* fdset)
- FD_ISSET(int fd, fd_set* fdset)
- fdset에서 fd가 있다면 양수를 반환
- select의 호출 결과를 확인하는 용도로도 사용
관찰 범위지정과 타임아웃의 설정
select함수
int select(int maxfd,
fd_set* readset,
fd_set* writeset,
fd_set* exceptset,
const struct timeval* timeout);
- 타입아웃 시 0, 관심 파일디스크립터에 변화시 변화된 파일디스크립터 수 값, 실패시 -1 반환
- maxfd: 검사 대상이 되는 파일디스크립터의 수
- readset: fd_set에 수신된 데이터 존재여부에 관심있는 디스크립터 정보를 등록해 변수의 주소값 전달
- writeset: 블로킹 없는 데이터 전송 가능여부에 관심있는 디스크립터를 등록해 변수의 주소값 전달
- exceptset: 예외상황의 발생여부에 관심 있는 디스크립터 정보를 등록해 주소값 전달
- timeout: 무한 블로킹에 빠지지 않도록 설정
readset, writeset, exceptset에 다루고싶은 디스크립터를 명시한다!!
그럼 select의 관찰 범위, 타임아웃은 어떻게 정할까?
관찰범위
소켓이 하나씩 생성될 수록 디스크립터가 늘어나니 제일 큰 디스크립터 값에서 +1 해주면 된다!(0부터 시작이니)
타임아웃 시간(timeval)
struct timeval{
long tv_sec; //초
long tv_usec //마이크로초
}
- select은 원래 관찰 디스크립터에 변화가 생겨야 반환
- 관찰 디스크립터에 변화가 안생긴다면??? 무한 블로킹 상태에 빠짐
블로킹을 막기 위해 타임아웃을 지정
- timeval 구조체 값을 설정 후 select에 구조체 주소값을 넣어주면 된다.
- 만약 타임아웃으로 select가 반환이 된다면 반환값으로 반환의 원인 확인 가능
- 타임아웃 설정하지 않으려면 변수 값에 NULL을 넣으면 됨
- 즉시 체크를 하려면 timeval 값에 0을 설정
timeval 값 초기화에 대해
select함수가 실행 되고 난 후, timeval의 값은 초기화 되지 않는다.(흐른대로 남아있는다)
그래서 select을 호출하기 전에 매번 timeval을 초기화해야한다.
select 함수 호출 이후의 결과 확인
0이 아닌 양수가 반환이 되면 그 수만큼 파일디스크립터의 변화가 생겼다는 것을 뜻함
- select 함수에서 관찰 대상 파일 디스크립터에 해당 관심과 관련된 변화가 생겼다는 것을 의미
select가 실행되면 변화가 있었던 파일디스크립터 말고 다른 디스크립터는 0으로 초기화된다.
- 그래서 보통 원본 유지를 위해 복제할 fd_set을 둬서 작업을 한다.
- 이를 통해 변화가 있었던 파일 디스크립터를 확인할 수 있다
- 변화가 없으면 fd_set에서 값이 0으로 바뀌니 변화 여부를 확인 가능하다
멀티플렉싱 서버
- 서버 소켓은 항상 변화를 감지해야한다
- 클라이언트의 연결 요청을 받아야 함
- read set에 항상 있어야 한다
- 클라이언트가 연결이 되면, 클라이언트 소켓은 클라이언트 배열에 들어간다
- readset에 추가를 한다
- 클라이언트가 데이터를 보내면 select이 값을 반환한다
- 매크로 함수를 통해 어떤 클라이언트인지 확인 후 데이터를 읽는다
- 클라이언트와 연결 종료가 되면, readset에서 값을 0으로 변경한다
단, 어떠한 파일 디스크립터에 변화가 있는지 확인을 위해
전체 파일디스크립터를 반복을 통해, 어떤 디스크립터에 변화가 있었는지 확인한다