소켓프로그래밍#11 : select보다 맛있는 epoll

kkado·2022년 6월 5일
0

네트워크프로그래밍

목록 보기
13/16
post-thumbnail

이전에 한 소켓에서 여러 클라이언트를 감시할 수 있는 select 함수에 대해 알아보았다.

select 함수의 단점

select 함수는 커널 등에 의해 완성되는 기능이아니라 순수 함수에 의해 동작하기 때문에, select 함수 호출에 전달된 정보는 운영체제에 등록되지 않는다.
그래서 select 함수를 호출할 때마다 매번 관련 정보를 전달해야 한다.

운영체제에 관찰 대상에 대한 정보를 알려주고, 관찰 대상의 범위, 내용에 변경이 있을 때 변경 사항만 알려줄 수 있으면 좋겠다.

리눅스의 epoll이 그러한 기능을 할 수 있다.

epoll 함수의 장점

  • 상태변화의 확인을 위한 전체 파일 디스크립터를 대상으로 하는 반복문이 필요 없다.
  • select 함수에 대응하는 epoll_wait 함수 호출 시 관찰 대상 정보를 매번 전달할 필요가 없다.

상태 변화 관찰에 더 나은 방법을 제공한다.

epoll의 구현

  • epoll_create : epoll 파일 디스크립터 저장소 생성
  • epoll_ctl : 저장소에 파일 디스크립터 등록 및 삭제
  • epoll_wait : select 함수와 마찬가지로 파일 디스크립터 변화 대기
struct epoll_event
{
	__uint32_t events;
    epoll_data_t data;
}

	typedef union epoll_data
    {
    	void *ptr;
        int fd;
        __uint32_t u32;
        __uint32_t u64;
    } epoll_data_t; 

소켓 디스크립터 등록 및 이벤트 발생 확인에 사용되는 구조체

epoll_create

#include <sys/epoll.h>

int epoll_create(int size);

운영체제가 관리하는, epoll 인스턴스 라고 불리는 파일 디스크립터 저장소를 생성한다.
size 파라미터에는 epoll 인스턴스의 크기 정보가 들어간다.

epoll_ctl

#include <sys/epoll.h>

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

생성된 epoll 인스턴스에 관찰대상을 저장 및 삭제한다.
두 번째 전달 인자에 따라 등록, 삭제 및 변경 등이 이뤄진다. 옵션은 다음과 같이 넣어준다.

  • EPOLL_CTL_ADD : 파일 디스크립터를 epoll 인스턴스에 등록
  • EPOLL_CTL_DEL : 파일 디스크립터를 epoll 인스턴스에서 삭제
  • EPOLL_CTL_MOD : 등록된 파일 디스크립터 이벤트 발생 상황을 변경

예시

  • epoll_ctl(A, EPOLL_CTL_ADD, B, C); : epoll 인스턴스 A에 파일 디스크립터 B를 등록한다. 이 때 C 이벤트 관찰을 목적으로 한다.
  • epoll_ctl(A, EPOLL_CTL_DEL, B, NULL); : epoll 인스턴스 A에서 파일 디스크립터 B 삭제
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);

이벤트의 종류

  • EPOLLIN : 수신할 데이터가 존재하는 상황
  • EPOLLOUT : 출력 버퍼가 비워져 당장 데이터를 전송할 수 있는 상황
  • EPOLLPRI : OOB 데이터가 수신된 상황
  • EPOLLRDHUP : 연결이 종료되거나 Half-close가 진행된 상황 (이는 엣지 트리거 방식에서 유용하게 사용할 수 있다)
  • EPOLLERR : 에러가 발생
  • EPOLLET : 이벤트의 감지를 엣지 트리거 방식으로 동작시킴
  • EPOLLONESHOT : 이벤트가 한번 감지되면 해당 파일 디스크립터에선 더이상 이벤트를 발생시키지 않는다.

epoll_wait

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • epfd : epoll 인스턴스의 파일 디스크립터
  • events : 이벤트가 발생한 파일 디스크립터가 채워질 버퍼의 주소값
  • maxevents : 두번째 인자로 전달된 주소값의 버퍼에 등록 가능한 최대 이벤트 수
  • timeout : 대기 시간. -1 전달 시 이벤트 발생 시까지 무한 대기

epoll 기반의 에코 서버

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);
    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, BUFSIZE);
            if(str_len == 0)
            {
            	epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                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);
            }
        }
    }
}

연결 요청의 경우 수락 및 디스크립터 등록의 과정을 거친다.
종료 요청의 경우 디스크립터 해제의 과정을 거친다.

profile
울면안돼 쫄면안돼 냉면됩니다

0개의 댓글