Epoll은 기존 select 모델의 한계를 뛰어넘는 새로운 스타일의 서버입니다.

기존 select의 경우 일정 비트열을 두고 그곳에 0 이냐 1이냐로 클라이언트의 응답을 기다리는 방식이라 약 300~500 클라이언트 이상 동접시 속도저하 문제가 나타난다고 합니다.

(이 말은 즉, 동접 300 가량 정도는 select 으로 써도 상관은 없다는 얘기죠;;)
 

그래서 이를 해결하기 위해 아예 하드웨어적으로 클라이언트의 접속 변화를 감지해서 recv send냐 를 판별해 주는 함수가 epoll 이라 합니다.

 

문제는 이게.. 리눅스에서 만든거라 윈도우에선 사용이 불가능합니다.

그리고 리눅스도 커널 2.6 이상에서만 된다고 합니다.

 

하여간 소스 들어갑니다.

소스가.. 일단 숙제용으로서.. 게임서버 시간의 숙제를 하다가 이것저것 조사하고 찾은걸
(인터넷과 man 메뉴얼)로 짜집기 한겁니다.. orz
 

chatServer.c

#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 <sys/types.h>

#include <netinet/in.h>

#include <netinet/tcp.h>

#include <fcntl.h>

#include <sys/ioctl.h>

#include <errno.h>

 

#define EPOLL_SIZE 60

#define EPOLL_EVENT_SIZE 100

#define OPENPORT 9000                       // open port 번호


// main

int

main (int argc, char **argv)

{

        struct epoll_event *events;          // epoll 관련

        struct epoll_event ev;        

        struct sockaddr_in addr, clientaddr;

        int clilen;

        int sfd, efd, cfd;                   // server 소켓, epoll 소켓, client 소켓

        int i,j;                             // for문을 위해

        int max_got_events;

        int result;

        int readn;

        int sendflags = 0;

        char buf_in[256] = { '\0' };         // 버퍼

        int count = 0;

 
        // epoll 소켓 생성

        if ((efd = epoll_create (EPOLL_EVENT_SIZE)) < 0)

        {

                perror ("epoll_create (1) error");

                return -1;

        }

 

        // init pool

        events = (struct epoll_event *) malloc (sizeof (*events) * EPOLL_SIZE);

        if (NULL == events)

        {

                perror ("epoll_create (0) error");

                return -1;

        }

 
        // 서버 소켓 생성

        clilen = sizeof (clientaddr);

        sfd = socket (AF_INET, SOCK_STREAM, 0);

        if (sfd == -1)

        {

                perror ("socket error :");

                close (sfd);

                return -1;

        }


        // 소켓 정보 넣고 bind, listen 처리 

        addr.sin_family = AF_INET;

        addr.sin_port = htons (OPENPORT);

        addr.sin_addr.s_addr = htonl (INADDR_ANY);

        if (bind (sfd, (struct sockaddr *) &addr, sizeof (addr)) == -1)

        {

                close (sfd);

                return -2;

        }

        listen (sfd, 5);

 

        if (sfd < 0)

        {

                perror ("init_acceptsock error");

                return 1;

        }


        // 서버시작, epoll 이벤트 설정 및 컨트롤 함수로 활성화 

        printf("Running Server port %d\n",OPENPORT);

        ev.events = EPOLLIN;

        ev.data.fd = sfd;

        result = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &ev);

        if (result < 0)

        {

                perror ("epoll_ctl error");

                return 1;

        }

 

        // 서버 실제 루트
       
while (1)

        {

                // epoll 이벤트 대기 (클라이언트의 반응(접속 or send) 감지)
               
max_got_events = epoll_wait (efd, events, EPOLL_SIZE, -1);

             
                // epoll 이벤트 받은만큼의 숫자만큼 for
               
for (i = 0; i < max_got_events; i++)

                {

                        // 이벤트 들어온 소켓이 연결 요청이면
                       
if (events[i].data.fd == sfd)

                        {

                                cfd = accept (sfd, (struct sockaddr *)

                                                &clientaddr, &clilen);

                                if (cfd < 0)

                                {

                                        perror ("Accept error");

                                        return -1;

                                }

 

                                ev.events = EPOLLIN;

                                ev.data.fd = cfd;

                                epoll_ctl (efd, EPOLL_CTL_ADD, cfd, &ev);

 

                                printf("Accepted socket : %d\n",cfd);

                                count++;

                        }

                        // 그게 아니면,
                       
else

                        {

                                cfd = events[i].data.fd;

 

                                memset (buf_in, 0x00, 256);

                                readn = read (cfd, buf_in, 255);

                                // if it occured ploblems with reading,

                                // delete from epoll event pool and close socket                                                                                                                        // readn 값이 0 이하이면 recv 처리

                                if (readn <= 0)

                                {

                                        epoll_ctl(efd, EPOLL_CTL_DEL, cfd, &ev);

                                        close (cfd);

                                        printf ("Close fd %d\n", cfd);

 

                                        count--;

                                }

                                else

                                {

                                // 아니면 send 처리
                                //      printf ("%s", buf_in);

                                        for(j=5; j<count+5; j++)

                                        {

                                          // 단 이벤트로온 client엔 send 하지말고
                                          // 다른 모든 클라이언트에 send 해서 채팅

                                               
if(cfd != j)

                                                        send (j, buf_in,

                                                         strlen (buf_in),

                                                         sendflags);

                                        }

                                }

 

                        }

                }

        }

        return 1;

}

 

 

맨 아래쪽의

For j=5 라고 했는데, 리눅스에선 소켓 번호가 윈도우와 달리 5번부터인가 시작되서 그렇습니다.

윈도우는 OS가 자기 맘대로 할당하지만 리눅스에선 0~4번이 정해져 있고 그 이후 차례대로 count 됩니다..
물론 저 소스는 야메로 짠거라 문제 발생 요지가 많습니다. Orz

 

참고로 클라이언트는

그냥 telnet으로 telnet 127.0.0.1 9000 으로 접속하면 됩니다.

+ Recent posts