728x90

멀티플렉싱 서버

- 하나의 프로세스로 여러 클라이언트에 서비스를 하는 서버

- 서버의 리스닝 소켓을 bind, listen하고 클라이언트 쪽에서 connect가 들어오면 accept하여 클라이언트 소켓을 반환하고 그 소켓을 통해 클라이언트와 데이터 송수신

- 멀티 프로세스 서버에서는 connect마다 새로 프로세스를 만들어 하나의 프로세스마다 하나의 소켓을 관리

- 하지만 멀티 플렉싱 서버에서는 하나의 프로세스가 서버 소켓 + 여러 클라이언트 소켓을 관리함

- 관리할 때 fd_set이라는 구조체를 사용

 

fd_set

- fd를 관리하기 위해 디자인 된 구조체

- 배열 형태로 0번 인덱스부터 fd 0을 매핑하고 있음

- fd_set 관련 함수

    -> FD_ZERO : 인자로 전달된 fd_set의 모든 비트를 0으로 초기화

    -> FD_SET : 인자로 전달된 fd_set의 인덱스를 1로 설정

    -> FD_CLR : 인자로 전달된 fd_set의 인덱스를 0로 설정

    -> FD_ISSET : 인자로 전달된 fd_set의 해당 인덱스가 1이면 양수를 반환

 

select 함수

- 어느 소켓의 fd에 read, write, exception이 발생했는지 확인하는 함수

- fd_set을 전달하여 호출하면 변화가 발생한(입력 받은 데이터가 존대하거나 출력이 가능한 상황 등) 소켓의 디스크립터만 1로 설정

- fd_set에 대한 주소값을 전달하고 각 액션에 대한 결과를 적용하기 때문에 원본을 복사하여 복사본을 전달해야 함

#include <sys/select.h>
#include <sys/time.h>

int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);

struct timeval
{
    long tv_sec;		// 초
    long tv_usec;		// 밀리초
}

 

- 위와 같은 예시에서 readset에 fd_set을 전달했을 때 호출 후 아래와 같이 변했다면 fd1, fd3에 읽어들일 데이터가 있다(입력 버퍼에 데이터가 있다)라고 볼 수 있음

 

select를 이용한 멀티플렉싱 서버의 구현

- 1단계 : 서버 소켓과 fd_set 생성

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    struct timeval timeout;
    fd_set reads, cpy_reads;
    
    socklen_t adr_sz;
    int fd_max, str_len, fd_num, i;
    ...;
    
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    ...;
    
    if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1) {
        printf("bind() error");
    }
    if(listen(serv_sock, 5)==-1) {
        printf("listen() error");
    }
    
    FD_ZERO(&reads);		// fd_set 초기화
    FD_SET(serv_sock, &reads);	// 서버 소켓을 관리 대상으로 지정
    fd_max=serv_sock;           // 최대 파일 디스크립터 값

- 2단계 : select 함수 호출

    while(1)
    {
        cpy_reads=reads;			// 원본 fd_set 복사
        timeout.tv_sec=5;
        timeout.tv_usec=5000;		// 타임아웃 설정
        
        if((fd_num=select(fd_max+1, &cpy_reads, 0, 0, &timeout))==-1) {
            break;
        } // 아직 서버 소켓만 있으므로 connect 연결 요청 시 서버소켓에 데이터가 들어오게 됨
        
        if(fd_num==0) {
            continue;
        } // 타임 아웃 시 continue

- 3단계 : 소켓에 따른 구분

    -> for문으로 fd_set의 인덱스를 하나씩 순회하면서 변화가 있는 인덱스를 찾아냄

    -> 만약 그 fd가 서버 소켓이면 connect 요청이므로 새로운 소켓을 생성하여 fd_set에 등록

	for(i=0; i<fd_max+1; i++)
        {
            if(FD_ISSET(i, &cpy_reads))
            {
                if(i==serv_sock)     
                {
                    adr_sz=sizeof(clnt_adr);
                    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                    FD_SET(clnt_sock, &reads);
                    if(fd_max<clnt_sock)
                        fd_max=clnt_sock;
                    printf("connected client: %d \n", clnt_sock);
                } // 변화가 일어난 소켓이 서버 소켓이면 connect 요청인 경우
                else 
                {
                    str_len=read(i, buf, BUF_SIZE);
                    if(str_len==0)    // close request!
                    {
                        FD_CLR(i, &reads);
                        close(i);
                        printf("closed client: %d \n", i);
                    }
                    else
                    {
                        write(i, buf, str_len);    // echo!
                    }
                } // 다른 소켓인 경우에는 데이터 read
            }
        }
    }
    close(serv_sock);
    return 0;
}
728x90

+ Recent posts