Libevent R6: Bufferevents: concepts and basics

From 탱이의 잡동사니
Revision as of 15:24, 2 February 2015 by Pchero (talk | contribs)
Jump to navigation Jump to search

Overview

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

때로는 이벤트의 응답을 기다리기보다, 버퍼의 일부분을 직접 확인할 수 있는 기능이 필요할 때가 있다. 예를 들어 우리가 데이터를 write 한다면 다음과 순서를 가질 수가 있다.

  • Decide that we want to write some data to a connection; put that data in a buffer.
  • Wait for the connection to become writable.
  • Write as much of the data as we can.
  • Remember how much we wrote, and if we still have more data to write, wait for the connection to become writable again.

This buffered IO pattern is common enough that Libevent provides a generic mechanism for it. A "bufferevent" consists of an underlying transport (like a socket), a read buffer, and a write buffer. Instead of regular events, which give callbacks when the underlying transport is ready to be read or written, a bufferevent invokes its user-supplied callbacks when it has read or written enough data.

There are multiple types of bufferevent that all share a common interface. As of this writing, the following types exist:

  • socket-based bufferevents
A bufferevent that sends and receives data from an underlying stream socket, using the event_* interface as its backend.
  • asynchronous-IO bufferevents
A bufferevent that uses the Windows IOCP interface to send and receive data to an underlying stream socket. (Windows only; experimental.)
  • filtering bufferevents
A bufferevent that processes incoming and outgoing data before passing it to an underlying bufferevent object—for example, to compress or translate data.
  • paired bufferevents
Two bufferevents that transmit data to one another.

현재 bufferevents 인터페이스는 스트림 기반 프로토콜(TCP 같은..)에서만 작동한다. UDP 같은 프로토콜은 후에 추가될 예정이다.

Bufferevents and evbuffers

모든 bufferevent는 input buffer 와 output buffer를 가진다. struct evbuffer 타입이다. write 하고자 하는 데이터가 있다면 output buffer에 입력하고, read 하고 싶은 데이터가 있다면 input buffer 로부터 읽어 들이면 된다.

Callbacks and watermarks

모든 bufferevent 는 data-related read/write 콜백을 가진다. 기본적으로, read 콜백은 어떤때든지 데이터를 read 할 때 호출되고, write 콜백은 데이터를 write 가능 할 때(output buffer 가 write 해도 충분할 정도로 비워졌을때) 호출된다. 이 함수들은 오버라이딩해서 "워터마크"를 읽기/쓰기 를 하도록 수정 가능하다.

모든 bufferevent 들은 4개의 "워터 마크"를 가진다.

  • Read low-water mark
여기에 설정된 레벨 이상의 데이터가 채워지면 해당 bufferevent 의 read 콜백 함수가 호출된다. 기본값은 0 이다. 따라서 어떤 경우에도 read 콜백이 호출된다.
  • Read high-water mark
If the bufferevent’s input buffer ever gets to this level, the bufferevent stops reading until enough data is drained from the input buffer to take us below it again. Defaults to unlimited, so that we never stop reading because of the size of the input buffer.
  • Write low-water mark
Whenever a write occurs that takes us to this level or below, we invoke the write callback. Defaults to 0, so that a write callback is not invoked unless the output buffer is emptied.
  • Write high-water mark
Not used by a bufferevent directly, this watermark can have special meaning when a bufferevent is used as the underlying transport of another bufferevent. See notes on filtering bufferevents below.

이뿐만이 아니라, bufferevent 는 non-data-oriented 이벤트를 위한 "에러" 혹은 "이벤트" 를 위한 콜백을 가지고 있다. 예를 들면 연결이 에러로 인해 종료가 되었을 때 사용된다. 다음의 Flag 들을 설정할 수 있다.

  • BEV_EVENT_READING
eventbuffer 에서 read 작업 중 발생하는 이벤트.
  • BEV_EVENT_WRITING
eventbuffer에서 write 작업 중 발생하는 이벤트.
  • BEV_EVENT_ERROR
eventbuffer 작업중 붕 발생하는 에러. 발생된 에러에 대한 더 자세한 정보를 확인하고 싶다면 EVUTIL_SOCKET_ERROR() 함수를 호출하면 된다.
  • BEV_EVENT_TIMEOUT
bufferevent 에서 timeout 발생
  • BEV_EVENT_EOF
bufferevent 에서 End-of-file 발생
  • BEV_EVENT_CONNECTED
bufferevent 에서의 연결 요청 완료 발생.

Deferred callbacks

기본적으로 bufferevent 콜백은 해당 조건이 충족되면, 그 즉시 호출된다.(이 부분은 evbuffer 역시 동일하다). 이런 즉각 호출은 해당 bufferevent의 의존성으로 인해 문제를 야기할 수 있다. 예를 들어 evbuffer A 로 데이터를 move 하는 콜백이 있다고 가정해보자. 그리고 evbuffer A가 비게되면 다른 콜백이 evbuffer A에 데이터를 입력하자고 해보자. 그렇게되면 모든 콜백들은 스택에 쌓이기 시작하여 마친되는 stack overflow 를 일으킬 것이다.

이를 해결하기 위해서 bufferevent(혹은 evbuffer)의 콜백이 뒤늦게 동작하도록 설정할 수 있다. 콜백이 즉시 호출 대신, 지연 호출되게 되면, 이벤트 루프에 쌓이게 되고, 후에 차례가 왔을 때, 보통의 이벤트 콜백 처럼 사용할 수 있다.

Option flags for bufferevents

bufferevent 생성시, 하나의 혹은 그 이상의 flag를 조합하여 동작 내용을 변경할 수 있다.

  • BEV_OPT_CLOSE_ON_FREE
When the bufferevent is freed, close the underlying transport. This will close an underlying socket, free an underlying bufferevent, etc.
  • BEV_OPT_THREADSAFE
Automatically allocate locks for the bufferevent, so that it’s safe to use from multiple threads.
  • BEV_OPT_DEFER_CALLBACKS
When this flag is set, the bufferevent defers all of its callbacks, as described above.
  • BEV_OPT_UNLOCK_CALLBACKS
By default, when the bufferevent is set up to be threadsafe, the bufferevent’s locks are held whenever the any user-provided callback is invoked. Setting this option makes Libevent release the bufferevent’s lock when it’s invoking your callbacks.

Working with socket-based bufferevents

가장 쉽게 bufferevents 를 사용하는 방법은 그냥 socket-based 타입으로 활용하는 것이다. socket-based 타입의 bufferevent 는 기반 네트워크 소켓이 read/write 명령을 받아들인 준비가 되었는지를 탐지하기 위해 Libevent 의 기반 이벤트 메커니즘을 이용하고, 데이터 송/수신을 위해 기반 네트워크 콜(readv, writev, WSASend, WSARecv)를 사용한다.

Creating a socket-based bufferevent

bufferevent_socket_new() 를 이용하면 socket-based bufferevent 를 생성할 수 있다.

  • Interface

<source lang=c> struct bufferevent *bufferevent_socket_new(

   struct event_base *base,
   evutil_socket_t fd,
   enum bufferevent_options options);

</source>

base 는 event_base, options 는 bufferevent option(BEV_OPT_CLOSE_ON_FREE etc) 의 비트마스트이다. fd 인자는 소켓 파일 디스크립터이더. 나중에 설정하고 싶다면 -1로 설정하면 된다.

bufferevent_socket_new 에서 사용할 socket 설정시, 반드시 non-blocking 모드로 설정해야 한다. non-blocking 모드 설정시, evutil_make_socket_nonblocking() 을 사용하면 된다.

성공시 bufferevent 를 리턴하고, 실패시 NULL 을 리턴한다.

Launching connections on socket-based bufferevents

만약 bufferevent socket이 connection 상태가 아니라면, 새롭게 연결을 설정할 수 있다.

  • Interface

<source lang=c> int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen); </source> address, addrlen 인자 값은 표준 함수 connect()에서 사용되는 것과 같다. 만약 아직 bufferevent에 socket 설정이 안되어 있는 상태라면 이 함수를 호출해서 새로운 socket 을 생성하고 nonblocking 설정까지 같이 해 줄 수 있다.

만약 bufferevent 에 이미 socket 을 설정한 상태라면, bufferevent_socket_connect() 함수를 호출해서 Libevent에 socket 이 아직 connected 상태가 아니고, connection 명령이 정상적으로 종료될 때 까지 read/write 해서는 안된다고 알려주어야 한다. 단, connection 이 종료되기 전, output buffer 에 데이터를 입력하는 것은 괜찬핟.

성공시 0, 실패시 -1을 리턴한다.

  • Example

<source lang=c>

  1. include <event2/event.h>
  2. include <event2/bufferevent.h>
  3. include <sys/socket.h>
  4. include <string.h>

void eventcb(struct bufferevent *bev, short events, void *ptr) {

   if(events & BEV_EVENT_CONNECTED) {
       /* We're connected to 127.0.0.1:8080. Ordinarily we'd do
        * something here, like start reading or writing. */
   } else if(events & BEV_EVENT_ERROR) {
       /* An error occured while connecting. */
   }

}

int main_loop(void) {

   struct event_base *base;
   struct bufferevent *bev;
   struct sockaddr_in sin;
   
   base = event_base_new();
   
   memset(&sin, 0, sizeof(sin));
   sin.sin_family = AF_INET;
   sin.sin_addr.s_addr = htonl(0x7f000001);    /* 127.0.0.1 */
   sin.sin_port = htons(8080); /* port 8080 */
   
   bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
   
   bufferevent_setcb(bev, NULL, NULL, eventcb, NULL);
   
   if(bufferevent_socket_connect(bev, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
       /* Error starting connection */
       bufferevent_free(bev);
       return -1;
   }
   
   event_base_dispatch(base);
   
   return 0;

} </source>



References

<references />