네트워크프로그래밍 17

zh025700·2022년 6월 6일
0

네트워크 프로그래밍

17. select보다 나은 epoll

select는 오래된 멀티플렉싱 기법이다.
epoll이 주로 활용된다.

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

  • 모든 파일 디스크립터를 대상으로 하는 반복문
  • select를 호출할 때마다 인자로 매번 전달해야하는 관찰대상에 대한 정보들
select가 호출되면 변화가 있는 파일 디스크립터만 묶이는 것이 아니라 fd_set의 변화를 통해 변화한 파일디스크립터를 구분하기때문에 반복문은 필수적이다.

select 호출시 fd_set에 변화가 생기므로 select를 호출 전에 fd_set의 원본을 복사하고 계속 select 호출 시 새롭게 관찰 대상의 정보를 줘야한다

매번 관찰대상의 정보를 줘야한다??
이는 select 호출 시 정보를 운영체제에 전달한다는 것을 의미한다

운영체제에 정보를 전달하는 것은 오버로드가 크다.

소켓은 운영체제에 의해 관리된다.
select은 소켓의 변화를 관찰하는 함수이다.
그래서 select은 운영체제에 의해 완성되는 함수이다. 매번 정보를 운영체제에 전달해야한다.

=> 운영체제에 관찰대상의 정보를 한번만 알려주는 방법은?? epoll!!

select 장점

  • 대부분의 운영체제에서 지원을 한다.

epoll과 select의 차이점

  • epoll은 현재 관찰되는 파일 디스크립터와 관련 이벤트 정보를 운영체제에 보관하고 추가, 삭제, 수정할 수 있다

  • select

    • 관찰할 파일디스크립터의 목록이 select()를 실행했을 동안만 존재
      • select가 반환하면 목록 정보 없어짐
    • 하나의 이벤트에 대해서만 관찰 가능
  • epoll

    • 관찰할 파일 디스크립터 공간을 생성
      • 지속적이다
    • 여러 이벤트를 관찰 가능하다
      • 다수의 이벤트 중복 가능

epoll 구현에 필요한 함수와 구조체

epoll은??

  • 상태 변화 확인을 위한 반복문이 필요가 없다.
  • 관찰대상의 정보를 매번 전달할 필요가 없다.
epoll_create epoll 파일 디스크립터 저장소 생성
epoll_ctl 저장소에 파일 디스크립터 등록 및 삭제
epoll_wait 파일 디스크립터의 변화를 대기

select에선 fd_set을 직접 선언했다.
하지만 epoll에선 운영체제가 한다. epoll_create로 운영체제에게 요청하면 된다.

select에선 FD_SET, FD_CLR로 파일디스크립터를 추가, 삭제한다.
epoll에선 epoll_ctl로 운영체제에게 요청하는 형식으로 이뤄진다.

select에선 select함수를 호출해 변화를 대기하지만,
epoll에선 epoll_wait을 사용한다.

select에선 fd_set의 변화를 통해 상태변화를 확인하지만,
epoll에선 epll_event 구조체를 기반으로 상태변화가(이벤트) 발생한 파일디스크립터가 별도로 묶인다.

struct epoll_event{
    __uint32_t events;
    epoll_data_t data;
}

이 구조체를 배열로 만들어 epoll_wait에 인자로 전달하면, 상태변화가 발생한 파일 디스크립터의 정보가 배열에 별도로 묶여, 반복문이 불필요

epoll_create

int epoll_create(int size);
  • 성공 시 epoll 파일디스크립터, 실패시 -1 반환
  • epoll 인스턴스를 생성한다.
    • 파일 디스크립터의 저장소
    • 운영체제에 의해 관리
  • 반환된 파일디스크립터는 epoll 인스턴스를 구분하기 위해 사용됨.
  • close로 epoll_create로 인해 생긴 파일디스크립터를 닫아야한다.

epoll_ctl

epoll 인스턴스 생성 후 관찰대상이 되는 파일 디스크립터를 등록해야함.

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
  • 성공시 0 실패시 -1 반환
  • epfd: 관찰대상을 등록한 epoll 인스턴스의 파일 디스크립터
  • op: 관찰대상의 추가, 삭제, 변경여부
  • fd: 등록할 대상의 파일 디스크립터
  • event: 관찰대상의 관찰이벤트 유형

EX로 알아보기

epoll_ctl(A,EPOLL_CTL_AOD,B,C)

  • 두번째 인자는 추가를 의미
  • epoll 인스턴스 A에 파일디스크립터 B를 등록, C를 통해 전달된 이벤트의 관찰 목적으로 등록

epoll_ctl(A,EPOLL_CTL_DEL,B,NULL)

  • 두번째 인자는 삭제를 의미
  • epoll 인스턴스 A에서 파일 디스크립터 B를 삭제
EPOLL_CTL_ADD 등록
EPOLL_CTL_DEL 삭제
EPOLL_CTL_MOD 이벤트 발생상황 변경

epoll_event 구조체는 이벤트가 발생한 파일 디스크립터를 묶는 용도로 사용된다.
또한, 파일디스크립터를 epoll 인스턴스에 등록할 때, 이벤트의 유형을 등록하는 용도로도 사용

struct epoll_event event;

...
event.evetns = EPOLLIN;
event.data.fd= sockfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event);

epfd(epoll 인스턴스)에 sockfd등록, 수신 데이터 존재 시 이벤트 발생하게 등록

이벤트 유형

  • EPOLLIN
    • 수신할 데이터가 존재
  • EPOLLOUT
    • 출력버퍼가 비워져 당장 데이터를 전송할 수 있는 상황
  • EPOLLPRI
    • OOB 데이터 수신
  • EPOLLRDHUP
    • 연결이 종료 or half-close가 된 상황, 엣지 트리거에서 유용
  • EPOLLERR
    • 에러가 발생
  • EPOLLET
    • 이벤트 감지를 엣지 트리거 방식으로 동작
  • EPOLLONESHOT
    • 이벤트가 한번 감지되면 더이상 이벤트 발생 X
      • epoll_ctl로 이벤트를 재설정해야한다.

이벤트들은 or연산자를 통해 둘 이상을 함께 등록 가능하다

epoll_wait

보통 epoll 관련 함수 중 마지막으로 호출 됨

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
  • 성공 시 이벤트가 발생한 파일 디스크립터의 수, 실패시 -1
  • epfd: 관찰영역인 epoll 인스턴스의 파일 디스크립터
  • evnets: 이벤트가 발생한 파일 디스크리ㅂ터가 채워질 버퍼의 주소 값
  • maxevents: 두번째 주소값 버퍼에 등록 가능한 최대 이벤트 수
  • timeout: 대기시간

두번째 인자로 전달되는 주소값의 버프를 동적으로 할당!

해당 함수 호출 후엔 이벤트가 발생한 fd의 수가 반환, 두번째 인자에 fd가 별도로 묶이기 때문에 select와 달리 전체 fd반복문 필요 없다.

epoll 기반의 서버

  1. 서버 소켓을 event fd에 등록 후 epoll_ctl로 epoll 인스턴스에 등록
  2. epoll_wait을 통해 변화가 있는 파일 디스크립터 잡음
    1. 서버면 새로 클라이언트 소켓을 등록
    2. 클라이언트면 데이터를 읽음
      1. 만약 EOF가 전달 된다면 epoll 인스턴스에서 삭제 후 소켓 close

레벨 트리거, 엣지 트리거

레벨 트리거와 엣지 트리거의 차이

이벤트가 발생하는 시점에 있다.

  • 레벨트리거는 입력버퍼에 데이터가 남아있는 동안에 계속해서 이벤트가 등록 됨
  • 엣지트리거는 입력버퍼로 데이터가 수신된 상황에서 딱 한번만 이벤트가 등록 됨

select 모델은 레벨트리거 방식으로 동작

레벨트리거를 엣지 트리거로

설정해야하는 것이 2가지 존재

  1. errno를 이용한 오류의 원인을 확인

    • Non-blocking 모드일때 입력이 완료된것을 알아야할 필요가 있다

    • 데이터가 수신되면 딱 한번 이벤트가 등록된다

      • 이벤트가 발생하면 버퍼에 데이터를 모두 읽어야한다

        • 입력버퍼가 비었는지 확인하는 과정이 필요하다
        read는 더이상 읽을 데이터가 없을 때 -1을 반환하고 errno 값은 EAGAIN이 저장되면 더이상 읽을 데이터가 없는 상황이다
  2. Non-blocking IO를 위한 소켓의 특성을 변경

    • 에지트리거일때 이벤트는 한번만 일어난다
      • data는 읽기전에 충분히 축적되어야 한다 => 서버를 오래 멈출 수 있다
        • 이를 막기 위해 non blocking 모드로 만들어야한다
          • 입출력과 작업이 병렬적으로 이루어져야한다
          int flag = fcntl(fd,F_GETFL,0);
          fcntl(fd,F_SETFL,flag|O_NONBLOCK);

레벨 트리거 vs 엣지 트리거

엣지트리거 방식을 사용하면

  • 데이터의 수신과 데이터가 처리되는 시점을 분리할 수 있다
    • 각 이벤트는 1번씩만 일어나기 때문이다
      • ex - 수신오면 수신 1번 송신오면 송신1번

ex

  • 서버는 클라이언트 A B C로부터 데이터를 수신한다
  • 서버는 수시한 데이터를 A B C 순으로 조합한다
  • 조합한 데이터는 임의의 호스트에 전달한다
서버에서, 관리해야하는 데이터의 양이 커지면 엣지트리거가 성능이 좋다
왜?? 이벤트를 구분할 수 있으므로

만약 관리해야하는 데이터가 간단하고 다양하지 않다면 레벨 트리거가 성능이 더 좋다
profile
정리

0개의 댓글