728x90

select 기반의 IO 멀티 플렉싱이 느린 이유

- 소켓 관리하는 fd_set을 직접 관리해야 함

- select로 fd_set을 전달할 때 원본을 직접 전달하지 않고 복사본을 전달해야 하는 번거로움

- select가 한번 일어날 때 마다 fd_set을 모두 점검하면서 변화가 일어났는지 체크해야 함

- select는 운영체제에 의해서 완성되는 기능이 아니고 함수에 의해 완성되기 때문에 번거롭고 성능이 떨어질 수 있음

- 이 문제를 해결하기 위해선 운영체제에 관찰 대상(소켓)의 정보를 한번만 알려주고 변화가 있을 때 변화가 있는 소켓의 정보들만 받으면 됨

- 위처럼 운영체제 레벨에서 지원하는 멀티 플렉싱 모델이 epoll(리눅스), IOCP(윈도우)

 

select를 사용할 상황

- 서버의 접속자가 많지 않음

- 다양한 운영체제에서 사용할 수 있어야 함

    -> epoll이나 iocp는 운영체제에 종속적임

 

epoll 방식의 장점

- select처럼 모든 소켓에 대해 반복문을 수행하면서 점검할 필요가 없음

    -> 변화가 일어난 소켓의 정보만 전달해서 주기 때문

- select함수에 대응하는 epoll_wait 시 소켓의 정보를 매번 전달할 필요가 없음

 

epoll에 필요한 함수와 구조체

- epoll_event, epoll_data 구조체 : 소켓 디스크립터 등록, 이벤트 발생의 확인에 사용되는 구조체

typedef union epoll_data
{
        void *ptr;
        int fd;                    // 이벤트가 일어난(or 일어날) 파일 디스크립터
        __unit32_t u32;
        __unit64_t u64;
} epoll_data_t;

struct epoll_event
{
        __unit32_t events;        // 관찰할 이벤트의 종류
        epoll_data_t data;
}

- epoll_create : epoll 파일 디스크립터 저장소 생성

#include <sys/epoll.h>

int epoll_create(int size);

    -> 성공 시 epoll 파일 디스크립터, 실패 시 -1 반환

    -> epoll의 시작을 운영체제에 알려주어 OS가 fd를 관리할 저장소를 만들게 함

- epoll_ctl : 저장소에 파일 디스크립터를 등록, 삭제

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

    -> 성공 시 0, 실패 시 -1 반환

    -> epfd : epoll_create로 생성한 epoll 파일 디스크립터

    -> op : 관찰 대상의 추가, 삭제, 변경 여부

// epoll.h

#define EPOLL_CTL_ADD 1        /* Add a file decriptor to the interface.  */
#define EPOLL_CTL_DEL 2        /* Remove a file decriptor from the interface.  */
#define EPOLL_CTL_MOD 3        /* Change file decriptor epoll_event structure.  */

    -> fd : 등록할 파일디스크립터

    -> event : 관찰 대상의 이벤트 유형 

  • EPOLLIN : 수신할 데이터가 존재하는 이벤트
  • EPOLLOUT : 즉시 데이터를 전송할 수 있도록 출력 버퍼가 비워진 이벤트
  • EPOLLPRI : OOB(Out-Of-Band) 데이터가 수신된 이벤트
  • EPOLLRDHUP : 연결이 종료된 이벤트 (half-close 포함)
  • EPOLLET : Edge-trigger 방식으로 이벤트 감지. 이 경우 | 연산자를 이용해 이벤트 종류도 함께 명시
  • EPOLLONESHOT : 최초의 이벤트만 감지하고 이후에는 return하지 않는 방식. | 연산자를 이용해 이벤트 종류를 함께 명시
  • EPOLLERR : 에러가 발생한 이벤트
enum EPOLL_EVENTS
  {
    EPOLLIN = 0x001,
#define EPOLLIN EPOLLIN
    EPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRI
    EPOLLOUT = 0x004,
#define EPOLLOUT EPOLLOUT
    EPOLLRDNORM = 0x040,
#define EPOLLRDNORM EPOLLRDNORM
    EPOLLRDBAND = 0x080,
#define EPOLLRDBAND EPOLLRDBAND
    EPOLLWRNORM = 0x100,
#define EPOLLWRNORM EPOLLWRNORM
    EPOLLWRBAND = 0x200,
#define EPOLLWRBAND EPOLLWRBAND
    EPOLLMSG = 0x400,
#define EPOLLMSG EPOLLMSG
    EPOLLERR = 0x008,
#define EPOLLERR EPOLLERR
    EPOLLHUP = 0x010,
#define EPOLLHUP EPOLLHUP
    EPOLLRDHUP = 0x2000,
#define EPOLLRDHUP EPOLLRDHUP
    EPOLLONESHOT = (1 << 30),
#define EPOLLONESHOT EPOLLONESHOT
    EPOLLET = (1 << 31)
#define EPOLLET EPOLLET
  };

- epoll_wait : select 함수처럼 파일의 디스크립터 변화를 대기

    -> 성공 시 이벤트가 발생한 파일 디스크립터의 수, 실패 시 -1 반환

    -> epfd : epoll 파일 디스크립터

    -> events : 이벤트가 발생한 파일 디스크립터가 채워질 버퍼의 주소 값

    -> maxevent : 최대 이벤트 수

    -> timeout : 대기 시간, -1 전달 시 이벤트 발생 시까지 무한 대기

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
	epfd=epoll_create(EPOLL_SIZE);
	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);

	event.events=EPOLLIN;
	event.data.fd=serv_sock;	
	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

	while(1)
	{
		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        ...;
  	}

 

epoll 기반의 에코 서버

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t adr_sz;
	int str_len, i;
	char buf[BUF_SIZE];

	struct epoll_event *ep_events;
	struct epoll_event event;
	int epfd, event_cnt;
	...;

	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");

	epfd=epoll_create(EPOLL_SIZE);		// epoll 저장소 생성
	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);	// 이벤트 저장할 공간 생성

	event.events=EPOLLIN;
	event.data.fd=serv_sock;	
	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);		// 서버 소켓의 이벤트 등록

	while(1)
	{
		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);		// 이벤트 발생 대기
		if(event_cnt==-1)
		{
			puts("epoll_wait() error");
			break;
		}

		for(i=0; i<event_cnt; i++)
		{
			if(ep_events[i].data.fd==serv_sock)
			{
				adr_sz=sizeof(clnt_adr);
				clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
				event.events=EPOLLIN;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);		
                // 새로운 소켓도 등록
				printf("connected client: %d \n", clnt_sock);
			} // 발생한 소켓이 서버 소켓이면
			else
			{
					str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
					if(str_len==0)    // close request!
					{
						epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);		
                        // 소켓을 epoll 관리 대상에서 삭제
						close(ep_events[i].data.fd);		// 소켓 삭제
						printf("closed client: %d \n", ep_events[i].data.fd);
					}
					else
					{
						write(ep_events[i].data.fd, buf, str_len);    // echo!
					}
	
			}
		}
	}
	close(serv_sock);		// 서버 소켓 소멸
	close(epfd);			// epoll 인스턴스 소멸
	return 0;
}

 

728x90

+ Recent posts