Libevent R6: Bufferevents: concepts and basics

From 탱이의 잡동사니
Revision as of 15:43, 3 February 2015 by Pchero (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Overview

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

대부분의 경우, 어플리케이션들은 이벤트의 응답을 기다리기보다는, 버퍼의 일부분을 직접 운용하는 것을 선호한다. 예를 들어 우리가 데이터를 write 한다면 다음과 같은 패턴을 가질 수가 있다.

  • 어떤 데이터를 버퍼에 쓰기 할 것인지 결정한다.(데이터를 버퍼에 입력한다.)
  • 커넥션이 쓰기 가능해질때 까지 기다린다.
  • 쓰기 할 수 있는 만큼 데이터를 입력한다.
  • 얼마나 입력했는지를 기억하고, 만약 쓰기 해야할 데이터 양이 더 남았다면, 다음 쓰기 가능 해질 때까지 기다린다.

Libevent 에서 사용하는 대부분의 buffer IO 패턴은 위와 같은 buffer IO 패턴으로 작동한다. "bufferevent" 는 기반 Transport(소켓과 같은..), 읽기 버퍼, 쓰기 버퍼 로 이루어져 있다. 이 경우, Libevent는 일반적인 이벤트대신에, 기반 Transport가 읽기/쓰기 준비가 되었을때 사용자가 정의한 Callback 을 호출하는 기능을 제공한다.

여러가지 bufferevent type이 있는데, 모든 bufferevent type들은 대부분의 같은 interface 를 가지고 있다. 다음과 같은 타입들이 있다.

  • socket-based bufferevents
기반 stream socket 의 send/receive 데이터를 담당하는 bufferevent 이다. event_* 로 시작하는 인터페이스를 제공한다.
  • asynchronous-IO bufferevents
윈도우즈 IOCP 에서의 기반 stream socket 의 send/receive 데이터를 담당하는 bufferevent 이다.(Windows only)
  • filtering bufferevents
기반 stream socket 으로 send/receive 하기 전 선행되는 bufferevent(예를 들면, 데이터 압축/변환)
  • paired bufferevents
다른 곳으로 데이터 송/수신하는 bufferevent 두 개.

모든 bufferevent 타입들에 대해, 모든 인터페이스들을 완벽히 지원하지는 않는다. 따라서 아래 설명되는 인터페이스들의 사용시 주의를 해야한다.

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

Bufferevents and evbuffers

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

Callbacks and watermarks

모든 bufferevent 는 data-related read/write 콜백을 가진다. 기본적으로, read 콜백은 어떤때든지 기반 transport 에서 read 할 데이터가 있을 때 호출되고, write 콜백은 어떤 때든지 기반 transport 에서 write 할 데이터가 있을 때 기반 transport 로 모두 전송될 때까지 호출된다. 이런 함수들은 read/wirte "watermarks" 를 하기위한 오버라이딩이 가능하다.

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

  • Read low-water mark
bufferevent read 시, input buffer에 Read low-water mark에 설정된 레벨이상의 데이터가 채워지면 bufferevent 의 read callback 이 호출된다. 기본 설정 값은 0이다. 따라서 어떤 양의 데이터라도 인입이 되면 호출된다.
  • Read high-water mark
bufferevent read 시, input buffer 에 Read high-water mark 에 설정된 레벨만큼 데이터가 채워지면 bufferevent는 input buffer 가 충분히 비워질때까지 read 을 중지한다. input buffer가 비워지면 다시 read 를 시작한다. 기본값은 unlimited 이다. 따라서 멈춤없이 계속해서 input buffer를 계속 읽어들인다.
  • Write low-water mark
bufferevent write 시, write low-water mark 에 설정된 레벨보다 낮은 레벨의 데이터가 입력되면 write callback 을 호출된다. 기본 설정값은 0이다. 따라서 output buffer 가 완전히 비워지기 전까지는 호출되지 않는다.
  • Write high-water mark
bufferevent 에 의해서 직접적으로 사용되지는 않는다. 이 water mark 는 특별한 의미가 있는데, bufferevent가 다른 bufferevent 의 기반 transport 로 사용되는 경우이다. 아래 filtering bufferevent 부분을 참조바란다.

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

  • BEV_EVENT_READING
bufferevent 에서 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에서 connection 요청이 완료됐을 때.

Deferred callbacks

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

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

Option flags for bufferevents

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

  • BEV_OPT_CLOSE_ON_FREE
bufferevent 가 해제되었을 때, 기반 transport 역시 close 한다. 이와 반대로 기반 socket 이 close 되었을 때 기반 bufferevent 역시 해제된다.
  • BEV_OPT_THREADSAFE
bufferevent 사용시, 자동적으로 lock 을 설정하도록 한다. 다중 스레드에서도 thread-safe 해진다.
  • BEV_OPT_DEFER_CALLBACKS
이 플래그를 설정하게 되면, 모든 콜백들이 지연-호출 되게 된다.
  • BEV_OPT_UNLOCK_CALLBACKS
기본적으로 bufferevent 가 thread-safe 하기 위해서는 bufferevent 는 사용자 정의 콜백이 호출되는 동안에는 lock을 유지하고 있어야 한다. 이 옵션을 설정하게 되면 Libevent 로 하여금 bufferevent 의 사용자 정의 콜백이 호출될 때 lock을 해제하도록 한다.

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 을 설정한 상태라면, 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>

bufferevent_base_connect() 함수는 Libevent-2.0.2-alpha 버전 이후부터 사용가능하다. 그 이전버전이면, connect() 함수를 사용해야 한다. connect() 함수 호출 시, 연결이 완료되면 bufferevent 에서 write 이벤트로 연결이 완료되었다고 알려준다.

bufferevent_socket_connect()로 connect 시도시, 오직 BEV_EVENT_CONNECTED 이벤트만 수신할 수 있음을 알아두자. connect() 함수 사용시, write 이벤트로 결과를 알려준다.

connect() 함수를 사용했더라도, 연결 성공시 BEV_EVENT_CONNECTED 이벤트를 수신할 수 있다. connect() 함수 호출 후의 bufferevent_socket_connect(bev, NULL, 0) 함수 호출은 -1 리턴 값은 EAGAIN 혹은 EINPROGRESS errno 와 같은 에러 값을 나타낸다.

Launching connections by hostname

자주 hostname 으로 접속해야 할 경우가 있다. 다음의 인터페이스를 사용하면 된다.

  • Interface

<source lang=c> int bufferevent_socket_connect_hostname(struct bufferevent *bev,

   struct evdns_base *dns_base, int family, const char *hostname,
   int port);

int bufferevent_socket_get_dns_error(struct bufferevent *bev); </source>

이 함수는 hostname 을 주어진 주소 family(AF_INET, AF_INET6, AF_UNSPEC) 타입으로 분석을 한다. 만약 주소 변환이 실패하면, 에러 이벤트 콜백을 호출한다. 성공시 bufferevent_connect() 로 연결을 시도한다.

dns_base 인자는 옵션값이다. NULL 로 설정되면, 주소 변환이 끝날때 까지, block 이 된다. 만약 비동기 방식으로 동작하게 하고 싶다면 적절한 인자 값을 넣어주면 된다. 후에 다시 다루게 될 것이다.

bufferevent_socket_connect() 함수와 마찬가지로, Libevent 내부적으로 성공적으로 연결이 되기 전까지는 해당 소켓에 대해 read/write 를 할 수 없도록 설정된다.

에러 발생시, 아마 대부분은 DNS looup error 를 리턴할 것이다. 가장 최근의 에러를 확인하고 싶다면 bufferevent_socket_get_dns_error() 를 호출하면 된다. 리턴값이 0이라면 현재 아무런 에러가 없다는 뜻이다.

  • Example: Trival HTTP v0 client

<source lang=c> /* Don't actually copy this code: it is a poor way to implement an

* HTTP clint. Have a look at evhttp instead.
*/
  1. include <event2/dns.h>
  2. include <event2/bufferevent.h>
  3. include <event2/buffer.h>
  4. include <event2/util.h>
  5. include <event2/event.h>
  1. include <stdio.h>

void readcb(struct bufferevent *bev, void *ptr) {

   char buf[1024];
   int n;
   struct evbuffer *input = bufferevent_get_input(bev);
   while((n = evbuffer_remove(input, buf, sizeof(buf))) > 0) {
       fwrite(buf, 1, n, stdout);
   }

}

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

   if(events & BEV_EVENT_CONNECTED) {
       printf("Connect okay.\n");
   } else if(events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
       struct event_base *base = ptr;
       if(events & BEV_EVENT_ERROR) {
           int err = bufferevent_socket_get_dns_error(bev);
           if(err)
               printf("DNS error: %s\n", evutil_gai_strerror(err));
       }
       printf("Closing\n");
       bufferevent_free(bev);
       event_base_loopexit(base, NULL);
   }

}

int main(int argc, char **argv) {

   struct event_base *base;
   struct evdns_base *dns_base;
   struct bufferevent *bev;
   
   if(argc != 3) {
       printf("Trivial hTTP 0.x client\n"
           "Syntax: %s [hostname] [resource]\n"
           "Example: %s www.google.com /\n", argv[0], argv[0]);
       return 1;
   }
   
   base = event_base_new();
   dns_base = evdns_base_new(base, 1);
   
   bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
   bufferevent_setcb(bev, readcb, NULL, eventcb, base);
   bufferevent_enable(bev, EV_READ|EV_WRITE);
   evbuffer_add_printf(bufferevent_get_output(bev), "GET %s\r\n", argv[2]);
   bufferevent_socket_connect_hostname(
       bev, dns_base, AF_UNSPEC, argv[1], 80);
   event_base_dispatch(base);
   return 0;

} </source>

Generic bufferevent operations

여기에서 소개하는 함수들은 다중 bufferevent 환경에서도 동작하는 함수들이다.

Freeing a bufferevent

  • Interface

<source lang=c> void bufferevent_free(struct bufferevent *bev); </source> 위 함수는 생성한 bufferevent를 해제할 때 사용하는 함수이다. Bufferevent 는 내부적으로 관리되는데, 만약 bufferevent 를 해제했을 때, 이미 pending 중인 콜백에서 사용중이라면, 해당 콜백이 종료될 때 까지 실제로 삭제되지 않는다. 하지만, bufferevent_free() 함수를 사용하면 가능한 즉시 해당 bufferevent를 해제한다. 만약 bufferevent에 쓰기 대기중인 데이터가 있었더라도, bufferevent 해제가 먼저 실행될 것이다.

만약 BEV_OPT_CLOSE_ON_FREE 플래그가 설정되었고, 소켓 혹은 기반 transport 와 연동되어있다면, bufferevent 해제시에 같이 close 된다.

Manipulating callbacks, watermarks, and enabled operations

  • Interface

<source lang=c> typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx); typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);

void bufferevent_setcb(struct bufferevent *bufev,

   bufferevent_data_cb readcb, bufferevent_data_cb writecb,
   bufferevent_event_cb eventcb, void *cbarg);

void bufferevent_getcb(struct bufferevent *bufev,

   bufferevent_data_cb *readcb_ptr,
   bufferevent_data_cb *write_prt,
   bufferevent_event_cb *eventcb_ptr,
   void **cbarg_ptr);

</source> bufferevent_setcb() 함수는 하나 이상의 bufferevent callback 함수를 변경할 수 있다. reacb, writecb, eventcb 에 설정된 함수들은 각각 읽기, 쓰기, 이벤트 등이 발생하면 호출된다. 첫번째 인자 bufev 는 이벤트가 발생하는 bufferevent 를 가리킨다. 마지막 인자는 bufferevent_callcb 에서 사용되는 cbarg 인자를 가리키는데, 콜백함수에 특정 데이터를 넘겨주는 용도로 활용할 수 있다. events 인자는 이벤트 플래그의 비트마스크를 의미하는데, 자세한 내용은 "callbacks and watermarks"를 참고하기 바란다.

또한, 콜백을 비활성화 할 수도 있는데, 콜백함수를 지정하는 대신에 NULL 로 지정해주면 된다. 주의할 점은, 모든 콜백 함수들이 cbarg 값을 공유한다는 것이다. 따라서 만약 이 값을 변경하게 되면 모든 콜백 함수에 영향을 주게 된다.

bufferevent_getcb() 함수를 통해서 현재 bufferevent에 설정된 콜백 함수를 가져올 수 있는데, *readcb_ptr 에는 read 콜백, *writecb_ptr 에는 write 콜백, *eventcb_ptr 에는 event 콜백, *cbarg_ptr 에는 설정된 인자값이 설정된다. NULL 지정된 인자값들은 무시된다.

  • Interface

<source lang=c> void bufferevent_enable(struct bufferevent *bufev, short events); void bufferevent_disable(struct bufferevent *bufev, short events);

short bufferevent_get_enabled(short bufferevent *bufev); </source>

bufferevent 의 EV_READ, EV_WRITE, EV_READ|WRITE 이벤트를 활성화/비활성화 할 수 있다. 읽기/쓰기가 비활성화 된 상태에서는 read/write를 시도하지 않는다. 그리고 output buffer가 없을 경우에는 따로 쓰기 비활성화를 할 필요는 없다: output buffer가 비워졌을 시, bufferevent는 자동적으로 쓰기를 중지한다. 이후 다시 output buffer에 데이터가 채워지면 다시 쓰기를 시작한다.

비슷하게, input buffer가 high-water mark 레벨만큼 채워졌을 경우, 별도로 읽기 비활성화를 할 필요가 없다: 자동적으로 읽기를 중지하며 해당 버퍼가 충분히 비워지면 다시 읽기를 시작한다.

bufferevent_get_enabled() 를 호출해서 현재 활성화된 bufferevent event 들을 확인할 수 있다.

  • Interface

<source lang=c> void bufferevent_setwatermark(struct bufferevent *bufev, short events,

   size_t lowmark, size_t highmark);

</source> bufferevent_setwatermakr() 함수는 read/write watermark 를 설정하는 함수이다. (event 인자값으로 EV_READ 설정시 read watermark 를 설정하게 되고, EV_WRITE 설정시 write watermark 를 설정하게 된다)

high-water mark 를 0으로 설정하면 "unlimited"를 의미한다.

  • Example

<source lang=c>

  1. include <event2/event.h>
  2. include <event2/bufferevent.h>
  3. include <event2/buffer.h>
  4. include <event2/util.h>
  1. include <stdlib.h>
  2. include <errno.h>
  3. include <string.h>

struct info {

   const char *name;
   size_t total_drained;

};

void read_callback(struct bufferevent *bev, void *ctx) {

   struct info *inf = ctx;
   struct evbuffer *input = bufferevent_get_input(bev);
   size_t len = evbuffer_get_length(input);
   if(len) {
       inf->total_drained += len;
       evbuffer_drain(input, len);
       printf("Drained %lu bytes from %s\n", (unsigned long) len, inf->name);
   }

}

void event_callback(struct bufferevent *bev, short events, void *ctx) {

   struct info *inf = ctx;
   struct evbuffer *input = bufferevent_get_input(bev);
   int finished = 0;
   
   if(events & BEV_EVENT_EOF) {
       size_t len = evbuffer_get_length(input);
       printf("Got a close from %s. We drained %lu bytes from it, and have %lu left.\n", 
           inf->name,
           (unsigned long)inf->total_drained,
           (unsigned long)len);
       finished = 1;
   }
   if(events & BEV_EVENT_ERROR) {
       printf("Got an error ffrom %s: %s\n",
           inf->name, evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
       finished = 1;
   }
   if(finished) {
       free(ctx);
       bufferevent_free(bev);
   }

}

struct bufferevent *setup_bufferevent(void) {

   struct bufferevent *b1 = NULL;
   struct info *info1;
   
   info1 = malloc(sizeof(struct info));
   info1->name = "buffer 1";
   info1->total_drained = 0;
   
   /* ... Here we should set up the bufferevent and make sure it gets
    * connected... */
   /* Trigger the read callback only whenever there is at least 128 bytes
    * of data in the buffer. */
   bufferevent_setwatermark(b1, EV_READ, 128, 0);
   
   bufferevent_setcb(b1, read_callback, NULL, event_callback, info1);
   
   bufferevent_enable(b1, EV_READ);    /* Start reading. */
   return b1;

} </source>

Manipulating data in a bufferevent

Reading and writing data from the network does you no good if you can’t look at it. Bufferevents give you these methods to give them data to write, and to get the data to read:

  • Interface

<source lang=c> struct evbuffer *bufferevent_get_input(struct bufferevent *bufev); struct evbuffer *bufferevent_get_output(struct bufferevent *bufev); </source>

위의 두 함수는 매우 강력한 기본 함수이다. 각각 input/output buffer를 리턴한다. evbuffer로 할 수 있는 모든 기능들은 다음 장에서 소개될 것이다.

input buffer 에서는 삭제(추가가 아님)만 하고, output buffer에서는 추가(삭제가 아님)만 한다는 것에 유의하자.

쓰기하는 데이터가 너무 작거나, 읽기하는 데이터가 너무 크다면 작업이 잠시 멈출 수 있다. 하지만 데이터가 더 채워지거나 줄어든다면 곧 다시 작업을 개시할 것이다.

  • Interface

<source lang=c> int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size); int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf); </source>

위 함수들은 bufferevent의 output buffer에 데이터를 추가하는 함수들이다. bufferevent_Write() 는 지정된 size 바이트 크기만큼 add 버퍼 데이터를 output buffer 의 끝에 추가한다. bufferevent_write_buffer()는 buf 의 데이터를 모두 삭제하고 output buffer 에 끝에 추가한다. 성공시 0, 실패시 -1을 리턴한다.

  • Interface

<source lang=c> size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size); int bufferevent_read_buffer(struct bufferevent *bufev, struct evbuffer *buf); </source>

위 함수들은 bufferevent의 input buffer에서 데이터를 삭제하는 함수들이다. bufferevent_read() 함수는 input buffer 에서 size 바이트 크기만큼 데이터를 삭제하고, data 버퍼에 입력한다. 성공시 삭제된 바이트 크기를 리턴한다. bufferevent_read_buffer() 함수는 input buffer의 모든 데이터삭제하고 buf 에 입력한다. 성공시 0, 실패시 -1을 리턴한다.

bufferevent_read() 사용시, data에는 지정된 size 바이트 크기 만큼의 크기를 가져야 한다.

  • Example

<source lang=c>

  1. include <event2/bufferevent.h>
  2. include <event2/buffer.h>
  1. include <ctype.h>

void read_callback_uppercase(struct bufferevent *bev, void *ctx) {

   /* This callback removes the data from bev's input buffer 128
    * bytes at a time, uppercases it, and start sending it
    * back
    * 
    * (Watch out! In practice, you shouldn't use toupper to implement
    * a network protocol, unless you know for a fact that the current
    * locale is the one you want to be using.)
    */
   char tmp[128];
   size_t n;
   int i;
   while(1) {
       n = bufferevent_read(bev, tmp, sizeof(tmp));
       if(n <= 0)
           break;  /* No more data. */
       for(i = 0; i < n; ++i)
           tmp[i] = toupper(tmp[i]);
       bufferevent_write(bev, tmp, n);
   }

}

struct proxy_info {

   struct bufferevent *other_bev;

};

void read_callback_proxy(struct bufferevent *bev, void *ctx) {

   /* You might use a function like this if you're implementing
    * a simple proxy: it will take data form one connction (on
    * bev), and wirte it to another, copying as little as
    * possible. */
   struct proxy_info *inf = ctx;
   
   bufferevent_read_buffer(bev, bufferevent_get_output(inf->other_bev));

}

struct count {

   unsigned long last_fib[2];

};

void write_callback_fibonacci(struct bufferevent *bev, void *ctx) {

   /* Here's a callback that adds some Fibonacci numbers to the
    * output buffer of bev. It stops once we have added 1k of
    * data; once this data is drained, we'll add more. */
   struct count *c = ctx;
   
   struct evbuffer *tmp = evbuffer_new();
   while(evbuffer_get_length(tmp) < 1024) {
       unsigned long next = c->last_fib[0] + c->last_fib[1];
       c->last_fib[0] = c->last_fib[1];
       c->last_fib[1] = next;
       
       evbuffer_add_printf(tmp, "%lu", next);
   }
   
   /* Now we add the whole contents of tmp to bev */
   bufferevent_write_buffer(bev, tmp);
   
   /* We don't need tmp any longer. */
   evbuffer_free(tmp);

</source>

Read and write timeouts

다른 이벤트들처럼, bufferevent 에도 timeout 을 설정할 수 있다.

  • Interface

<source lang=c> void bufferevent_set_timeout(struct bufferevent *bufev,

   const struct timeval *timeout_read, const struct timeval *timeout_write);

</source>

timeout을 NULL로 설정하게되면, 설정된 timeout을 삭제하는 것으로 간주한다. 그러나 Libevent 2.1.2-alpha 이전 버전에서는 특정 데이터 타입에 대해서 잘 작동 안 할 수도 있다. (오래된 버전 사용시, timeout 에 굉장히 큰 값을 지정해주거나 eventcb 에 BEV_TIMEOUT 을 무시하도록 설정하는 방법을 사용할 수 있다.)

bufferevent 에서 read 작업시 timeout_read 초 이상의 시간이 넘어가면 read timeout 이 발생된다. 마찬가지로, bufferevent 에서 write 작업시, timeout_write 초 이상의 시간이 넘어가면 write timeout 이 발생한다.

위 timeout 들은 오직 bufferevent가 read/write 할 때만 동작한다는 것을 알아두자. 달리 말하면, read timeout 은 read 비활성화 상태이거나 input buffer가 full 인 상태(high-water mark만큼 데이터가 있는 상태)에서는 동작하지 않는다. 이와 마찬가지로, write timeout 은 write가 비활성화 상태이거나, write 할 데이터가 없는 상태에서는 동작하지 않는다.

bufferevent에서 read/write timeout 이 발생했을 때, read/write 작동이 중지된다. 대신 BEV_EVENT_TIMEOUT|BEV_EVENT_READING 혹은 BEV_EVENT_TIMEOUT|BEV_EVENT_WRITING 으로 설정된 이벤트 콜백이 호출된다.

Initiating a flush on a bufferevent

  • Interface

<source lang=c> int bufferevent_flush(struct bufferevent *bufev,

   short iotype, enum bufferevent_flush_mode state);

</source> bufferevent를 flush 한다는 뜻은 다른 제약 조건들을 모두 무시하고 강제로 가능한 최대의 byte를 기반 transport 로 읽기/쓰기 한다는 뜻이다. 세부적으로는 bufferevent 의 타입에 따라 달리 구현된다.

iotype 인자는 반드시 EV_READ, EV_WRITE 혹은 EV_READ|EV_WRITE 셋 중 하나여야 하며, 각각 읽기, 쓰기, 읽기/쓰기 중 어느 것의 buffer 를 flush 할 것인지를 의미한다. state 인자는 BEV_NORMAL, BEV_FLUSH, BEV_FINISHED 중 하나가 올 수 있다. BEV_FINISHED 는 상대편에 더이상 전송한 데이터가 없다고 알려주는 것이고, BEV_NOMARL과 BEV_FLUSH 의 차이점은 bufferevent 의 타입이 다르다는 것이다.

실패시 -1, flush 할 데이터가 없다면 0, flush 성공시 flush 한 데이터 양을 리턴한다.

Type-specific bufferevent functions

모든 bufferevent types 을 지원하지는 않는다.

  • Interface

<source lang =c> int bufferevent_priority_set(struct bufferevent *bufev, int pri); int bufferevent_get_priority(struct bufferevent *bufev); </source> 위 함수들은 buffeerevent 우선순위를 설정하는 함수이다. 자세한 정보는 event_priority_set() 함수를 참조바란다.

성공시 0, 실패시 -1을 리턴한다. socket-based bufferevents 에서만 동작한다.

  • Interface

<source lang=c> int bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd); evutil_socket_t bufferevent_getfd(struct bufferevent *bufev); </source> 위 함수들은 fd-based 이벤트들의 파일 디스크립터를 설정하거나 리턴하는 함수들이다. bufferevent_setfd() 함수는 Socket-based bufferevent 에서만 동작한다. 성공시 0(setfd), 실패시 -1을 리턴한다.

  • Interface

<source lang=c> struct event_base *bufferevent_get_base(struct bufferevent *bev); </source> bufferevent 가 설정되어있는 event_base 를 리턴한다.

  • Interface

<source lang=c> struct bufferevent *bufferevent_get_underlying(struct bufferevent *bufev); </source> 기반 transport 로 사용중인 bufferevent 를 리턴한다(만약 사용중이라면). 자세한 설명은 filtering bufferevents 부분을 참조바란다.

Manually locking and unlocking a bufferevent

evbuffers를 사용한다면, bufferevent 내에서도 각각 별개로 작동해야하는 명령들이 필요할 때가 있다. Libevent 에서는 이를 위한 locking/unlocking 관련 함수를 제공한다.

  • Interface

<source lang=c> void bufferevent_lock(struct bufferevent *bufev); void bufferevent_unlock(struct bufferevent *bufev); </source> 만약 Libevent에서 스레드 지원이 활성화 되어있지 않거나, 스레드 생성 전에, bufferevent에 BEV_OPT_THREADSAFE 를 설정하지 않았다면 locking 이 불가능하다.

bufferevent locking 을 하게되면 연결되어 있는 evbuffer 에도 동일하게 적용된다. 이 함수들은 재귀적으로 작동한다: 한번 locking 한 eventbuffer에 대해서 다시 locking 을 시도해도 괜찮다. 당연하겠지만, bufferevent 를 lock 을 할 때마다 반드시 unlock으로 풀어주어야 한다.



References

<references />