IT_Programming/Network Programming

[펌_Linux] epoll() 함수

JJun ™ 2009. 11. 18. 14:38



출처: http://www.joinc.co.kr/   



 

/dev/epoll에 대한 연구 위키


아직 잘 알려지진 않은 소켓 연결 방식인 이벤트 기반 입출력처리 방식인 epoll에 대한 내용을 담고 있습니다.

epoll의 세부 작동방식에 대한 설명이 부족합니다.

 

Contents

1 /dev/epoll에 대한 연구 위키
2 소개
3 개요
4 epoll
4.1 epoll에 대해서
4.2 Linux에서의 epoll 지원
4.2.1 Kernel 2.6.x 준비
4.2.2 glibc 2.3.2 이상
4.2.3 gcc 3.3.x 이상준비
4.2.4 epoll 지원 테스트
4.2.5 라이브러리 차원에서의 epoll 지원
4.3 epoll API
4.3.1epoll_create(2)
4.3.2epoll_wait(2)
4.3.3epoll_ctl(2)
5 epoll과 poll의 비교
6 epoll의 장점/단점/해결방법
6.1 장점
6.2 단점
7 예제 프로그램
8 프로젝트 진행
9 참고 문서


2 소개

차세대(??) 소켓 연결방식인 epoll에 대해서 연구하도록 하겠습니다.

시스템 콜 방식의 /dev/epoll은 RT Signal 보다 약 10~20%까지 성능향상을 낼수 있다는 것이 연구결과 밝혀졌습니다.(참고 문서 1)

3 개요

epoll 이벤트 통지 방식에 대해서 알아본다. 어떤 장점과 단점을 가지고 있는지를 알아본다. 다른 이벤트 통지 방식중 하나인 RTS와 비교하며, 가능하면 성능 테스트 결과를 찾아본다. epoll을 이용한 간단한 서버/클라이언트 프로그램을 작성 해봄으로써 이해및 활용방법에 대한 기본을 잡도록 한다.

4 epoll

4.1 epoll에 대해서

리눅스는 전통적으로 이벤트 기반의 비동기 통지 방식 보다는 동기적으로 관심있어 하는 파일(소켓)에 읽기/쓰기 이벤트가 발생했는지를 검사하는 입출력 다중화 방식을 주로 사용해왔다. 혹은 여러개의 프로세스를 생성시켜서 다중의 클라이언트를 처리하는 방법을 주로 이용해왔다.

이들 방법은 보통 비용이 매우 많이 소비된다. 입출력 다중화를 위해서 사용하는 select(2), poll(2)은 커널과 유저공간사이에 여러번의 데이터 복사가 있을 뿐 아니라 이벤트가 발생했는지를 확인하기 위해서 넓은 범위의 소켓테이블을 검사해야 했다. select(2)라면 최악의 경우 단 하나의 이벤트가 어느 소켓에서 발생했는지 확인하기 위해서 1024개의 이벤트 테이블을 몽땅 검색해야 하는 비효율을 감수해야 한다.

이러한 문제를 해결하기 위해서 kqueue, RTS, epoll과 같은 이벤트 통지 기반의 입출력 처리 도구가 개발되었다.

epoll은 이름에서 알 수 있듯이 좀 더 빠르고 효율적으로 입출력 이벤트의 처리가 가능하도록 poll(2)을 확장시킨 도구이다. 이러한 성능의 향상은 Edge Trigger(ET)과 Level Trigger(LT) 인터페이스를 채용해서 관심있어 하는 파일을 좀더 효과적으로 관리 할 수 있도록 함으로써 이루어 졌다.

다음과 같은 시나리오를 생각해보자

  1. read sid of pipe(RFD)에 있는 파일 지정자가 epoll 장치에 추가된다.
  2. Pipe write가 2Kb의 데이터를 쓴다.
  3. epoll_wait(2)가 호출되고 RFD는 이벤트가 발생한 파일 지정자를 리턴한다.
  4. Pipe reader은 RFD로 부터 1Kb데이터를 읽어들인다.
  5. epoll_wait(2)가 호출된다.

만약 RFD 파일 지정자가 EPOLLET 플래그를 이용할경우 마지막 단계의 epoll_wait(2)는 영원히 반환되지 않을 것이다. 왜냐하면 위의 단계를 보면 쓴 데이터는 2kb인데 반해 읽어들인 데이터는 1kb임을 알수 있다. 이럴 경우 여전히 파일 입력 버퍼에는 사용할수 있는 데이터가 남아 있게 되고 버퍼를 모두 비우기 전까지는 원격 클라이언트에게 응답(메시지 수신이 끝났다는)을 보내지 않게 된다. 따라서 EPOLLET 플래그를 (Edge Triggerd) 는 반드시 non-blocking 소켓에 사용해야한다.

epoll을 Edge Triggered (EPOLLET) 인터페이스를 사용할때는 다음과 같이 사용하도록 하자

  1. non-block 파일디스크립터를 사용한다.

  2. read(2) 나 write(2) 가 errno로 EGAIN을 반환할때만 wait 을 하도록 하자 (epoll_wait)

반대로 Level Triggerd 인터페이스로 epoll을 사용할 경우, epoll 은 항상 poll(2) 보다 빠르고 poll을 그대로 대체할 수 있다. (사용법이 똑같아서) Egde Triggered 방식의 epoll이 여러 덩어리의 데이터를 받을 수 있는 것처럼 , EPOLLONESHOT 옵션을 설정할 수도 있다. 이것은 epoll에게 이벤트를 받은 후 해당 디스크립터를 감시하는것을 멈추라고 말하는것이다. 그러므로 이벤트 발생후 파일디스크립터를 다시 장전(?) 할 책임은 호출자(혹은 프로그래머?) 에게 있다. FixMe 이것이 select 를 호출할때 매번 FD_SET 을 행하는 것과 같은 뜻으로 이해했는데 맞는지 모르겠습니다.

4.2 Linux에서의 epoll 지원

2.4.x 버전에서는 패치로 지원하며, 2.5.x(정확한 버젼은 모르겠음)부터 정식지원되기 시작되었다. 2.6.x는 당연히 지원한다. 여기에서는 RH9.0 커널버젼 2.6.x를 기준으로 epoll지원에 대해서 설명하도록 한다.

4.2.1 Kernel 2.6.x 준비

기본적으로 epoll지원은 커널에 포함되어 있다.
만약 동작하지 않는 것 같다면 커널 옵션을 살펴보자. General setup > Configure standard kernel features 는 일반적인 환경 (PC등..) 에서는
선택할 필요가 없으나, 
만약 이것이 선택되어 있을 경우는 그 하부메뉴에서 Enable eventpoll support를 선택해주면 된다.
다시 한번 말하지만 Configure standard kernel features가 선택 안되어있다고 해서 그 하부설정이 비활성화 되었다는 뜻이 절대 아니다.

위의 내용은 커널 2.6이 널리 사용되지 않았을 적의 이야기로, 지금은 (2007/02/07 현재) 굳이 체크할 필요도 없을 것이다.
 
4.2.2 glibc 2.3.2 이상
그다음 glibc에서 epoll관련 API를 지원하도록 해야 되는데, glibc2.3.2이상에서 지원하고 있다고 되어 있다. 필자는 RH9.0을 가지고 있다.
RH9.0은 glibc 2.3.2를 채택하고 있으나, 어찌된 일인지 epoll을 지원하지 않고 있었다. 아무래도 최신의 glibc를 받아서 컴파일 하든지 해야 될것 같다.


젠투에서라면 emerge를 이용해서 어렵잖게 epoll지원환경을 구성할 수 있을 것이다.

Fedora 2의 경우 glibc 차원에서 epoll이 지원되고 있음을 확인했다.

이상의 내용이 확인되었다면 kernel소스를 컴파일 하도록 한다. 커널 2.6컴파일에 대한 일반적인 내용은 mz의 gentoo설치기문서를 참고하기 바란다.
물론 여러분이 fedora core 2이상이나 혹은 젠투 최신버전을 사용하고 있을 경우 커널 컴파일을 할 필요는 없을 것이다.
커널 컴파일 하기 전에 지원여부를 먼저 확인해 보기 바란다. 
컴파일 되었다면 부트로더 설치 프로그램을 이용해서 커널을 올리고,
커널 헤더파일을 /usr/include/linux로 링크시키도록 한다(아마도 /usr/src/linux/include/linux로 이미 링크되어 있을 것이다)

 
4.2.3 gcc 3.3.x 이상준비
다음 컴파일러도 3.3.x이상이 준비되어야 한다. 소스를 컴파일하든지.. 패키지를 다운받든지 하도록 하자.

 
4.2.4 epoll 지원 테스트
위의 3가지 사항을 모두 만족시켰다면, nm을 이용해서 epoll관련 함수가 지원되는지 확인 해보도록 하자.
# nm -D /lib/libc.so.6 
... 
000d5c00 W epoll_create 
000d5c00 W epoll_ctl 
000d5c00 W epoll_wait 
... 
 

지금까지의 내용들을 모두 확인했다면 epoll응용을 위한 환경은 갖춘셈이다. 다음의 코드가 컴파일되고 실행된다면, 성공적으로 환경을 설정한 경우가 된다.
#include <stdlib.h>  
#include <sys/epoll.h>  
#include <errno.h>  
#include <string.h>  
int main(int argc,char** argv) 
{  
    int epoll_fd; 
    if((epoll_fd = epoll_create(1024)) ==-1)  
    {  
        fprintf(stderr, "epoll_create error\n"); 
    } 
    else 
    { 
        printf("epoll create success\n"); 
    } 
} 
 
다음은 컴파일 방법이다.
# gcc -o test_poll test_poll.c -levent 
 
컴파일 옵션에서 event를 선택해주어야 한다. 그렇지 않을경우 제대로 컴파일 되지 않을 것이다.
커널 2.6버전을 사용하고 있다면, 라이브러리 링크옵션을 줄필요없이 컴파일 될 것이다.
(커널 2.6.15 에서 테스트)

 
4.2.5 라이브러리 차원에서의 epoll 지원
glibc에서 epoll인터페이스를 지원하도록 하는게 가장 좋은 방법이겠지만 아시다 시피 glibc를 업그레이드 시키는건 상당히 조심해야 하는 작업으로
쉽게 손댈 수 있는 부분이 아니다. 만약 여러분이 gentoo와 같은 리눅스 배포판을 사용한다면 별 어려움 없이 업그레이드가 가능하겠지만 RedHat을 사용할 경우
매우 위함한 작업이 될것이다.


그렇다면 위험한 glibc를 수정하는 대신 epoll인터페이스를 포함하는 라이브러리를 작성하는 방법도 생각할 수 있다.
이미 여러분은 커널에서 epoll시스템콜을 호출할 수 있도록 컴파일 했기 때문에 인터페이스만 가지는 라이브러리를 만들면 된다.
이것은 그리 복잡한 작업은 아니지만 귀찮은 작업은 될 수 있다.


다행히도 이미 epoll관련 유저레벨 라이브러리가 존재한다. epoll lib는 http://seclists.org/lists/linux-kernel/2003/Apr/0134.html 에서 다운받을 수 있다.
epoll-lib를 컴파일 하기 위해서는
mkdep가 필요하다. 이것은 pmake에 포함되어 있으니 배포판의 패키지 시스템을 이용해서 설치한 후 epoll-lib를 컴파일 하도록 하자.

컴파일 설치 방법은 아래와 같이 같단하다.
# make 
# make install 
 
epoll-lib를 다운받으면 example디렉토리에 예제파일이 존재하니, 제대로 컴파일 되는지 확인해 보자.

 

4.3 epoll API

앞장에서 복잡하게? 프로세스를 설명했지만 프로그래머 입장에서는 단지 3개정도의 관련 함수만 알면 어렵지 않게 epoll응용 어플의 제작이 가능하다.
 
4.3.1epoll_create(2)
epoll_create(int size);
epoll_create()는 이벤트를 저장하기 위한 size만큼의 공간을 커널에 요청한다. 커널에 요청한다고 해서 반드시 size만큼의 공간이 확보되는 건 아니지만
커널이 대략 어느 정도의 공간을 만들어야 할지는 정해줄 수 있다. 수행된 후 파일 지정자를 되돌려 주는데, 이 후 모든 관련작업은 리턴된 파일 지정자를 통해서
이루어지게 된다. 모든 작업이 끝났다면
close()를 호출해서 닫아주어야 한다.
 
4.3.2epoll_wait(2)

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 실제 이벤트가 발생하는걸 기다리고 있다가, 이벤트가 발생하면 이벤트 관련 정보를 넘겨주는 일을 한다.

epfdepoll_create(2)를 이용해서 생성된 epoll지정자이다. 만약 이벤트가 발생하면 리턴하게 되는데,
리턴된 이벤트에 관한 정보는 events에 저장된다. maxevents는 epoll이벤트 풀의 크기다.
timeout는 기다리는 시간이다. 0보다 작다면 이벤트가 발생할 때까지 기다리고, 0이면 바로 리턴,
0보다 크면 timeout 밀리세컨드 만큼 기다린다. 만약 timeout시간에 이벤트가 발생하지 않는다면
0을 리턴한다. 이벤트가 발생했다면 발생한 이벤트의 갯수를 리턴한다.

4.3.3epoll_ctl(2)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
이벤트풀을 제어하기 위해서 사용한다. poll(2)와 매우 비슷하게 작동한다.
opfd에 대해서 어떤 작업을 할것인지를 정의하기 위해서 사용된다.
op가 실행된 결과는 event구조체에 적용된다.
다음은 epoll_event구조체의 모습이다.
 
typedef union epoll_data {
     void *ptr;
     int fd;
     __uint32_t u32;
     __uint64_t u64;
} epoll_data_t;

struct epoll_event {
     __uint32_t events;  /* 발생된 이벤트 */
     epoll_data_t data;  /* 유저 데이터로 직접 설정가능하다 */ 
};
epoll_data_t를 유심히 볼필요가 있다. 이것은 유저 데이터가 직접 설정이 가능한데, 여기에서 설정한 값은 epoll_wait를 통해서 넘어오는 epoll_event구조체값으로
그대로 사용할 수 있다. 예를들어 여기에 pid값이라든지 소켓지정번호등을 지정해 놓게되면 나중에 이벤트가 발생했을 때 이벤트가 발생한 파일등에 대한 정보를
쉽게 얻어올 수 잇다.


op는 다음과 같은 종류의 작업명령들을 가지고 있다. poll(2)와 비교해보면 매우 유사함을 알 수 있을 것이다.
  • EPOLL_CTL_ADD
    fd를 epoll 이벤트 풀에 추가하기위해서 사용한다.
  • EPOLL_CTL_DEL
    fd를 epoll 이벤트 풀에서 제거하기 위해서 사용한다.
  • EPOLL_CTL_MOD
    이미 이벤트 풀에 들어 있는 fd에 대해서 event의 멤버값을 변경하기 위해서 사용한다.
  • EPOLLIN
    입력(read)이벤트에 대해서 검사한다.
  • EPOLLOUT
    출력(write)이벤트에 대해서 검사한다.
  • EPOLLERR
    파일지정자에 에러가 발생했는지를 검사한다.
  • EPOLLHUP
    Hang up이 발생했는지 검사한다.
  • EPOLLPRI
    파일지정자에 중요한 데이터가 발생했는지 검사한다.
  • EPOLLET
    파일지정자에 대해서 Edge 트리거 행동을 설정한다. Level 트리거가 기본설정 된다.

5 epoll과 poll의 비교

테스트 관련자료및 코드 : http://www.monkey.org/~provos/libevent/

이 자료는 likesylph님이 테스트 하신 자료 입니다.

Test Server : Pentium 233Mhz, 128MB

Test Library : libepoll

Test Mothod : 클라이언트의 지속적인 접속요청에 대한 응답 시간


소켓수 100개
EPOLL POLL
real 0m0.299s 0m0.240s
user 0m0.020s 0m0.000s
sys 0m0.030s 0m0.080s

소켓수 1000개
EPOLL POLL
real 0m10.336s 0m10.252s
user 0m0.000s 0m0.020s
sys 0m0.140s 0m0.200s

소켓수 3000개
EPOLL POLL
real 0m30.132s 2m30.106s
user 0m0.000s 0m0.010s
sys 0m0.030s 0m0.030s

소켓수 5000개
EPOLL POLL
real 0m50.189s 11m41.188s
user 0m0.000s 0m0.000s
sys 0m0.060s 0m0.060s

소켓수 10000개
EPOLL POLL
real 1m49.375s 테스트 못 함
user 0m0.000s
sys 0m0.150s
 
 
 

6 epoll의 장점/단점/해결방법

6.1 장점

  1. 좀더 적은 자원을 차지하면서 효율은 기존의 기술보다 좋다는 것이 아닐까~~?
  2. /dev/poll등에 비해 충분히 효율적이면서도 RealTime Signal에 비해서 사용하기 쉽다.

6.2 단점

  1. 아직 정식 지원이 안된 상태여서 커널에 포함되어 있지않다.
    • kernel 2.6.x에는 정식으로 포함되어 있습니다.
    • glibc 업데이트를 하거나 epoll-lib를 설치해야 한다.
    • 2.4.x는 패치를 이용하면 됩니다.
  2. 표준 지원사항이 아니라서 다른 유닉스에는 적용시킬 수 없다.

7 예제 프로그램

epoll시스템 구축을 기념삼아서 간단한 예제프로그램을 만들어 보았다. 아래 프로그램은 echo서버의 epoll버젼이다. (돌아가기에 급급한 코드다.)
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <string.h> 
#include <sys/epoll.h> 
#include <arpa/inet.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <fcntl.h> 
#include <sys/ioctl.h> 
#define SA  struct sockaddr 
#define EPOLL_SIZE        20 
int main(int argc, char **argv) 
{ 
    struct sockaddr_in addr, clientaddr; 
    struct eph_comm *conn; 
    int sfd; 
    int cfd; 
    int clilen; 
    int flags = 1; 
    int n, i; 
    int readn; 
    struct epoll_event *events; 
    int efd; 
    char buf_in[256]; 
    // 이벤트 풀의 크기만큼 events구조체를 생성한다. 
    events = (struct epoll_event *)malloc(sizeof(*events) * EPOLL_SIZE); 
    // epoll_create를 이용해서 epoll 지정자를 생성한다.     
    if ((efd = epoll_create(100)) < 0) 
    { 
        perror("epoll_create error"); 
        return 1; 
    } 
    // -------------------------------------- 
    // 듣기 소켓 생성을 위한 일반적인 코드 
    clilen = sizeof(clientaddr); 
    sfd = socket(AF_INET, SOCK_STREAM, 0);     
    if (sfd == -1) 
    { 
        perror("socket error :"); 
        close(sfd); 
        return 1; 
    } 
    addr.sin_family = AF_INET; 
    addr.sin_port = htons(atoi(argv[1])); 
    addr.sin_addr.s_addr = htonl(INADDR_ANY); 
    if (bind (sfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) 
    { 
        close(sfd); 
        return 1; 
    } 
    listen(sfd, 5); 
    // -------------------------------------- 
    // 만들어진 듣기 소켓을 epoll이벤트 풀에 추가한다. 
    // EPOLLIN(read) 이벤트의 발생을 탐지한다. 
    events->events = EPOLLIN; 
    events->data.fd = sfd; 
    epoll_ctl(efd, EPOLL_CTL_ADD, sfd, events); 
    while(1) 
    { 
        // epoll이벤트 풀에서 이벤트가 발생했는지를 검사한다. 
        n = epoll_wait(efd, events, EPOLL_SIZE, -1); 
        if (n == -1 ) 
        { 
            perror("epoll wait error"); 
        } 
        // 만약 이벤트가 발생했다면 발생한 이벤트의 수만큼 
        // 돌면서 데이터를 읽어 들인다.  
        for (i = 0;    i < n; i++) 
        { 
            // 만약 이벤트가 듣기 소켓에서 발생한 거라면 
            // accept를 이용해서 연결 소켓을 생성한다.  
            if (events[i].data.fd == sfd) 
            { 
                printf("Accept\n"); 
                cfd = accept(sfd, (SA *)&clientaddr, &clilen); 
                events->events = EPOLLIN; 
                events->data.fd = cfd; 
                epoll_ctl(efd, EPOLL_CTL_ADD, cfd, events); 
            }
            // 5단계에 이른 상태에서 ET에 의해서 아직 읽을 데이터가 있음을 확인하고 이벤트의 발생으로 인해서
            // epoll_wait(2)가 호출되고 3단계로 넘어가서 다시 데이터를 읽어들인다. 
            // 4단계에 이르러서 데이터를 읽어들이면 더이상 버퍼에 아무런 데이터가 남아 있지 않고 
            // 5단계에서 (lock)이 걸리게 된다. 
            // 연결소켓에서 이벤트가 발생했다면 
            // 데이터를 읽어들인다. 
            else 
            { 
                memset(buf_in, 0x00, 256); 
                readn = read(events[i].data.fd, buf_in, 255); 
                // read에 문제가 생겼다면 epoll이벤트 풀에서  
                // 제거하고 소켓을 닫는다. 
                if (readn <= 0) 
                { 
                    epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, events); 
                    close(events[i].data.fd); 
                    printf("Close fd\n", cfd); 
                } 
                else 
                    printf("read data %s\n", buf_in); 
            } 
        } 
    } 
} 
아래와 같이 컴파일한 후 테스트 해보기 바란다.
# gcc -o epoll epoll_echo.c -lepoll

8 프로젝트 진행

  1. epoll로 echo 서버를 만든뒤 다른 기술과 비교해 성능비교를 하도록 하겠다.
  2. ircu를 보면 select, poll, /dev/poll, /dev/epoll등을 엔진화 시켜서 시스템의 상황에 맞게 엔진을 선택할 수 있도록 만들어져 있다. 이것을 참고해서 개인적으로 구현해보는 것도 괜찮을 것 같다.

9 참고 문서

  1. epoll과 다른 소켓연결방식 비교
  2. yundream RTS 연구 위키
  3. ircu irc서버 epoll, kqueue, poll 범용 코드

--------------------------------------------------------------------------------------------------
 
 
 
[ Kernel 2.6 ]
 
이전의 epoll문서가 Kernel 2.4 기반으로, 2.6이 대중화된 현 시점에서는 너무 오래된 감이 있어서 2.6기반으로 재작성해보기로 했다.
예전 문서는 그대로 남겨두기로 했다. epoll에 대한 개괄적인 내용은 예전문서를 확인해 보기 바란다.

2.4에서 epoll은 정식지원 사항이 아니었지만, 2.6에서는 정식지원이 되는 관계로 별도의 커널패치라든지
라이브러리설치가 필요 없어졌다. epoll을 사용하기 위해선 파일지정자를 nonblock으로 해야 한다.
c&p 하면 되긴 하겠지만 약간 귀찮다.
#include <pthread.h> 
#include <stdio.h> 
#include <sys/timeb.h> 
#include <sys/socket.h> 
#include <sys/types.h> 
#include <sys/wait.h> 
#include <sys/epoll.h> 
#include <netinet/in.h> 
#include <string.h> 
#include <fcntl.h> 
#include <signal.h> 
#include <errno.h> 
#define MAX_CLIENT 101 
#define PORT 3355 
#define DEBUG 
int listenfd; 
// nonblock 소켓생성 
void nonblock(int sockfd) 
{ 
    int opts; 
    opts = fcntl(sockfd, F_GETFL); 
    if(opts < 0) 
    { 
        perror("fcntl(F_GETFL)\n"); 
        exit(1); 
    } 
    opts = (opts | O_NONBLOCK); 
    if(fcntl(sockfd, F_SETFL, opts) < 0)  
    { 
        perror("fcntl(F_SETFL)\n"); 
        exit(1); 
    } 
} 
int main(int argc, char **argv) 
{ 
    int epfd; 
    struct epoll_event *events; 
    struct epoll_event ev; 
    struct sockaddr_in srv; 
    int clifd; 
    int i; 
    int n; 
    int res; 
    char buffer[1024]; 
    if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    { 
        perror("sockfd\n"); 
        exit(1); 
    } 
    bzero(&srv, sizeof(srv)); 
    srv.sin_family = AF_INET; 
    srv.sin_addr.s_addr = INADDR_ANY; 
    srv.sin_port = htons(PORT); 
    if( bind(listenfd, (struct sockaddr *) &srv, sizeof(srv)) < 0) 
    { 
        perror("bind\n"); 
        exit(1); 
    } 
    listen(listenfd, 1024); 
    epfd = epoll_create(MAX_CLIENT); 
    if(!epfd) 
    { 
        perror("epoll_create\n"); 
        exit(1); 
    } 
    ev.events = EPOLLIN | EPOLLERR | EPOLLHUP; 
    ev.data.fd = listenfd; 
    if(epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) < 0) 
    { 
        perror("epoll_ctl, adding listenfd\n"); 
        exit(1); 
    } 
    for( ; ; ) 
    { 
        res = epoll_wait(epfd, events, MAX_CLIENT, 0); 
        for(i = 0; i < res; i++) 
        { 
            if(events[i].data.fd == listenfd) 
            { 
                clifd = accept(listenfd, NULL, NULL); 
                if(clifd > 0) 
                { 
                    nonblock(clifd); 
                    ev.events = EPOLLIN | EPOLLET; 
                    ev.data.fd = clifd; 
                    if(epoll_ctl(epfd, EPOLL_CTL_ADD, clifd, &ev) < 0) 
                    { 
                        perror("epoll_ctl ADD\n"); 
                        exit(1); 
                    } 
                } 
            } 
            else { 
                n = recv(events[i].data.fd, buffer, 1023, 0); 
                if(n == 0) 
                { 
#ifdef DEBUG 
                    printf("%d closed connection\n", events[i].data.fd); 
                    epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL); 
#endif 
                } 
                else if(n < 0) 
                { 
#ifdef DEBUG 
                    printf("%d error occured, errno: %d\n", 
                            events[i].data.fd, errno); 
#endif 
                } 
                else { 
#ifdef DEBUG 
                    printf("%d data received: %s\n",  
                            events[i].data.fd, buffer); 
                    bzero(&buffer, strlen(buffer)); 
#endif 
                    send(events[i].data.fd, buffer, strlen(buffer), 0); 
                } 
            } 
        } 
    } 
    return 0; 
} 
Java1.5에 NIO가 포함되어 있던데, 리눅스에서의 NIO 는 결국 epoll의 구현이였다.
--------------------------------------------------------------------------------------------------