Libevent R7: Evbuffers: utility functionality for buffered IO

From 탱이의 잡동사니
Jump to navigation Jump to search

Overview

원문은 이곳<ref>http://www.wangafu.net/~nickm/libevent-book/Ref7_evbuffer.html</ref>애서 찾을 수 있다.

Libevent 에서의 evbuffer 는 Add to the end, Removing from the front 에 최적화된 byte Queue 로 구현되어 있다.

Evbuffers 는 buffered network IO 에서의 buffer 의 역할을 담당한다. evbuffer 자체적으로는 스케줄링이나 IO trigger 등을 제공하지는 않는다. 그런 것들은 bufferevent 에서 제공한다.

Creating or feeing an evbuffer

  • Interface

<source lang=c> struct evbuffer *evbuffer_new(void); void evbuffer_free(struct evguffer *buf); </source> evbuffer_new() 함수는 evbuffer 를 생성하고, evbuffer_free() 함수는 evbuffer 를 해제한다.

Evbuffers and Thread-safety

  • Interface

<source lang=c> int evbuffer_enable_locking(struct evbuffer *buf, void *lock); void evbuffer_lock(struct evbuffer *buf); void evbuffer_unlock(struct evbuffer *buf); </source> 기본적으로 여러개의 스레스들이 한번에 하나의 evbuffer 에 접근하는 것은 위험하다. 만약 다중 스레드 환경에서도 안전하게 사용하고 싶다면, evbuffer_enable_locking() 함수를 호출하면 된다. 만약 lock 인자가 NULL 이라면, Libevent는 내부적으로 evthread_set_lock_creation_callback 을 이용하여 새로운 lock 을 생성한 뒤 리턴한다. 만약 lock 인자값이 지정되어 있다면, 해당 인자값을 lock 으로 사용한다.

evbuffer_lock() 과 evbuffer_unlock() 함수는 각각 lock 을 할당하고 해제하는 역할을 한다. 각각 독립적으로 작동하도록 할 수 있는데, 만약 evbuffer 에서 locking 이 비활성화 되어 있는 상태라면 별 소용이 없다.

(Note that you do not need to call evbuffer_lock() and evbuffer_unlock() around individual operations: if locking is enabled on the evbuffer, individual operations are already atomic. You only need to lock the evbuffer manually when you have more than one operation that need to execute without another thread butting in.)

Inspecting an evbuffer

  • Interface

<source lang=c> size_t evbuffer_get_length(const struct evbuffer *buf); </source> evbuffer 에 저장된 데이터의 크기를 리턴한다.

  • Interface

<source lang=c> size_t evbuffer_get_contiguous_space(const struct evbuffer *buf); </source> 위 함수는 evbuffer 의 처음부분에서 연속적으로 저장된 데이터의 크기를 리턴한다. evbuffer 에 저장된 데이터들은 각각의 덩어리별로 구분되어 저장된다. 위 함수는 그 메모리 덩어리 중, 가장 첫 번째 덩어리의 크기를 리턴한다.

Adding data to an evbuffer: basics

  • Interface

<source lang=c> int evbuffer_add(struct evbuffer *buf, const void *data, size_t datlen); </source> datlen 크기 만큼의 데이터 data를 buf(제일 뒤쪽)에 입력한다. 성공시 0, 실패시 -1을 리턴한다.

  • Interface

<source lang=c> int evbuffer_add_printf(struct evbuffer *buf, const char *fmt, ...); int evbuffer_add_vprintf(struct evbuffer *buf, const char *fmt, va_list ap); </source> 위 함수들은 buf 의 끝단에 formatted data 를 붙이는 함수들이다. 각각의 formaated data는 C 라이브러리 printf 와 vprintf 의 사용법과 동일하다. 성공시 추가된 바이트 크기를 리턴한다.

  • Interface

<source lang=c> int evbuffer_expand(struct evbuffer *buf, size_t datlen); </source> 이 함수는 evbuffer의 마지막 메모리 부분을 변경하거나 새로운 메모리를 추가하는 함수이다.

  • Examples

<source lang=c>

/* Here are two ways to add "Hello world 2.0.1" to a buffer. */ /* Directly: */ evbuffer_add(buf, "Hello world 2.0.1", 17);

/* Via printf */ evbuffer_add_printf(buf, "Hello %s %d.%d.%d", "world", 2, 0, 1); </source>

Moving data from one evbuffer to another

Libevent에는 evbuffer 에서 다른 evbuffer 로 데이터를 이동하는데 최적화 된 함수가 있다. <source lang=c> int evbuffer_add_buffer(struct evbuffer *dst, struct evbuffer *src); int evbuffer_remove_buffer(struct evbuffer *src, struct evbuffer *dst, size_t datlen); </source> evbuffer_add_buffer() 함수는 src 에서 dst 로 모든 데이터를 옮기는 함수이다. 성공시 0, 실패시 -1 을 리턴한다.

evbuffer_remove_buffer() 함수는 src 에서 dst로 정확히 datlen 크기만큼의 데이터를 옮기는 함수이다. 만약 src 에 datlen 보다 작은 크기의 데이터가 있었다면, 모든 데이터를 옮기게 된다. 성공시 옮겨진 데이터의 바이트 크기가 리턴된다.

Adding data to the front of an evbuffer

  • Interface

<source lang=c> int evbuffer_prepend(struct evbuffer *buf, const void *data, size_t size); int evbuffer_prepend_buffer(struct evbuffer *dst, struct evbuffer* src); </source> 이 함수들은 각각 evbuffer_add() 와 evbuffer_add_buffer() 함수들과 비슷한 동작을 한다. 단, evbuffer 의 끝쪽에 데이터를 추가하는 것이 아닌, 제일 앞쪽에 데이터를 추가한다는 것이 다르다.

Rearranging the internal layout of an evbuffer

최초 몇 바이트의 데이터가 필요하거나, 연속적으로 정리된 상태로 데이터를 확인해야 할 경우가 있다. 이럴 경우, 먼저 데이터가 정말로 연속적으로 정렬이 되어 있는지를 확인해야 한다.

  • Interface

<source lang=c> unsigned char *evbuffer_pullup(struct evbuffer *buf, ev_ssize_t size); </source> evbuffer_pullup() 함수는 최초 size 바이트 만큼의 buf 데이터를 선형적으로 정렬된 시킨다. 만약 size 가 음수값이라면, 모든 데이터를 정렬시킨다. 그리고 size 가 실제 buf 크기보다 큰 값이면, NULL 을 리턴한다. 성공적으로 실행될 경우, 정렬된 최초 포인터를 리턴한다.

evbuffer_pullup() 함수 사용시 너무 큰 사이즈를 지정하게 되면 느려지게 된다. 왜냐하면 내부적으로 전체 버퍼를 복사하기 때문이다.

  • Example

<source lang=c>

  1. include <event2/buffer.h>
  2. include <event2/util.h>
  1. include <string.h>

int parse_socks4(struct evbuffer *buf, ev_uint16_t *port, ev_uint32_t *addr) {

   /* Let's parse the start of a SOCKS4 request! The format is easy:
    * 1 byte of version, 1 byte of command, 2 bytes destport, 4 bytes of
    * destip. */
   unsigned char *mem;
   
   mem = evbuffer_pullup(buf, 8);
   
   if(mem == NULL) {
       /* Not enough data in the buffer */
       return 0;
   } else if(mem[0] != 4 || mem[1] != 1) {
       /* Unrecognized protocol or command */
       return -1;
   } else {
       memcpy(port, mem + 2, 2);
       memcpy(addr, mem + 4, 4);
       *port = ntohs(*port);
       *addr = ntohl(*addr);
       /* Actually remove the data from the buffer now that we know we
        * like it. */
       evbuffer_drain(buf, 8);
       return 1;
   }

} </source> Calling evbuffer_pullup() with size equal to the value returned by evbuffer_get_contiguous_space() will not result in any data being copied or moved.

Removing data from an evbuffer

Adding data copies with evbuffer-based IO

정말로 빠른 네트워크 프로그램을 작성하가 위해서는 데이터 카피를 최소한으로 줄여야 한다. Libevent 에서는 이를 지원하는 방법을 제공한다.

  • Interface

<source lang =c> typedef void (*evbuffer_ref_cleanup_cb)(const void *data, size_t datalen, void *extra); int evbuffer_add_reference(struct evbuffer *outbuf, const void *data, size_t datlen, evbuffer_ref_cleanup_cb cleanupfn, void *extra); </source> 이 함수는 evbuffer 의 끝단에 참조로써 데이터를 추가하는 기능을 한다. 이 기능을 사용하면, 데이터 복사를 수행하지 않는 대신에 단지 datlen 에 지정된 바이트 크기 만큼의 데이터를 data 에 포인터로 넘겨준다. 그리고 넘겨진 버퍼는 evbuffer 에서 계속 사용하고 있는 한, 유효한 값을 가지게 된다. 그리고 evbuffer 를 더이상 사용 안하게 되면, cleanupfn 함수를 호출하게 되는데, data 포인터, datlen 값, 와 extra 포인터를 인자값으로 사용한다. 성공시 0, 실패시 -1을 리턴한다.

  • Example

<source lang=c>

  1. include <event2/buffer.h>
  2. include <stdlib.h>
  3. include <string.h>

/* In this example, we have a bunch of evbuffers that we want to use to

* spool a one-megabyte resource out to the network. We do this
* without kepping any more copies of the resource in memory than
* necessary. */
  1. define HUGE_RESOURCE_SIZE (1024 * 1024)

struct huge_resource {

   /* We keep a count of the references that exist to this structure,
    * so that we know when we can free it. */
   int reference_count;
   char data[HUGE_RESOURCE_SIZE];

};

struct huge_resource *new_resource(void) {

   struct huge_resource *hr = malloc(sizeof(struct huge_resource));
   hr->reference_count = 1;
   /* Here we should fill hr->data with something. In real life,
    * we'd probably load something or do a complex calculation.
    * Here, we'll just fill it with EEs. */
   memset(hr->data, 0xEE, sizeof(hr->data));
   return hr;

}

void free_resource(struct huge_resource *hr) {

   --hr->reference_count;
   if(hr->reference_count == 0)
   {
       free(hr);
   }

}

static void cleanup(const void *data, size_t len, void *arg) {

   free_resource(arg);

}

/* This is the function that actually adds the resource to the buffer */ void spool_resource_to_evbuffer(struct evbuffer *buf, struct huge_resource *hr) {

   ++hr->reference_count;
   evbuffer_add_reference(buf, hr->data, HUGE_RESOURCE_SIZE, cleanup, hr);

} </source>

References

<references />