반응형
728x90
반응형

Epoll 모델을 구현해 볼 시간이다. 

먼저, 필요로 하는 변수들이다.

코드는 중요한 변수만 넣었고, 그림에는 주석과 내가 작성한 그대로 캡춰한 것이니 함께 보면 좋을 것 같다.

1
2
3
int epoll_fd; // Epoll Instance
struct epoll_event* epoll_events; // 변화가 일어난 디스크립터들에 대해서 저장할 저장소(only 동적 할당)
struct epoll_event epoll_event;    // EPOLL_CTL_ADD 로 epoll_ctl 시 사용됨.
cs

다음으로는 초기화 과정이다

1
2
3
4
5
6
7
8
9
// epoll 인스턴스 저장소 생성.
this->epoll_fd = epoll_create(Server::SERVER__CONSTVAR__MAX_USER_COUNT);
// 변화가 발생한 디스크립터들만 저장되는 구조체이며, 반드시 동적할당.
this->epoll_events = (struct epoll_event*)malloc(sizeof(struct epoll_event) * Server::SERVER__CONSTVAR__MAX_USER_COUNT);
// 이벤트 등록을 위한 준비
this->epoll_event.events = EPOLLIN;
this->epoll_event.data.fd = this->serv_sock;
// 이벤트 종류 등록
epoll_ctl(this->epoll_fd, EPOLL_CTL_ADD, this->serv_sock, &this->epoll_event);
cs

그림으로 충분히 설명될꺼라고 생각한다.

이어서, 접속 요청이나 데이터 수신을 위해 epoll_wait 함수를 호출하는 부분이다

1
2
int changed_fd_count = 0;
changed_fd_count = epoll_wait(this->epoll_fd, this->epoll_events, Server::SERVER__CONSTVAR__MAX_USER_COUNT, -1);
cs

2번 라인에서 epoll instance(epoll_fd)와 변화된 파일 디스크립터를 저장할 버퍼의 주소 값(epoll_events), 변화될 이벤트를 저장할 갯수, -1 값이 순서대로 들어간다. 

epoll_wait의 마지막 인자에 -1 값이 들어간다면 파일 디스크립터의 변화가 생겨서 이벤트가 발생할 때까지 무한 대기에 들어간다.

그리고 가장 중요한 것은 select 모델에서 처럼 매번의 epoll_wait 함수 호출마다 등록된 파일 디스크립터의 배열을 전달하지 않는다.

다음은 epoll_wait 함수 호출로 받아온 리턴 값 별 처리이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
changed_fd_count = epoll_wait(this->epoll_fd, this->epoll_events, Server::SERVER__CONSTVAR__MAX_USER_COUNT, -1); // -1 전달 시 이벤트 발생까지 무한 대기
if (changed_fd_count == -1)
    HandleStatus("SERVER"true"epoll_wait() Error\n");
if (changed_fd_count == 0)
    return 0;
for (int i = 0, fd = -1; i < changed_fd_count && (fd = this->epoll_events[i].data.fd); i++)
{
    if (fd == this->serv_sock) // 접속 요청
    {
        AcceptConnectionRequest(this->serv_sock);
        this->PrintConsoleNowUser();
    }
    else // 수신 및 종료
    {
        r_var = this->ReceiveData(fd);
        if (r_var == Server::SERVER__RETURNVAR__DISCONNECT_USER) {
            this->Disconnect(fd);
               this->PrintConsoleNowUser();
        } else {
            if (this->HandlePacketMessage(fd) == Server::SERVER__RETURNVAR__TERMINATE_BY_ADMIN)
                return Server::SERVER__RETURNVAR__TERMINATE_BY_ADMIN;
        }
    }
}
cs

중요한 것은 6번 라인에서 돌아가는 반복문이 등록된 모든 파일 디스크립터를 대상으로 하는 것이 아니라, 변화가 일어난 파일 디스크립터의 수 만큼만 반복이 이루어진다는 점이다.

좀 더 세분화 했을 때, 접속 요청에 따른 수락 시 클라이언트 파일 디스크립터의 등록이다.

1
2
3
this->epoll_event.events = EPOLLIN;
this->epoll_event.data.fd = this->clnt_sock;
epoll_ctl(this->epoll_fd, EPOLL_CTL_ADD, this->clnt_sock, &this->epoll_event);
cs

접속해 온 클라이언트 소켓 파일 디스크립터에 대해서 수신에 대해서 감시하겠다 라는 의미로 EPOLLIN 이벤트로 등록한다

 

접속한 클라이언트가 종료할 경우를 확인한다

종료하려는 클라이언트의 파일 디스크립터를 당연히 원본 fd_set의 배열에서 빼줘야한다. 종료하는데 지니고 있어서 뭐 하는가 ㅎㅎ..

대충 큰 처리 방법들은 한 번씩 다 훑어봤다.

728x90
반응형
728x90
반응형
 

C기반 I/O Multiplexing - 5. Select 모델의 단점

자, 오늘도 기록을 한다 지난 포스팅들을 보면 select 모델들을 학습했다. 그런데 이 select 모델은 굉장한 단점들이 있다고 하더라.. 단점들로 인해서 발생하는 현상부터 말하자면 다음과 같다. 클

typingdog.tistory.com

이전 포스팅에서 select 모델의 단점에 대해서 기록했다.

결론적으로 단점은 다음과 같다.

1. select 반환 값에 따라 행동을 할 때, 등장하는 등록된 파일 디스크립터들을 대상으로 하는 반복문
2. Select 함수를 호출할 때마다 관찰 대상의 인자 전달.

결국, 등록된 파일 디스크립터들을 매번 통째로 넘기고, 통째로 결과를 받아오기 때문에 운영체제에게 넘기고 받고 하는 과정도 부담스럽고, 뭐가 변했는지 알려면 모든 파일 디스크립터들을 대상으로 반복문도 오지게~ 돌려야한다.

그러나 초기에 파일 디스크립터들을 넘기고, 갱신되는 부분들만 넘기고 받는다면 운영체제에게 데이터를 주고 받는 과정에서 발생하는 오버헤드를 줄일 수 있고, 등록된 파일 디스크립트 전체를 대상으로 돌던 반복문을 갱신된 파일 디스크립터에 대해서만 반복문을 돌리면 되니 매우 효율적일 수 밖에 없다. 이런 방식으로 동작하는 것이 바로 Epoll 모델이다.

Epoll 모델의 컨셉

Epoll 모델의 컨셉은 파일 디스크립터들을 등록하고, 이벤트 발생 대기 함수를 실행하여 변화된 파일 디스크립터들을 뽑아내어 처리한다는 Select 모델의 컨셉과 다를 바가 없다. 이를 전제로 차이를 확인하면서 컨셉을 보자.

파일 디스크립터 저장소 생성

파일 디스크립터를 저장할 수 있는 저장소의 생성이다. 이 저장소를 Epoll Instance 라고 하며, 이 인스턴스 또한 파일 디스크립터를 부여받는다. 이 fd를 이용하여 파일 디스크립터를 추가 및 제거할 때 저장소를 지목(?) 할 때 쓴다.

epoll_create 로 저장소를 생성하며, 인자로 들어가는 크기 정보는 절대적인 것이 아니라 운영체제가 참고로만 사용한다고 한다.

파일 디스크립터의 등록

epoll_ctl 함수는 감시할 파일 디스크립터를 등록 및 삭제, 수정하는 함수이다. Select 모델에서의 FD_ 매크로들의 기능을 하나의 함수로 수행하도록 되어 있으며, 파일 디스크립터마다 관찰할 이벤트 유형을 지정할 수 있다.

먼저 추가, 삭제, 변경(수정) 부분이다.

이러한 매크로 상수를 값으로 넣을 수 있다. 용도에 맞게.

관찰 대상의 이벤트 유형을 등록할 때에는 아래와 같은 자료형에 값을 넣어서 인자 전달을 한다.

왼쪽 구조체에서 events 라는 부분에 이벤트의 종류를 명시하면 되는데 우리가 만드는 프로그램은 서버이므로 수신 이벤트에 초점을 맞춘다. 그러므로 아래와 같이 EPOLLIN 이라는 매크로 상수를 사용한다.

참고 ) Epoll 등록가능한 이벤트는 enum으로 열거되어 있다.

Epoll_wait 함수의 호출

epoll_wait 함수를 이용하여 변화하는 이벤트를 솎아낸다 ㅋㅋ 이 함수를 통해서 변화가 발생하는 파일 디스크립터들만 뽑아낼 수 있다! 어디에? 두 번째 인자로 전달되는 버퍼 변수에!

기본 컨셉은 select 모델과 다를 부분이 크게 없으며, 약간 약간의 차이이다. 그리고 select 모델에서 level trigger 적인 특성을 지닌 것처럼 epoll 모델은 level trigger가 기본 형태이다.

다음 포스팅에는 코드 레벨에서 정리를 해볼 것이다.

728x90
반응형
728x90
반응형

자, 오늘도 기록을 한다 

지난 포스팅들을 보면 select 모델들을 학습했다. 그런데 이 select 모델은 굉장한 단점들이 있다고 하더라..

단점들로 인해서 발생하는 현상부터 말하자면 다음과 같다.

클라이언트를 동시에 수용할 수 있는 수가 100 을 넘기가 힘들다.

뭐 나처럼 프리 서버나 만드는 수준이라면 상관없겠지만, 그래도 100명은 너무 적지 않나 싶다. 그렇다면 왜 그런지 알아보자.

Select 모델의 단점

1. select 반환 값에 따라 행동을 할 때, 등장하는 등록된 파일 디스크립터들을 대상으로 하는 반복문

이게 첫 번째 문제이다. 아래 코드를 보면 된다.

55번 라인에서 select 함수를 호출한 뒤 반환되는 값에 따라서 변화한 파일 디스크립터의 수만큼 반복 루프를 도는 것도 아니고, 전체 등록된 파일 디스크립터의 수만큼 반복 루프 및 조건 검사를 진행한다.

이 말인 즉슨, 파일 디스크립터가 딱 하나 변한다고 하더라도 전체를 검사한다는 소리이다. -> 매우 비효율적이다.

2. Select 함수를 호출할 때마다 관찰 대상의 인자 전달.

위 그림에서 51번 라인에서 등록된 파일 디스크립터들을 최신화하고, 55번 라인에서 select 함수의 인자로 전달을 한다. 이러한 인자 전달은 select 함수 내에서만 사용되고 마는 것이 아니다. 

파일 디스크립터의 변화를 파악한다는 것은 수신 버퍼, 쓰기 버퍼 등을 확인하는 작업이기 때문에 운영체제의 관리 대상이다. 운영체제의 도움이 필요한 것이다.  즉, 전달된 인자를 운영체제에게까지 전달하여 운영체제의 도움을 받아야 한다는 소리인데..

응용 프로그램 단에서 운영체제에게 이렇게 많은 데이터를 전달한다는 것은 부담이 크다고 한다. 

그래서 운영체제에게 관찰 대상에 대한 정보를 초기에 한 번 알려주고, 갱신되는 부분에 대해서만 알려주는 방식을 사용하면 되지않을까 라는 생각에서 나온 것이 Epoll 방식이다. 윈도우에서는 IOCP 라는 개념으로 발전되었다고 한다.

다음 번 포스팅은 Epoll 이다. Epoll 모델이 기존 Select 모델에 비해 무엇이 달라졌는지 정도 확인과 작성 코드를 분석하는 시간을 갖겠다.

 

 

 

728x90
반응형

+ Recent posts