-
서버 프로그램은 일반적으로 동시에 여러 클라이언트에게 서비스를 제공해야 한다.
-
이러한 작업은 멀티 프로세스와 멀티 스레드를 이용하여 처리가 가능하고 독립된 흐름을 만들어서 클라이언트의 요청을 각가 처리한다.
-
하지만 하나의 흐름으로 모든 클라이언트의 요청을 처리하는 방법도 있다. 이 방법을 사용하면 하나의 프로세스가 여러 클라이언트에게 순차적으로 서비스를 제공한다.
-
이러한 서비스 모델은 통신에서 하나의 미디엄에 여러 목적지에 해당하는 정보를 실어 보내는 멀티 플렉싱과 유사해 보이기 때문에 멀티 플렉싱이라는 용어로 불리기도 한다.
멀티 플렉싱
-
TCP 연결이 형성된 이후의 입출력은 결국에 파일 디스크립터를 이용한
write()/read(), send()/recv()
함수와 같은 입출력 함수를 통해서 이루어진다. -
멀티 프로세스 프로그래밍과 멀티 스레드 프로그래밍은 클라이언트와의 입출력을 처리하기 위한 파일 디스크립터를 독립된 실행의 흐름으로 관리했었다.
-
상담 서비스에 비유를 하자면, 고객에게 전화가 올 때마다 한명의 상담원을 할당하여 고객에게 서비스를 제공하는 상황과 비슷하다고 생각할 수 있다.
-
각각의 상담원들은 고객의 요청이 들어올 때마다 그에 상응하는 서비스를 제공한다. 상담원들은 고객의 요청이 없더라도 계속 대기한다.
-
만약 한 명의 상담원이 여러 명의 고객에게 서비스를 제공해야하는 상황이 되면 어떻게 처리할 수 있을까? 고객의 요청이 자동 응답기에 의해서 저장되고 상담원이 자동 응답기를 조회하면서 처리해야하는 일이 발견 될 때마다 일을 한다면 한명의 상담원으로 여러 명의 고객에게 서비스를 할 수 있을 것 이다.
-
통신에서 하나의 통신 채널을 이용하여 여러 쌍의 송수신자 사이의 통신을 가능하게 하는 것을 멀티플렉싱이라고 한다.
SELECT () 함수
-
이미 연결된 소켓에 읽을 데이터가 있는지 없는지를 확인하는 작업이 필요한데 이러한 작업을 위해서
select()
함수를 소개한다. -
select()
함수는 데이터의 유무를 확인할 소켓을 지정하는 역할을 한다. -
즉
select()
함수는 관찰하고자 하는 파일 디스크립터를 등록하는 함수라고 볼 수 있다.
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
인자
-
nfds
: 파일 디스크립터 중에 가장 높은 값 -
readfds
,writefds
,exeptfds
: 파일 디스크립터의 집합으로 (읽기/쓰기/예외)에 관심을 두고 모니터링할 파일 디스크립터를 지정할 때 사용한다. -
timeout
:select()
함수의 호출은 지정한 관찰 결과가 발생할 때까지 프로그램을 블로킹 상태로 만든다. -
timeout
은 계속해서 블록킹 상태로 대기하는 것을 방지하기 위해서 블로킹 상태로 유지되는 시간의 상한을 정하는 것이다. -
select()
함수는 여러 개의 파일 디스크립터를timeout
시간만큼 모니터링 한다. 모니터링 하던 중에 읽기/쓰기/예외와 같은 입출력 동작이 블로킹 되지 않고 수행할 수 있는 상황이 발생하면 양수값을 반환하면서 함수를 종료한다. -
특정 조건이 발생할 때까지 파일 디스크립터들을 모니터링을 하면서 호출한 프로세스를 대기 상태로 만든다.
-
관찰할 파일 디스크립터는
fd_set
이라는 변수형으로 묶을 수 있다.fd_set
은 관찰 대상이 되는 파일 디스크립터를 비트 단위로 저장한 자료형이다. -
fd_set
의 값을 설정하려면 비트 단위 연산을 해야하기 때문에 매크로의 도움 없이 값을 설정하고 읽는 작업은 복잡한 작업이 된다. 따라서 아래와 같은 매크로가 정의되어 있으며 이 매크로들을 사용하여fd_set
자료형의 값을 제어할 수 있다.
매크로 | 설명 |
---|---|
FD_ZERO(fd_set *fdset) | fdset 변수의 모든 비트를 0으로 설정 |
FD_SET(int fd, fd_set *fdset) | fdset 변수 중 fd에 해당하는 비트를 1로 설정 |
FD_CLR(int fd, fd_set *fdset) | fdset 변수 중 fd에 해당하는 비트를 0으로 설정 |
FD_ISSET(int fd, fd_set *fdset) | fdset 변수 중 fd에 해당하는 비트가 1이면 양수를 반환한다. |
-
select
함수는 파일 디스크립터 셋을 모니터링 하는 중에 해당 입출력 이벤트가 발생하면 해당fd_set
변수에서 이벤트가 발생하지 않은 파일 디스크립터 비트를 0으로 셋팅 한 후에 양의 정수를 반환하면서 종료한다. -
따라서 종료 이후에
fd_set()
매크로를 사용하여 어떤 파일 디스크립터가 준비된 것인지를 알아낼 수 있다. -
또한
select()
함수의 관찰 시간은timeval
이라는 구조체를 이용하여select()
함수에 전달된다.
struct timeval {
long tv_sec; // 초
long tv_usec; // 마이크로 초
}
- 만약
timeval
변수를 사용하여 정해진 시간 동안 모니터링하고 있는 파일 디스크립터 셋에 변화가 발생하지 않으면select()
함수는 0을 반환한다. 또한,timeval()
변수 자리에NULL
인자를 사용하면 시간을 무한대로 설정할 수 있다.
SELECT() 함수의 활용
-
멀티 프로세스 / 스레드를 활용한 일반적인 서버 프로그램의 호출은 다음과 같다.
-
socket()
호출 -
bind()
호출 -
listen()
호출 -
accept()
호출 후에 프로세스 또는 스레드를 생성하여 각자의 입출력을 처리 -
select()
함수를 이용하여 만들 서버 프로그램은 단일 프로세스 프로그램이기 때문에,accept()
호출 이후 생성되는 연결 소켓의 파일 디스크립터를 받는 일과 연결 소켓을 이용하여 입출력을 처리하는 루틴이 있어야 한다. 따라서 아래와 같은 흐름을 가진다.
- socket 호출 (듣기 소켓 생성)
- bind 호출
- listen 호출
관찰 대상이 되는 디스크립터 셋 형성
- 루프 시작
- 듣기 소켓을 관찰하여 읽을 데이터가 있을 경우 accept를 호출한다.
- accept 호출로 전송 소켓 디스킙터를 관찰 대상에 등록한다.
- 전송 소켓을 관찰하여 읽을 데이터가 있을 경우 입출력을 한다.
- 루프 끝
예제
-
에코 서버 프로그램을 작성하면서
SELECT()
의 사용 방법을 알아 볼 것이다. -
서버 프로그램은 클라이언트의 요청을 받아들여 TCP 연결을 형성한다.
-
서버 프로그램은 연결 이후 클라이언트가 전송하는 문자열을 다시 클라이언트로 재전송 한다.
-
서버 프로그램은 멀티 플렉싱을 사용하여 다수의 클라이언트를 처리한다.
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <errno.h>
void error_proc(const char*);
int main(int argc, char *argv[]) {
int listen_sd, connect_sd;
struct sockaddr_in server_addr, client_addr;
int client_addr_len, read_len, str_len;
char read_buffer[BUFSIZ];
int max_fd = 0;
fd_set default_fds, read_fds;
int res, i;
if (argc != 2) {
printf("usage: %s [port number]\n", argv[0]);
return -1;
}
printf("server start...\n");
listen_sd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (listen_sd == -1) error_proc("socket");
memset(&server_addr, 0, sizeof(server_addr));
31,0-1 10%
} else { // 듣기 소켓 외의 다른 소켓이 읽기 준비 상태가 되면 IO를 수정
read_len = read(i, read_buffer, sizeof(read_buffer) -1);
if (read_len == 0) {
fprintf(stderr, "a client is disconnected... \n");
FD_CLR(i, &default_fds);
close(i);
continue;
}
read_buffer[read_len] = '\0';
printf("client (%d): %s\n", i - 3, read_buffer);
write(i, read_buffer, strlen(read_buffer));
}
}
}
}
close(listen_sd);
return 0;
}
void error_proc(const char *str) {
fprintf(stderr, "error: %s\n", str);
exit(1);
}
-
select()
함수는 소켓 프로래밍에만 특화되어 있는 함수가 아니라 파일 디스크립터를 관리하는 방법을 제공하는 함수로 볼 수 있다. -
여러 개의 파일 디스크립터 상태 변화를 모니터링하여 적절하게 입출력 작업을 할 때 유용하게 사용할 수 있다.
참고 문헌
>> Home