epoll 서버 샘플 프로그래밍

프로그래밍/서버프로그래밍 2007/04/18 10:19

epoll 서버 샘플 프로그래밍  by xevious7   http://www.xevious7.com/52
(문서의 작성일. 2006.5월9일)
(문서의 수정및 추가 2007년 4월)
(문서의 추가 : 참초할만한 다른 좋은 소스들 2008년 3월)

거의 1년여전에 이 문서를 작성해놓고 실수로 공개를 안하고 있었습니다.
계속 비공개문서로 있었던 것이죠. 아마도 무슨 바쁜일 때문에 미처 공개하지
못하고 다른 문서들로 인하여 계속 비공개로 남아있었던 것 같습니다.
최근에 epoll에 대한 것을 테스트하다가 뒤늦게 문서가 비공개로 되어있는
것을 알았습니다. 그래서  내용도 조금 수정하고 테스트서버도 작성해보았습니다.
소스도 공개합니다.

epoll을 이용하여 작성된 완성된 훌륭한 서버소스가 많이 공개되어있지만 ,
일반적으로 공부하려는 프로그래머가 그 큰 소스의 내부를 분석하여
이해하기는 좀 무리가 있다고 생각이 듭니다.

아무래도 잘 짜여지고 다듬어지다보니
보다 추상화가 많이 되어있고 보다 감싸져있기 때문에 쓰기는 편해도 이해하기는
힘들다고 할까요
.

직관적으로 이해를 돕기 위해서 , 여기 공개하는 epoll응용
테스트서버는 C언어로 작성하였습니다.  단순한 에코형서버입니다.
접속한 모든 클라이언트에게 내용을 전달합니다. 두 클라이언트가 접속하면
서로 채팅하는것이 될것이고 여러클라이언트가 붙으면 대화방처럼 작동합니다.

여기에 간단한 프로토콜을 넣으면 (채널관리 ,룸관리) 간단한 채팅서버가
될것입니다.

다시 말하면 이해를 위한 뼈대구조의 하나의 샘플입니다.


소스코드가 매끄럽지 못합니다. 다만 직관적 이해를 위해 코드를 압축하거나
추상화하는 것은 자제했습니다.

관련링크

http://www.joinc.co.kr/modules/moniwiki/wiki.php/epoll
윗글은 epoll의 제작자가 만든 원래의 URL에(man페이지) 대한 번역과
이 링크에도 간단한 에코서버소스가 공개되어있습니다.

epoll 제작자의 글은 다음 URL에 있습니다.
http://www.xmailserver.org/linux-patches/epoll.txt


대용량 서버에 대한 방법론에 대한것은 다음문서
http://www.kegel.com/c10k.html 에서 자세히 볼 수 있습니다.

epoll을 사용하기위한 조건

epoll은 2001년 7월 11일에 다비드 라이프니치(David Libenzi)씨에 의해서
리얼타임 시그날 (RealTime Sinal)의 대안으로 제안된 이후로 2.5.x 커널에 추가되기
시작하였습니다.

그리고 2.6.x 커널대에서는 기본으로 탑재되어 있습니다.

(2.6.x 커널은 2003년 12월에 릴리즈 되었습니다. 현재까지(2007년3월) 계속되고 있습니다.)
9.x 대 리눅스라면 epoll을 사용하는데 전혀 무리가 없다는 이야기입니다.
만약  2.6.x 커널 및의 리눅스라면 위의 joinc 문서에서 사용여부 가능방법과 라이브러리
등의 설치등등의 확인 방법이 설명되어있으니 참조하기 바랍니다.

자신의 리눅스 시스템에서 epoll이 사용가능한지 체크하는 C 코드

      #include <stdio.h>
      #include <stdlib.h>
      #include <sys/epoll.h>
     
      int main(int argc, char** argv)
      {
               int epoll_fd;
                 
               if ( (epoll_fd = epoll_create(500)) == -1 )
               {
                       printf("epoll 파일디스크립터 생성 오류 \n");
               }
               else
               {
                       printf("epoll 파일디스크립터 생성성공 \n");
                      close(epoll_fd);
                      /* 생성된 fd는 다른 fd와 마찬가지로 프로그램 종료전에 반드시 닫아주어야한다. */

               }
        }
        /* EOF */

컴파일이 안되거나 , 실행시 오류가 난다면 무엇인가 설정이 잘못되어있는 것입니다. 

epoll 을 사용하기 위한 API

epoll을 사용하기 위해서 알아야 될 API는 다음 세가지입니다.
int epoll_create(int size);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

함수의 각각의 설명은 man페이지나 제작자의 다음 링크에서 볼 수 있습니다.
http://www.xmailserver.org/linux-patches/epoll_create.txt
http://www.xmailserver.org/linux-patches/epoll_ctl.txt
http://www.xmailserver.org/linux-patches/epoll_wait.txt

한글번역문서는 jacking님의 일본man 페이지 번역이 다음링크에 있습니다.
http://jacking75.cafe24.com/Network/epoll.htm

위의 각각의 3개의 API의 이해가 소스를 이해하는데 필수적입니다. 당연한
이야기이지만요.

소스는 epoll LT모드를 사용하고있습니다. ( ET를 선언하지 않은경우
자동으로 LT입니다. 소스에 LT를 따로 선언하지 않았습니다.)
ET모드는 socket이 반드시 non blocking 소켓이어야 하지만 LT는
상관없습니다. 그래서 따로 non blocking 부분을 설정하는 부분이 없고
소켓 그대로 사용하였습니다. 또한 닫혀진 소켓에 대한 epoll set에서
삭제하는 부분도 없습니다.  이부분은 epoll 제작자의 FAQ에서 보면
닫힌 소켓에대해서 epoll set에서 자동으로 없어지는가 하는 질문에
닫힌 소켓은 자동으로 epoll set에서 없어진다라고 되어있기 때문에
따로 처리하지 않았습니다. 소켓이 닫히는순간 epoll set에서 사라집니다.

주석은 영어로 되어있습니다.  ^^;;

추가적인 참조사이트들

더 참조할만한 epoll echo server
다음링크 (청아님 홈페이지입니다.)
여기서 ET모드를 포함한 non-blocking socket 등의 좀더 정밀한
샘플소스및 설명이 있습니다.
http://chonga.pe.kr/blog/index.php?pl=919&ct1=31

추가적인 링크입니다. 장성재님 블로그에 non-blocking 모드에 대한
echo-server와  send시에 발생할수 있는 오류처리를 위한 부분까지
친철하게 추가한 소스가 있습니다.
http://blog.ilovelinux.org/2009/10/epoll-echo-server.html


테스트서버 소스
/*-----------------------------------------------------------------

  epoll server test program by EuiBeom Hwang. : 2005-2007.
  Platform : Linux 2.6.x (kernel)
  compiler Gcc: 3.4.3.
  License: GNU General Public License  
  descption : simple test server. multi client accept.
              sample skelecton epoll structure(conceptional)
              this code is just sample implemention code.
------------------------------------------------------------------*/

/* header files */
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

/* definition */
#define MAX_CLIENT   10000
#define DEFAULT_PORT 9006
#define MAX_EVENTS   10000


/* global definition */
int g_svr_sockfd;              /* global server socket fd */
int g_svr_port;                /* global server port number */

struct {
         int  cli_sockfd;  /* client socket fds */
         char cli_ip[20];              /* client connection ip */
} g_client[MAX_CLIENT];

int g_epoll_fd;                /* epoll fd */

struct epoll_event g_events[MAX_EVENTS];

/* function prototype */
void init_data0(void);            /* initialize data. */
void init_server0(int svr_port);  /* server socket bind/listen */
void epoll_init(void);            /* epoll fd create */
void epoll_cli_add(int cli_fd);   /* client fd add to epoll set */

void userpool_add(int cli_fd,char *cli_ip);

/*--------------------------------------------------------------*/
/* FUNCTION PART
---------------------------------------------------------------*/

/*---------------------------------------------------------------
  function : init_data0
  io: none
  desc: initialize global client structure values
----------------------------------------------------------------*/
void init_data0(void)
{
  register int i;

  for(i = 0 ; i < MAX_CLIENT ; i++)
  {
     g_client[i].cli_sockfd = -1;
  }
}

/*-------------------------------------------------------------
  function: init_server0
  io: input : integer - server port (must be positive)
  output: none
  desc : tcp/ip listening socket setting with input variable
----------------------------------------------------------------*/

void init_server0(int svr_port)
{
  struct sockaddr_in serv_addr;
 
  /* Open TCP Socket */
  if( (g_svr_sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0 )
  {
      printf("[ETEST] Server Start Fails. : Can't open stream socket \n");
      exit(0);
  }

  /* Address Setting */
  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(svr_port);

  /* Set Socket Option  */
  int nSocketOpt = 1;
  if( setsockopt(g_svr_sockfd, SOL_SOCKET, SO_REUSEADDR, &nSocketOpt, sizeof(nSocketOpt)) < 0 )
  {
      printf("[ETEST] Server Start Fails. : Can't set reuse address\n");
      close(g_svr_sockfd);
      exit(0);
  }
 
  /* Bind Socket */
  if(bind(g_svr_sockfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
  {
     printf("[ETEST] Server Start Fails. : Can't bind local address\n");
     close(g_svr_sockfd);
     exit(0);
  }

  /* Listening */
  listen(g_svr_sockfd,15); /* connection queue is 15. */

  printf("[ETEST][START] Now Server listening on port %d\n",svr_port);
}
/*------------------------------- end of function init_server0 */

void epoll_init(void)
{
  struct epoll_event events;

  g_epoll_fd = epoll_create(MAX_EVENTS);
  if(g_epoll_fd < 0)
  {
     printf("[ETEST] Epoll create Fails.\n");
     close(g_svr_sockfd);
     exit(0);
  }
  printf("[ETEST][START] epoll creation success\n");

  /* event control set */
  events.events = EPOLLIN;
  events.data.fd = g_svr_sockfd;

  /* server events set(read for accept) */
  if( epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, g_svr_sockfd, &events) < 0 )
  {
     printf("[ETEST] Epoll control fails.\n");
     close(g_svr_sockfd);
     close(g_epoll_fd);
     exit(0);
  }

  printf("[ETEST][START] epoll events set success for server\n");
}
/*------------------------------- end of function epoll_init */

void epoll_cli_add(int cli_fd)
{
 
  struct epoll_event events;

  /* event control set for read event */
  events.events = EPOLLIN;
  events.data.fd = cli_fd;

  if( epoll_ctl(g_epoll_fd, EPOLL_CTL_ADD, cli_fd, &events) < 0 )
  {
     printf("[ETEST] Epoll control fails.in epoll_cli_add\n");
  }

}

void userpool_add(int cli_fd,char *cli_ip)
{
  /* get empty element */
  register int i;

  for( i = 0 ; i < MAX_CLIENT ; i++ )
  {
     if(g_client[i].cli_sockfd == -1) break;
  }
  if( i >= MAX_CLIENT ) close(cli_fd);

  g_client[i].cli_sockfd = cli_fd;
  memset(&g_client[i].cli_ip[0],0,20);
  strcpy(&g_client[i].cli_ip[0],cli_ip);

}

void userpool_delete(int cli_fd)
{
  register int i;

  for( i = 0 ; i < MAX_CLIENT ; i++)
  {
       if(g_client[i].cli_sockfd == cli_fd)
       {
          g_client[i].cli_sockfd = -1;
          break;
       }
  }
}

void userpool_send(char *buffer)
{
  register int i;
  int len;

  len = strlen(buffer);

  for( i = 0 ; i < MAX_CLIENT ; i ++)
  {
      if(g_client[i].cli_sockfd != -1 )
      {
          len = send(g_client[i].cli_sockfd, buffer, len,0);
          /* more precise code needed here */
      }
  }
 
}


void client_recv(int event_fd)
{
  char r_buffer[1024]; /* for test.  packet size limit 1K */
  int len;
  /* there need to be more precise code here */
  /* for example , packet check(protocol needed) , real recv size check , etc. */

  /* read from socket */
  len = recv(event_fd,r_buffer,1024,0);
  if( len < 0 || len == 0 )
  {
      userpool_delete(event_fd);
      close(event_fd); /* epoll set fd also deleted automatically by this call as a spec */
      return;
  }

  userpool_send(r_buffer);

}

void server_process(void)
{
  struct sockaddr_in cli_addr;
  int i,nfds;
  int cli_sockfd;
  int cli_len = sizeof(cli_addr);

  nfds = epoll_wait(g_epoll_fd,g_events,MAX_EVENTS,100); /* timeout 100ms */

  if(nfds == 0) return; /* no event , no work */
  if(nfds < 0)
  {
      printf("[ETEST] epoll wait error\n");
      return; /* return but this is epoll wait error */
  }

  for( i = 0 ; i < nfds ; i++ )
  {
      if(g_events[i].data.fd == g_svr_sockfd)
      {
          cli_sockfd = accept(g_svr_sockfd, (struct sockaddr *)&cli_addr,(socklen_t *)&cli_len);
          if(cli_sockfd < 0) /* accept error */
          {
          }
          else
          {
             printf("[ETEST][Accpet] New client connected. fd:%d,ip:%s\n",cli_sockfd,inet_ntoa(cli_addr.sin_addr));
             userpool_add(cli_sockfd,inet_ntoa(cli_addr.sin_addr));
             epoll_cli_add(cli_sockfd);
          }
          continue; /* next fd */
      }
          /* if not server socket , this socket is for client socket, so we read it */
         client_recv(g_events[i].data.fd);   

  } /* end of for 0-nfds */
}
/*------------------------------- end of function server_process */

void end_server(int sig)
{
  close(g_svr_sockfd); /* close server socket */
  printf("[ETEST][SHUTDOWN] Server closed by signal %d\n",sig);
  exit(0);
}

int main( int argc , char *argv[])
{
 
  printf("[ETEST][START] epoll test server v1.0 (simple epoll test server)\n");
  /* entry , argument check and process */
  if(argc < 3) g_svr_port = DEFAULT_PORT;
  else
  {
     if(strcmp("-port",argv[1]) ==  0 )
     {
        g_svr_port = atoi(argv[2]);
        if(g_svr_port < 1024)
        {
           printf("[ETEST][STOP] port number invalid : %d\n",g_svr_port);
           exit(0);
        }
     }
  }


  init_data0();  

  /* init server */
  init_server0(g_svr_port);
  epoll_init();    /* epoll initialize  */

  /* main loop */
  while(1)
  {
     server_process();  /* accept process. */
  } /* infinite loop while end. */

}


top

◀ PREV : [1] : [2] : [3] : [4] : [5] : [6] : .. [8] : NEXT ▶