Libevent R4: Working with events: Difference between revisions

From 탱이의 잡동사니
Jump to navigation Jump to search
No edit summary
No edit summary
Line 75: Line 75:
}
}
</source>
</source>
=== The event flags ===
* EV_TIMEOUT
: 같이 설정한 시간뒤에 active 된다.
* EV_READ
: 등록한 파일 디스크립터가 Read 가능한 상태일 때 active 된다.
* EV_WRITE
: 등록한 파일 디스크립터가 Write 가능한 상태일 때 active 된다.
* EV_SIGNAL
: 등록한 시그널이 감지될 때 active 된다.
* EV_PERSIST
: 해당 이벤트는 "영구적"임을 나타낸다.
* EV_ET
: event_base 의 백엔드가 Edge-trigger 를 지원할 때, 해당 이벤트가 Edge-triggered 를 사용한다는 것을 나타낸다. EV_READ/EV_WRITE 의 Edge-trigger 버전이다.
=== About Event Persistance ===
기본적으로 이벤트가 pending 상태에서 active 상태가 되면, 등록된 callback 함수가 실행된 직후, non-pending 상태가 된다. 따라서 만약 해당 이벤트를 다시금 pending 상태로 만들고 싶다면, 콜백 함수 내에서 event_add() 통해 다시금 이벤트를 등록시켜주어야 한다.
하지만 만약 EV_PERSIST 플래그가 세팅되어 있다면, 이벤트는 영구적이 된다. 무슨 말이냐면, 이벤트가 active 상태가 되어 콜백을 실행시킨 이후에 다시 pending 상태로 돌아온다는 뜻이다. 만약 non-pending 상태로 만들고 싶다면, event_del() 를 사용하면 된다.
timeout과 persistent 옵션을 같이 사용하게 되면, timout 와 trigger 를 공유하는 형태가 된다. 예를 들어 EV_READ|EV_PERSIST 와 timeout 5초를 함께 설정하게 되면 다음과 같이 된다.
* 소켓이 read 가능하면 active된다.
* 마지막으로 active 된 지 매 5 초 후마다 active 된다.
=== Creating an event as its own callback argument ===
때때로, 콜백 함수 자신을 인자값으로 처리해야 하는 경우가 있을 수 있다. 하지만 이벤트 자체를 그냥 포인터로 넘겨줄 수는 없다. 왜냐하면 아직 생성되지 않았기 때문이다. 실제로 이벤트가 active 되고 실행하는 순간 callback 메모리가 할당되기 때문이다. 따라서 이를 위해서는 별도의 event_self_cbarg() 함수를 사용해야 한다.
* Interface
<source lang=c>
void *event_self_arg();
</source>
event_self_cbarg()는 콜백 함수를 가리키는 "매직 포인터"를 리턴해준다.
* Example
<source lang=c>
#include <event2/event.h>
static int n_calls = 0;
void cb_func(evutil_socket_t fd, short what, void *arg)
{
    struct event *me = arg;
   
    printf("cb_func called %d times so far.\n", ++n_calls);
   
    if(n_calls > 100)
    {
        event_del(me);
    }
}
void run(struct event_base *base)
{
    struct timeval one_sec = {1, 0};
    struct event *ev;
    /* We're going to set up a repeating timer to get called called 100
    * times. */
    ev = event_new(base, -1, EV_PERSIST, cb_func, event_self_cbarg());
    event_add(ev, &one_sec);
    event_base_dispatch(base);
}
</source>
event_new(), evtimer_new(), evsignal_new(), event_assign(), evtimer_assign, evsignal_assign() 등과 함께 사용할 수 있다.
=== Timeout-only events ===
evtimer_ 로 시작하는 매크로 들이 있다. event_ 로 시작하는 함수와 timer 관련 옵션을 함께 설정할때 사용가능한데, 이를 사용하면 번거로운 작업을 줄일 수 있다.
* Interface
<source lang=c>
#define evtimer_new(base, callback, arg) \
    event_new(base), -1, 0, (callback), (arg))
#define evtimer_add(ev, tv) \
    event_add((ev), (tv))
#define evtimer_Del(ev) \
    event_del(ev)
#define evtimer_pending(ev, tv_out) \
    event_pending((ev), EV_TIMEOUT, (tv_out))
</source>
=== Constructing signal events ===
Libevent는 POSIX 시그널도 감지가 가능하다.
* Interface
<source lang=c>
#define evsignal_new(base, signum, cb, arg) \
    event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
</source>
event_new 사용법과 같은데, 파일 디스크립터 대신 시그널 번호만 넘겨주는 것이 다르다.
* Example
<source lang=c>
struct event *hup_event;
struct event_base *base = event_base_new();
/* call sighup_function on a HUP signal */
hup_event = evsignal_new(base, SIGHUP, sighup_function, NULL);
</source>
등록한 시그널 콜백은 시그널이 발생될때 이벤트 루프에서 호출된다. 따라서 시그널 콜백 사용시, POSIX 시그널 핸들러에 아무처리를 하지않도록 따로 설정해놓는 것이 안전하다.
signal event 등록은 위한 매크로 역시 존재한다.
* Interface
<source lang=c>
#define evsignal_add(ev, tv) \
    event_add((ev), (tv))
#define evsignal_del(ev) \
    event_del(ev)
#define evsignal_pending(ev, what, tv_out) \
    event_pending((ev), (what), (tv_out))
</source>
* Caveats when working with signals
: Libevent 에서 시그널을 등록해서 사용할 때 주의해야할 점이 있다. 프로세스당 오직 하나의 event_base 에서만 시그널을 수신할 수 있다는 것이다. 만약 하나의 프로세스에서 여러개의 event_base 를 생성하고 각각의 event_base 마다 시그널을 등록했을 경우, 오직 하나의 event_base 에서만 시그널을 수신하게 된다는 것이다. 시그널의 번호가 모두 달라도 안된다.
: 단, kqueue 에서는 제약이 없다.
=== Setting up events without heap-allocation ===
성능, 혹은 다른 이유로 힙영역 이외의 다른 곳에 이벤트 메모리를 관리할 수 있다. 이 기능을 사용하게 되면 다음의 문제들을 관리할 수 있다.
* 작은 양의 메모리를 heap 영역에 할당하게 되면 오버헤드가 발생한다.
* struct event pointer 를 역 조회하게되면 time overhead 가 발생한다.
* 만약 이벤트가 캐시에 없는 경우, 이를 위한 time-overhead 가 발생할 수 있다.
이를 위해서는 event_assign() 을 사용하면 된다. 하지만 이를 사용하게 되면 다른 버전의 Libevent 와의 바이너리 호환성을 깨트릴 수 있다. 왜냐하면 이벤트 구조체의 사이즈가 달라질 수 있기 때문이다.
위에 나열한 문제들은 굉장히 작은 문제들이다. 그리고 거의 대부분의 어플리케이션에서는 문제되지 않는다. 이벤트 메모리를 다른 곳에 관리함으로써 생기는 문제점들에 대해 정확히 알고있지 않은 한, event_new() 를 사용하길 바란다. event_assign()을 사용하게 되면 다른 버전의 Libevent(만약 이벤트 구조체 크기가 달라졌다면)와 연동했을 경우, 에러를 찾기 힘들어진다.
* Interface
<source lang=c>
int event_assign(struct event *event,
    struct event_base *base,
    evutil_socket_t fd,
    short what,
    void (*callback)(evutil_socket_t, short, void *),
    void *arg);
</source>
event 인자값을 제외한, 모든 인자값은 event_new() 에서 사용하는 것과 같다. 성공시 0, 실패시 -1을 리턴한다.
* Example
<source lang=c>
#include <event2/event.h>
/* Watch out! Including event_struct.h means that your code will not
* be binary-compatible with future versions of Libevent. */
#include <event2/event_struct.h>
#include <stdlib.h>
struct event_pair {
    evutil_socket_t fd;
    struct event read_event;
    struct event write_event;
};
void readcb(evutil_socket_t, short, void *);
void writecb(evutil_socket_t, short, void *);
struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd)
{
    struct event_pair *p = malloc(sizeof(struct event_pair));
    if(!p) return NULL;
    p->fd = fd;
    event_assign(&p->read_event, base, fd, EV_READ|EV_PERSIST, readcb, p);
    event_assign(&p->write_event, base, fd, EV_WRITE|EV_PERSIST, writecb, p);
    return p;
}
</source>
* Warning
절대로 pending 중인 이벤트에 대해 event_assign() 를 사용하면 안되다. 만약 그런 일을 하게되면 정말로 오류가 발생해도 원인을 찾을 수 없게 된다. 만약 이미 Initialized 됐고, pending 상태의 이벤트라면, event_del() 을 먼저 사용하고, event_assign()을 사용해야 한다.
assign 과 관련된 매크로 함수들 역시 존재한다.
* Interface
<source lang=c>
#define evtimer_assign(event, base, callback, arg) \
    event_assign(event, base, -1, 0, callback, arg)
#define evsignal_assign(event, base, signum, callback, arg) \
    event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback arg)
</source>
event_assign() 을 사용하면서도 추후의 Libevent 라이브러리와 바이너리 호환성을 유지해야 한다면, 사용중인 라이브러리의 struct event 크기를 확인할 수 있다.
* Interface
<source lang=c>
size_t event_get_struct_event_size(void);
</source>
이 함수는 현재 사용중인 이벤트 구조체의 바이트 크기를 리턴한다.
This function returns the number of bytes you need to set aside for a struct event. As before, you should only be using this function if you know that heap-allocation is actually a significant problem in your program, since it can make your code much harder to read and write.
Note that event_get_struct_event_size() may in the future give you a value smaller than sizeof(struct event). If this happens, it means that any extra bytes at the end of struct event are only padding bytes reserved for use by a future version of Libevent.
Here’s the same example as above, but instead of relying on the size of struct event from event_struct.h, we use event_get_struct_size() to use the correct size at runtime.


== References ==
== References ==

Revision as of 15:22, 29 January 2015

Overview

원문은 이곳<ref>http://www.wangafu.net/~nickm/libevent-book/Ref4_event.html</ref>에서 확인할 수 있다.

Libevent의 기본 실행 단위는 event(이벤트)이다. 모든 이벤트는 상태를 가지게 되는데 이는 다음과 같다.

  • 파일 디스크립터의 read/write 준비 완료
  • 파일 디스크립터의 read/write 준비 중(Egde-triggered IO Only)
  • timout 만료
  • signal 발생
  • 사용자-정의 trigger

이벤트들은 비슷한 주기를 가지게 되는데, 한번 이벤트를 생성하게 되면 initialized 상태가 된다. 이후 event_base 에 등록하게 되면 pending 상태가 된다. 이벤트가 pending 상태에서 특정 조건을 만족하여 trigger(readable/writable, timeout expire) 되게 되면, active 상태가 되면서 등록된 callback 함수가 실행된다. 만약 이벤트가 persistent 로 설정되었다면, 다시 pending 상태로 돌아간다. 만약 persistent 가 아니라면, pending으로 돌아가지 않는다. 이벤트 delete 를 함으로써 pending 상태에서 non-pending 상태로 바꿀 수 있으며, add 를 이용해서 non-pending 에서 pending으로 변경할 수도 있다.

Constructing event objects

새로운 이벤트를 생성하기 위해서는 event_new() 인터페이스를 사용하면 된다.

  • Interface

<source lang=c>

  1. define EV_TIMEOUT 0x01
  2. define EV_READ 0x02
  3. define EV_WRITE 0x04
  4. define EV_SIGNAL 0x08
  5. define EV_PERSIST 0x10
  6. define EV_ET 0x20

typedef void (*event_callback_fn)(evutil_socket_t, short, void *);

struct event *event_new(struct event_base *base,

   evutil_socket_t fd,
   short what, 
   event_callback_fn cb,
   void *arg);

void event_free(struct event *event); </source>

event_new() 함수는 같이 넘겨지는 인자 *base 에서 동작하는 이벤트를 생성한다. 그리고 what 인자는 flag 셋인데, 사용가능한 flag 가 위쪽에 나와있다. 만약 fd 가 양수 값으로 설정됐다면, read/write 하고자 하는 파일 디스크립터 값으로 간주된다. 이벤트가 active 되면, 이벤트는 여기서 등록된 cb 함수를 다음의 인자 값과 함께 실행시킨다(fd : 파일 디스크립터, arg: 이벤트 생성시 넘겨준 arg 값)

오류 발생시, event_new() 는 NULL 을 리턴한다.

모든 이벤트는 생성 직후 initialized 상태 혹은 non-pending 상태가 된다. pedning 상태로 만들기 위해서는 event_add() 를 호출해야 한다.(아래에서 설명함)

이벤트를 삭제(메모리 해제)하고 한다면 event_free() 를 사용해야 하며, 해당 이벤트가 pending/active 상태 중에서도 사용 가능하다.(해당 이벤트가 non-pending/inactive 상태일 때 삭제된다)

  • Example

<source lang=c>

  1. include <event2/event.h>

void cb_func(evutil_socket_t fd, short what, void *arg) {

   const char *data = arg;
   printf("Got an event on socket %d:%s%s%s%s [%s]",
       (int) fd,
       (what & EV_TIMEOUT)?    " timeout" : "",
       (what & EV_READ)?       " read" : "",
       (what & EV_WRITE)?      " write" : "",
       (what & EV_SIGNAL)?     " signal" : "",
       data);

}

void main_loop(evutil_socket_t fd1, evutil_socket_t fd2) {

   struct event *ev1, *ev2;
   struct timeval five_seconds = {5, 0};
   struct event_base *base = event_base_new();
   
   /* The caller has already set up fd1, fd2 somehow, and make them
    * nonblocking. */
    
   ev1 = event_new(base, fd1, EV_TIMEOUT|EV_READ|EV_PERSIST, cb_func, (char*)"Reading event");
   ev2 = event_new(base, fd2, EV_WRITE|EV_PERSIST, cb_func, (char*)"Writing event");
   
   event_add(ev1, &five_seconds);
   event_add(ev2, NULL);
   event_base_dispatch(base);

} </source>

The event flags

  • EV_TIMEOUT
같이 설정한 시간뒤에 active 된다.
  • EV_READ
등록한 파일 디스크립터가 Read 가능한 상태일 때 active 된다.
  • EV_WRITE
등록한 파일 디스크립터가 Write 가능한 상태일 때 active 된다.
  • EV_SIGNAL
등록한 시그널이 감지될 때 active 된다.
  • EV_PERSIST
해당 이벤트는 "영구적"임을 나타낸다.
  • EV_ET
event_base 의 백엔드가 Edge-trigger 를 지원할 때, 해당 이벤트가 Edge-triggered 를 사용한다는 것을 나타낸다. EV_READ/EV_WRITE 의 Edge-trigger 버전이다.

About Event Persistance

기본적으로 이벤트가 pending 상태에서 active 상태가 되면, 등록된 callback 함수가 실행된 직후, non-pending 상태가 된다. 따라서 만약 해당 이벤트를 다시금 pending 상태로 만들고 싶다면, 콜백 함수 내에서 event_add() 통해 다시금 이벤트를 등록시켜주어야 한다.

하지만 만약 EV_PERSIST 플래그가 세팅되어 있다면, 이벤트는 영구적이 된다. 무슨 말이냐면, 이벤트가 active 상태가 되어 콜백을 실행시킨 이후에 다시 pending 상태로 돌아온다는 뜻이다. 만약 non-pending 상태로 만들고 싶다면, event_del() 를 사용하면 된다.

timeout과 persistent 옵션을 같이 사용하게 되면, timout 와 trigger 를 공유하는 형태가 된다. 예를 들어 EV_READ|EV_PERSIST 와 timeout 5초를 함께 설정하게 되면 다음과 같이 된다.

  • 소켓이 read 가능하면 active된다.
  • 마지막으로 active 된 지 매 5 초 후마다 active 된다.

Creating an event as its own callback argument

때때로, 콜백 함수 자신을 인자값으로 처리해야 하는 경우가 있을 수 있다. 하지만 이벤트 자체를 그냥 포인터로 넘겨줄 수는 없다. 왜냐하면 아직 생성되지 않았기 때문이다. 실제로 이벤트가 active 되고 실행하는 순간 callback 메모리가 할당되기 때문이다. 따라서 이를 위해서는 별도의 event_self_cbarg() 함수를 사용해야 한다.

  • Interface

<source lang=c> void *event_self_arg(); </source>

event_self_cbarg()는 콜백 함수를 가리키는 "매직 포인터"를 리턴해준다.

  • Example

<source lang=c>

  1. include <event2/event.h>

static int n_calls = 0;

void cb_func(evutil_socket_t fd, short what, void *arg) {

   struct event *me = arg;
   
   printf("cb_func called %d times so far.\n", ++n_calls);
   
   if(n_calls > 100)
   {
       event_del(me);
   }

}

void run(struct event_base *base) {

   struct timeval one_sec = {1, 0};
   struct event *ev;
   /* We're going to set up a repeating timer to get called called 100
    * times. */
   ev = event_new(base, -1, EV_PERSIST, cb_func, event_self_cbarg());
   event_add(ev, &one_sec);
   event_base_dispatch(base);

} </source> event_new(), evtimer_new(), evsignal_new(), event_assign(), evtimer_assign, evsignal_assign() 등과 함께 사용할 수 있다.

Timeout-only events

evtimer_ 로 시작하는 매크로 들이 있다. event_ 로 시작하는 함수와 timer 관련 옵션을 함께 설정할때 사용가능한데, 이를 사용하면 번거로운 작업을 줄일 수 있다.

  • Interface

<source lang=c>

  1. define evtimer_new(base, callback, arg) \
   event_new(base), -1, 0, (callback), (arg))
  1. define evtimer_add(ev, tv) \
   event_add((ev), (tv))
  1. define evtimer_Del(ev) \
   event_del(ev)
  1. define evtimer_pending(ev, tv_out) \
   event_pending((ev), EV_TIMEOUT, (tv_out))

</source>

Constructing signal events

Libevent는 POSIX 시그널도 감지가 가능하다.

  • Interface

<source lang=c>

  1. define evsignal_new(base, signum, cb, arg) \
   event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)

</source> event_new 사용법과 같은데, 파일 디스크립터 대신 시그널 번호만 넘겨주는 것이 다르다.

  • Example

<source lang=c> struct event *hup_event; struct event_base *base = event_base_new();

/* call sighup_function on a HUP signal */ hup_event = evsignal_new(base, SIGHUP, sighup_function, NULL); </source> 등록한 시그널 콜백은 시그널이 발생될때 이벤트 루프에서 호출된다. 따라서 시그널 콜백 사용시, POSIX 시그널 핸들러에 아무처리를 하지않도록 따로 설정해놓는 것이 안전하다.

signal event 등록은 위한 매크로 역시 존재한다.

  • Interface

<source lang=c>

  1. define evsignal_add(ev, tv) \
   event_add((ev), (tv))
  1. define evsignal_del(ev) \
   event_del(ev)
  1. define evsignal_pending(ev, what, tv_out) \
   event_pending((ev), (what), (tv_out))

</source>

  • Caveats when working with signals
Libevent 에서 시그널을 등록해서 사용할 때 주의해야할 점이 있다. 프로세스당 오직 하나의 event_base 에서만 시그널을 수신할 수 있다는 것이다. 만약 하나의 프로세스에서 여러개의 event_base 를 생성하고 각각의 event_base 마다 시그널을 등록했을 경우, 오직 하나의 event_base 에서만 시그널을 수신하게 된다는 것이다. 시그널의 번호가 모두 달라도 안된다.
단, kqueue 에서는 제약이 없다.

Setting up events without heap-allocation

성능, 혹은 다른 이유로 힙영역 이외의 다른 곳에 이벤트 메모리를 관리할 수 있다. 이 기능을 사용하게 되면 다음의 문제들을 관리할 수 있다.

  • 작은 양의 메모리를 heap 영역에 할당하게 되면 오버헤드가 발생한다.
  • struct event pointer 를 역 조회하게되면 time overhead 가 발생한다.
  • 만약 이벤트가 캐시에 없는 경우, 이를 위한 time-overhead 가 발생할 수 있다.

이를 위해서는 event_assign() 을 사용하면 된다. 하지만 이를 사용하게 되면 다른 버전의 Libevent 와의 바이너리 호환성을 깨트릴 수 있다. 왜냐하면 이벤트 구조체의 사이즈가 달라질 수 있기 때문이다.

위에 나열한 문제들은 굉장히 작은 문제들이다. 그리고 거의 대부분의 어플리케이션에서는 문제되지 않는다. 이벤트 메모리를 다른 곳에 관리함으로써 생기는 문제점들에 대해 정확히 알고있지 않은 한, event_new() 를 사용하길 바란다. event_assign()을 사용하게 되면 다른 버전의 Libevent(만약 이벤트 구조체 크기가 달라졌다면)와 연동했을 경우, 에러를 찾기 힘들어진다.

  • Interface

<source lang=c> int event_assign(struct event *event,

   struct event_base *base,
   evutil_socket_t fd, 
   short what,
   void (*callback)(evutil_socket_t, short, void *), 
   void *arg);

</source> event 인자값을 제외한, 모든 인자값은 event_new() 에서 사용하는 것과 같다. 성공시 0, 실패시 -1을 리턴한다.

  • Example

<source lang=c>

  1. include <event2/event.h>

/* Watch out! Including event_struct.h means that your code will not

* be binary-compatible with future versions of Libevent. */
  1. include <event2/event_struct.h>
  2. include <stdlib.h>

struct event_pair {

   evutil_socket_t fd;
   struct event read_event;
   struct event write_event;

}; void readcb(evutil_socket_t, short, void *); void writecb(evutil_socket_t, short, void *); struct event_pair *event_pair_new(struct event_base *base, evutil_socket_t fd) {

   struct event_pair *p = malloc(sizeof(struct event_pair));
   if(!p) return NULL;
   p->fd = fd;
   event_assign(&p->read_event, base, fd, EV_READ|EV_PERSIST, readcb, p);
   event_assign(&p->write_event, base, fd, EV_WRITE|EV_PERSIST, writecb, p);
   return p;

} </source>

  • Warning

절대로 pending 중인 이벤트에 대해 event_assign() 를 사용하면 안되다. 만약 그런 일을 하게되면 정말로 오류가 발생해도 원인을 찾을 수 없게 된다. 만약 이미 Initialized 됐고, pending 상태의 이벤트라면, event_del() 을 먼저 사용하고, event_assign()을 사용해야 한다.

assign 과 관련된 매크로 함수들 역시 존재한다.

  • Interface

<source lang=c>

  1. define evtimer_assign(event, base, callback, arg) \
   event_assign(event, base, -1, 0, callback, arg)
  1. define evsignal_assign(event, base, signum, callback, arg) \
   event_assign(event, base, signum, EV_SIGNAL|EV_PERSIST, callback arg)

</source>

event_assign() 을 사용하면서도 추후의 Libevent 라이브러리와 바이너리 호환성을 유지해야 한다면, 사용중인 라이브러리의 struct event 크기를 확인할 수 있다.

  • Interface

<source lang=c> size_t event_get_struct_event_size(void); </source>

이 함수는 현재 사용중인 이벤트 구조체의 바이트 크기를 리턴한다. This function returns the number of bytes you need to set aside for a struct event. As before, you should only be using this function if you know that heap-allocation is actually a significant problem in your program, since it can make your code much harder to read and write.

Note that event_get_struct_event_size() may in the future give you a value smaller than sizeof(struct event). If this happens, it means that any extra bytes at the end of struct event are only padding bytes reserved for use by a future version of Libevent.

Here’s the same example as above, but instead of relying on the size of struct event from event_struct.h, we use event_get_struct_size() to use the correct size at runtime.




References

<references />