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 옵션을 줘야된다.

댓글 없음:

댓글 쓰기