2012년 4월 12일 목요일

bind() 소켓에 IP주소와 포트번호 지정


설명
bind() 함수는 소켓에 IP주소와 포트번호를 지정해 줍니다. 이로서 소켓을 통신에 사용할 수 있도록 준비가 됩니다.
헤더#include <sys/types.h>
#include <sys/socket.h>
형태int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen);
인수
int sockfd: 소켓 디스크립터 
struct sockaddr *myaddr: 주소 정보로 인터넷을 이용하는 AF_INET인지 시스템 내에서 통신하는 AF_UNIX에 따라서 달라집니다.
인터넷을 통해 통신하는 AF_INET인 경우에는 struct sockaddr_in을 사용합니다.
struct sockaddr_in {
   sa_family_t  sin_family;       /* Address family  */
   unsigned short int sin_port        /* Port number   */
   struct in_addr sin_addr;         /* Internet address  */
   
   /* Pad to size of ´struct sockaddr'. */
   unsigned char  __pad[__SOCK_SIZE__ - sizeof(short int) -
      sizeof(unsigned short int) - sizeof(struct in_addr)];
};
시스템 내부 통신인 AF_UNIX인 경우에는 struct sockaddr을 사용합니다.
struct sockaddr {
   sa_family_t  sa_family;   /* address family, AF_xxx */
   char        sa_data[14];  /* 14 bytes of protocol address */
};
socklen_t addrlen: myadd 구조체의 크기
반환
0: 성공
-1: 실패
TCP/IP 소켓 프로그램을 작성해 보겠습니다. 본 포스트는 동영상을 먼저 보신 후, 본문 내용을 보시면 이해하시기 편합니다.
 이 포스트에는 동영상이 준비되어 있습니다. 동영상을 보시려면 를 클릭하여 주십시오.
TCP/IP 통신 함수 사용 순서
TCP/IP 예제 소개
  TCP/IP 예제를 서버와 클라이언트로 나누어서 설명을 드리도록 하겠습니다.
  1. 서버와 클라이언트는 port 4000번을 사용
  2. 클라이언트프로그램에서 서버에 접속하면 실행할 때 입력받은 문자열을 전송
  3. 서버는 클라이언트로부터 자료를 수신하면 문자열 길이와 함께 수신한 문자열을 클라이언트로 전송
서버 프로그램
  서버 프로그램에서 사용해야할 함수와 순서는 아래와 같습니다.
? 우선 socket 부터 만들어야 합니다. TCP/IP에서는 SOCK_STREAM을 UDP/IP에서는 SOCK_DGRAM을 사용하는 것을 참고하여 주십시오. socket()에 대한 더 자세한 말씀은 "Unix C Reference의 11장 7절 소켓 열고 닫기"를 참고하십시오.
int     server_socket;
server_socket = socket( PF_INET, SOCK_STREAM, 0);
if (-1 == server_socket)
{
   printf( "server socket 생성 실패");
   exit( 1) ;
}
  bind() 함수를 이용하여 socket에 server socket 에 필요한 정보를 할당하고 커널에 등록

  1. 만들어진 server_socket 은 단지 socket 디스크립터일 뿐입니다.
  2. 이 socket에 주소를 할당하고 port 번호를 할당해서 커널에 등록해야 합니다.
  3. 커널에 등록해야 다른 시스템과 통신할 수 있는 상태가 됩니다.
  4. 더 정확히 말씀드린다면 커널이 socket 을 이용하여 외부로부터의 자료를 수신할 수 있게 됩니다.
  5. socket에 주소와 port 를 할당하기 위해 sockaddr_in 구조체를 이용합니다.
    struct sockaddr_in server_addr;
    memset( &server_addr, 0, sizeof( server_addr);
    server_addr.sin_family      = PF_INET;              // IPv4 인터넷 프로토롤
    server_addr.sin_port        = htons( 4000);         // 사용할 port 번호는 4000
    server_addr.sin_addr.s_addr = htonl( INADDR_ANY);   // 32bit IPV4 주소

    if( -1 == bindserver_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
    {
       printf( "bind() 실행 에러\n");
       exit( 1);
    }
  6. htonlINADDR_ANY) 는 주소를 지정해 주는 것으로 inet_addr( "내 시스템의 IP ")로도 지정할 수 있습니다. 그러나 프로그램이 실행되는 시스템 마다 IP 가 다를 것이므로 주소 지정을 고정 IP로 하지 않고 htonlINADDR_ANY) 를 사용하는 것이 편리합니다.
? 이제 listen() 함수로 클라이언트 접속 요청을 확인합니다. 

if( -1 == listen( server_socket, 5))
{
    printf( "대기상태 모드 설정 실패\n");
    exit( 1);
}
  1. listen() 함수를 호출하면 클라이언트의 접속 요청이 올 때 까지 대기 상태가 됩니다. 즉, 블록된 모습이 되죠.
  2. 함수가 리턴이 되었을 때에는 클라이언트의 접속이 요청 되었다든지, 아니면 에러가 발생했을 경우입니다.
  3. 에러 없이 함수가 복귀했다면 클라이언트의 접속 요청입니다.
  4. 접속 요청을 허락합니다.
? 클라이언트 접속 요청에 따라 accept()로 접속을 허락합니다. 

  1. accept()로 접속 요청을 허락하게 되면 클라이언트와 통신을 하기 위해서 커널이 자동으로 소켓을 생성합니다.
  2. 이 소켓을 client socket이라고 하겠습니다.
  3. client socket 정보를 구하기 위해 변수를 선언합니다.  그리고 client 주소 크기를 대입합니다.
    int     client_addr_size;
    client_addr_size = sizeof( client_addr);
  4. accept()를 호출 후에 에러가 없으면 커널이 생성한 client socket 을 반환해 줍니다.
    client_socket = accept( server_socket, (struct sockaddr*)&client_addr,
                                                              &client_addr_size);
    if ( -1 == client_socket)
    {
       printf( "클라이언트 연결 수락 실패\n");
       exit( 1);
    }
? 이제 client socket까지 만들어 졌으므로 read(), write() 함수를 이용하여 자료를 송수신 할 수 있습니다. read() 함수를 이용하여 클라이언트로부터 전송되어 오는 자료를 읽어 들입니다.
read ( client_socketbuff_rcv, BUFF_SIZE);
  1. read() 를 이용하여 클라이언트로부터 전송된 자료를 읽어 들입니다.
  2. 만일 클라이언트로부터 전송된 자료가 없다면 송신할 때 까지 대기하게 됩니다. 즉, 블록된 모습이 됩니다.
? 이번에는 wirte() 함수를 이용하여 클라이언트도 데이터를 전송합니다. 

  1. 수신된 데이터의 길이를 구하여 전송 데이터를 준비합니다.
    sprintf( buff_snd, "%d : %s", strlen( buff_rcv), buff_rcv);
  2. write() 를 이용하여 클라이언트로 자료를 송신합니다.
    write( client_socketbuff_snd, strlen( buff_snd)+1); // +1: NULL까지 포함해서 전송
? 작업이 완료되면 close() 를 이용하여 client socket 을 소멸 시켜 데이터 통신을 종료합니다.
closeclient_socket);
클라이언트 프로그램
  클라이언트 프로그램은 서버에 비해 간단합니다. 바로 설명 들어갑니다.
  socket() 을 이용하여 소켓을 먼저 생성합니다.
int     client_socket;
client_socket = socket( PF_INET, SOCK_STREAM, 0);
if( -1 == client_socket)
{
   printf( "socket 생성 실패\n");
   exit( 1);
}
  connect()를 이용하여 서버로 접속을 시도합니다. 

  1. 주소 정보에 서버의 주소와 포트번호를 지정하고
  2. 서버와의 연결을 시도합니다.
  3. 예제에서는 시스템 자기를 가르키는 IP, 127.0.0.1 을 사용했습니다.
    struct sockaddr_in    server_addr;
    memset( &server_addr, 0, sizeof( server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons( 4000);
    server_addr.sin_addr.s_addr= inet_addr( "127.0.0.1");  // 서버의 주소
    if( -1 == connect( client_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
    {
       printf( "접속 실패\n");
       exit( 1);
    }
  1. 접속에 성공하면 데이터를 전송합니다.
    writeclient_socket, argv[1], strlen( argv[1])+1); // +1: NULL까지 포함해서 전송
  2. 자료를 수신하고 화면에 출력합니다.
    read ( client_socket, buff, BUFF_SIZE);
    printf( "%s\n", buff);
  3. socket 을 소멸하여 통신 작업을 완료합니다.
    closeclient_socket);
서버 프로그램 소스
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define  BUFF_SIZE   1024

int   main( void)
{
   int   server_socket;
   int   client_socket;
   int   client_addr_size;

   struct sockaddr_in   server_addr;
   struct sockaddr_in   client_addr;

   char   buff_rcv[BUFF_SIZE+5];
   char   buff_snd[BUFF_SIZE+5];



   server_socket  = socket( PF_INET, SOCK_STREAM, 0);
   if( -1 == server_socket)
   {
      printf( "server socket 생성 실패\n");
      exit( 1);
   }

   memset( &server_addr, 0, sizeof( server_addr));
   server_addr.sin_family     = AF_INET;
   server_addr.sin_port       = htons( 4000);
   server_addr.sin_addr.s_addr= htonl( INADDR_ANY);

   if( -1 == bind( server_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
   {
      printf( "bind() 실행 에러\n");
      exit( 1);
   }

   while( 1)
   {
      if( -1 == listen(server_socket, 5))
      {
         printf( "대기상태 모드 설정 실패\n");
         exit( 1);
      }

      client_addr_size  = sizeof( client_addr);
      client_socket     = accept( server_socket, (struct sockaddr*)&client_addr, &client_addr_size);

      if ( -1 == client_socket)
      {
         printf( "클라이언트 연결 수락 실패\n");
         exit( 1);
      }

      read ( client_socket, buff_rcv, BUFF_SIZE);
      printf( "receive: %s\n", buff_rcv);
      
      sprintf( buff_snd, "%d : %s", strlen( buff_rcv), buff_rcv);
      write( client_socket, buff_snd, strlen( buff_snd)+1);          // +1: NULL까지 포함해서 전송
      close( client_socket);
   }
}
클라이언트 프로그램 소스
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define  BUFF_SIZE   1024

int   main( int argc, char **argv)
{
   int   client_socket;

   struct sockaddr_in   server_addr;

   char   buff[BUFF_SIZE+5];

   client_socket  = socket( PF_INET, SOCK_STREAM, 0);
   if( -1 == client_socket)
   {
      printf( "socket 생성 실패\n");
      exit( 1);
   }

   memset( &server_addr, 0, sizeof( server_addr));
   server_addr.sin_family     = AF_INET;
   server_addr.sin_port       = htons( 4000);
   server_addr.sin_addr.s_addr= inet_addr( "127.0.0.1");

   if( -1 == connect( client_socket, (struct sockaddr*)&server_addr, sizeof( server_addr) ) )
   {
      printf( "접속 실패\n");
      exit( 1);
   }
   write( client_socket, argv[1], strlen( argv[1])+1);      // +1: NULL까지 포함해서 전송
   read ( client_socket, buff, BUFF_SIZE);
   printf( "%s\n", buff);
   close( client_socket);
   
   return 0;
}
]$ gcc server.c -o server    // 서버 프로그램을 server 이름으로 컴파일
]$ gcc client.c -o client    // 클라이언트 프로그램을 client 이름으로 컴파일
]$ ./server &                // 서브 프로그램을 백그라운드로 실행
[1] 25869
]$ ./client test_string      // 클라이언트를 문자열을 입력하여 실행
receive: test_string
11 : test_string
]$ ./client forum.falinux.com
receive: forum.falinux.com
17 : forum.falinux.com
]$



출처 URL : http://forum.falinux.com/zbxe/?document_srl=430926

2012년 4월 5일 목요일

epoll 사용


Event POLL #

epoll 패치 #

epoll은 event poll의 줄임말이다. epoll은 사용법이 어렵지 않으면서 괜찮은 성능이 나오는게 강점이다. 사용은 poll과 비슷하다.
솔라리스의 /dev/poll을 리눅스로 포팅한것인데.. 초기의 패치는 성능이 생각만큼 안나왔었다.
그래서 코드를 리눅스에맞게 새로짜기 시작했다. 그래서 나온것이 epoll이다.

일단 epoll을 사용할려면 libepoll이라는 라이브러리가 필요하다. epoll은 3가지 시스템 콜이 있는데 유저모드에선 이 시스템콜을 바로 호출 못하기때문에 유저모드에서 사용가능한 libevent를 제공하고있다.

리눅스 커널 2.6을 사용한다면 epoll이 그냥 지원되고 2.4를 쓴다면 http://www.xmailserver.org/linux-patches/nio-improve.html여기서 패치를 받아서 커널을 다시 컴파일해야된다. 커널 컴파일이 끝났으면 mknod /dev/epoll c 10 124 를 수행해서 /dev/epoll을 생성해줘야된다.
이 작업이 끝났다면 libevent를 설치하면된다.

epoll 사용 #

이제 epoll을 사용해보자. 우선 libepoll에서 제공하는 함수로는 epoll_create, epoll_ctl, epoll_wait이 있다.
그럼 각 함수들의 사용법을 알아보자.

          int eventpoll;
          ...
          if((eventpoll=epoll_create(MAXSOCKET))<0)
          {
              epoll device를 열수없음.
          }
          
epoll을 사용하려면 먼저 epoll_create함수로 epoll device를 열어줘야된다.

          struct epoll_event event;
          ...
          event.events=EPOLLIN;
          event.data.fd=server_socket;
          if(epoll_ctl(eventpoll,EPOLL_CTL_ADD,server_socket,&event)<0)
          {
              에러
          }
          
epoll device를 열었다면 epoll_ctl로 관심있는 이벤트를 등록해줘야된다. poll로 친다면 pollfd의 events에 해당이벤트를 기록하는거라고 볼수있다. 여기서 주목할것은 epoll_event의 data부분이다.

          typedef union epoll_data
          {
            void *ptr;
            int fd;
            uint32_t u32;
            uint64_t u64;
          } epoll_data_t;
          
          struct epoll_event
          {
            uint32_t events;      /* Epoll events */
            epoll_data_t data;    /* User data variable */
          };
          
이것은 epoll.h의 일부분이다. data부분을 보면 pollfd와는 달리 fd뿐만 아니라 32,64비트의 unsigned interger자료형 또는 void의 포인터를 담을수있다. void형의 포인터이기 때문에 어떤 자료라도 담을수있기 때문에 이것을 이용하면 코딩이 한결 편해진다. 이벤트가 생기면 해당 ptr이 바로 넘어오기 때문에 fd뿐만 아니라 해당 fd와 연관된 각종 정보를 바로 알수있다.

          struct epoll_event ev[MAXSOCKET];
          int nready,n;
          ...
          while(1)
          {
              nready=epoll_wait(eventpoll,ev,MAXSOCKET,-1);
              for(n=0;n<nready;n++)
              {
                  if(ev[n].data.fd==serv_sock)
                  {
                      accept처리
                  } else {
                      이벤트처리
                  }
              }
          }
          
이벤트를 등록했으면 이제 epoll_wait로 기다리면 된다. 이벤트가 발생하면 이벤트가 발생한 갯수와 발생한 소켓들이 ev로 모두 넘어온다. 따라서 for문으로 차례대로 처리해주면된다. poll과 같이 모든 소켓에서 검사할 필요가 없다.

아래는 예제로 만들어본 epoll을 사용한 echo서버 프로그램이다. 최대한 쉽고 간단하게 만들어봤다.
          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          #include <sys/socket.h>
          #include <sys/epoll.h>
          #include <arpa/inet.h>
          #include <unistd.h>
          #include <errno.h>
          #include <fcntl.h>
          
          #define MAXPENDING 5
          #define BUFSIZE 256
          #define MAXSOCKET 10000
          
          typedef struct
          {
              int fd;
              char buffer[BUFSIZE];
          } epoll_data;
          
          int eventpoll;
          struct epoll_event serv_event,ev[MAXSOCKET];
          int serv_sock,clnt_sock;
          int connection=0;
          
          void die_with_error(char *string)
          {
              perror(string);
              exit(1);
          }
          
          int main(int argc, char *argv[])
          {
              int nready,n;
              ssize_t nread;
              socklen_t clnt_len;
              epoll_data *data;
              struct sockaddr_in serv_addr;
              struct sockaddr_in clnt_addr;
              unsigned short serv_port;
              
              if(argc!=2)
              {
                  fprintf(stderr,"Usage: %s <Server Port>\n",argv[0]);
                  exit(1);
              }
              serv_port=atoi(argv[1]);
              
              serv_sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
              
              memset(&serv_addr,0,sizeof(serv_addr));
              serv_addr.sin_family=AF_INET;
              serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
              serv_addr.sin_port=htons(serv_port);
              
              if(bind(serv_sock,(struct sockaddr *)&serv_addr,sizeof(serv_addr))<0)
                  die_with_error("bind() error");
              
              if((eventpoll=epoll_create(MAXSOCKET))<0)
                  die_with_error("epoll_create error");
              
              if(listen(serv_sock,MAXPENDING)<0)
                  die_with_error("listen() error");
              
              if(fcntl(serv_sock,F_SETFL,O_NONBLOCK)<0)
                  die_with_error("set non-blocking error");
              
              serv_event.events=EPOLLIN;
              serv_event.data.fd=serv_sock;
              if(epoll_ctl(eventpoll,EPOLL_CTL_ADD,serv_sock,&serv_event)<0)
                  die_with_error("epoll_ctl error");
          
              for(;;)
              {
                  nready=epoll_wait(eventpoll,ev,MAXSOCKET,-1);
                  if(nready<0)
                      die_with_error("epoll_wait error");
                  for(n=0;n<nready;n++)
                  {
                      if(ev[n].data.fd==serv_sock)
                      {
                          struct epoll_event event;
                          clnt_len=sizeof(clnt_addr);
                          if((clnt_sock=accept(serv_sock,(struct sockaddr *)&clnt_addr,&clnt_len))<0)
                              die_with_error("accept() failed");
                      
                          if(fcntl(clnt_sock,F_SETFL,O_NONBLOCK)<0)
                              die_with_error("set non-blocking error");
                      
                          data=(epoll_data*)malloc(sizeof(epoll_data));
                          event.events=EPOLLIN;
                          data->fd=clnt_sock;
                          memset(data->buffer,0,sizeof(data->buffer));
                          event.data.ptr=data;
                          if(epoll_ctl(eventpoll,EPOLL_CTL_ADD,clnt_sock,&event)<0)
                              die_with_error("epoll_ctl error");
                      
                          printf("client[%d] connected (Address: %s)\n",clnt_sock-4,inet_ntoa(clnt_addr.sin_addr));
                      } else {
                          data=ev[n].data.ptr;
                          if((nread=recv(data->fd,data->buffer,sizeof(data->buffer),0))<0)
                          {
                              if (errno==ECONNRESET)
                              {
                                  printf("client[%d] aborted connection\n",data->fd-4);
                                  if(epoll_ctl(eventpoll,EPOLL_CTL_DEL,data->fd,ev)<0)
                                      die_with_error("epoll_ctl error");
                                  close(data->fd);
                              } else
                                  die_with_error("read error");
                          } else if(nread==0) {
                              printf("client[%d] closed connection\n",data->fd-4);
                              if(epoll_ctl(eventpoll,EPOLL_CTL_DEL,data->fd,ev)<0)
                                  die_with_error("epoll_ctl error");
                              close(data->fd);
                          } else {
                              if(send(data->fd,data->buffer,strlen(data->buffer),0)!=strlen(data->buffer))
                                  die_with_error("send() error");
                          }
                      }
                  }
              }
          }
          
위 코드를 컴파일할때 libevent를 사용하므로 -lepoll 옵션을 줘야된다.

2012년 4월 4일 수요일

/dev/epoll에 대한 연구 위키


3 epoll #
3.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에게 이벤트를 받은  해당 디스크립터를 감시하는것을 멈추라고 말하는것이다그러므로 이벤트 발생후 파일디스크립터를 다시 장전(?)  책임은 호출자(혹은 프로그래머?) 에게 있다.

3.3 epoll API #
앞장에서 복잡하게프로세스를 설명했지만 프로그래머 입장에서는 단지 3개정도의 관련 함수만 알면 어렵지 않게 epoll응용 어플의 제작이 가능하다.

epoll_create(int size)
epoll_create() 이벤트를 저장하기 위한 size만큼의 공간을 커널에 요청한다커널에 요청한다고 해서 반드시 size만큼의 공간이 확보되는  아니지만 커널이 대략 어느 정도의 공간을 만들어야 할지는 정해줄  있다수행된  파일 지정자 되돌려 주는데  모든 관련작업은 리턴된 파일 지정자 통해서 이루어지게 된다모든 작업이 끝났다면 close() 호출해서 닫아주어야 한다.

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

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

이벤트가 발생했다면 발생한 이벤트의 갯수를 리턴한다.

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
이벤트풀을 제어하기 위해서 사용한다poll(2) 매우 비슷하게 작동한다op fd 대해서 어떤 작업을 할것인지를 정의하기 위해서 사용된다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 트리거가 기본설정 된다.

6 예제 프로그램 #
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