接口
struct bufferevent *bufferevent_socket_new(
struct event_base *base,
evutil_socket_t fd,
enum bufferevent_options options);
base是event_base,options是表示bufferevent選項(BEV_OPT_CLOSE_ON_FREE等)的位掩碼,fd是一個可選的表示套接字的文件描述符。如果想以后設置文件描述符,可以設置fd為-1。
成功時函數(shù)返回一個bufferevent,失敗則返回NULL。
bufferevent_socket_new()函數(shù)由2.0.1-alpha版新引入。
5.2 在基于套接字的bufferevent上啟動連接
如果bufferevent的套接字還沒有連接上,可以啟動新的連接。
接口
int bufferevent_socket_connect(struct bufferevent *bev,
struct sockaddr *address, int addrlen);
address和addrlen參數(shù)跟標準調(diào)用connect()的參數(shù)相同。如果還沒有為bufferevent設置套接字,調(diào)用函數(shù)將為其分配一個新的流套接字,并且設置為非阻塞的。
如果已經(jīng)為bufferevent設置套接字,調(diào)用bufferevent_socket_connect()將告知libevent套接字還未連接,直到連接成功之前不應該對其進行讀取或者寫入操作。
連接完成之前可以向輸出緩沖區(qū)添加數(shù)據(jù)。
如果連接成功啟動,函數(shù)返回0;如果發(fā)生錯誤則返回-1。
示例
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <sys/socket.h>
#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;
}
bufferevent_socket_connect()函數(shù)由2.0.2-alpha版引入。在此之前,必須自己手動在套接字上調(diào)用connect(),連接完成時,bufferevent將報告寫入事件。
注意:如果使用bufferevent_socket_connect()發(fā)起連接,將只會收到BEV_EVENT_CONNECTED事件。如果自己調(diào)用connect(),則連接上將被報告為寫入事件。
這個函數(shù)在2.0.2-alpha版引入。
5.3 通過主機名啟動連接
常常需要將解析主機名和連接到主機合并成單個操作,libevent為此提供了:
接口
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);
這個函數(shù)解析名字hostname,查找其family類型的地址(允許的地址族類型有AF_INET,AF_INET6和AF_UNSPEC)。如果名字解析失敗,函數(shù)將調(diào)用事件回調(diào),報告錯誤事件。如果解析成功,函數(shù)將啟動連接請求,就像bufferevent_socket_connect()一樣。
dns_base參數(shù)是可選的:如果為NULL,等待名字查找完成期間調(diào)用線程將被阻塞,而這通常不是期望的行為;如果提供dns_base參數(shù),libevent將使用它來異步地查詢主機名。關于DNS的更多信息,請看第九章。
跟bufferevent_socket_connect()一樣,函數(shù)告知libevent,bufferevent上現(xiàn)存的套接字還沒有連接,在名字解析和連接操作成功完成之前,不應該對套接字進行讀取或者寫入操作。
函數(shù)返回的錯誤可能是DNS主機名查詢錯誤,可以調(diào)用bufferevent_socket_get_dns_error()來獲取最近的錯誤。返回值0表示沒有檢測到DNS錯誤。
示例:簡單的HTTP v0客戶端
/* Don't actually copy this code: it is a poor way to implement an
HTTP client. Have a look at evhttp instead.
*/
#include <event2/dns.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/event.h>
#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;
}
6 通用bufferevent操作
本節(jié)描述的函數(shù)可用于多種bufferevent實現(xiàn)。
6.1 釋放bufferevent
接口
void bufferevent_free(struct bufferevent *bev);
這個函數(shù)釋放bufferevent。bufferevent內(nèi)部具有引用計數(shù),所以,如果釋放bufferevent時還有未決的延遲回調(diào),則在回調(diào)完成之前bufferevent不會被刪除。
如果設置了BEV_OPT_CLOSE_ON_FREE標志,并且bufferevent有一個套接字或者底層bufferevent作為其傳輸端口,則釋放bufferevent將關閉這個傳輸端口。
這個函數(shù)由libevent 0.8版引入。
6.2 操作回調(diào)、水位和啟用/禁用
接口
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 *writecb_ptr,
bufferevent_event_cb *eventcb_ptr,
void **cbarg_ptr);
bufferevent_setcb()函數(shù)修改bufferevent的一個或者多個回調(diào)。readcb、writecb和eventcb函數(shù)將分別在已經(jīng)讀取足夠的數(shù)據(jù)、已經(jīng)寫入足夠的數(shù)據(jù),或者發(fā)生錯誤時被調(diào)用。每個回調(diào)函數(shù)的第一個參數(shù)都是發(fā)生了事件的bufferevent,最后一個參數(shù)都是調(diào)用bufferevent_setcb()時用戶提供的cbarg參數(shù):可以通過它向回調(diào)傳遞數(shù)據(jù)。事件回調(diào)的events參數(shù)是一個表示事件標志的位掩碼:請看前面的“回調(diào)和水位”節(jié)。
要禁用回調(diào),傳遞NULL而不是回調(diào)函數(shù)。注意:bufferevent的所有回調(diào)函數(shù)共享單個cbarg,所以修改它將影響所有回調(diào)函數(shù)。
這個函數(shù)由1.4.4版引入。類型名bufferevent_data_cb和bufferevent_event_cb由2.0.2-alpha版引入。
接口
void bufferevent_enable(struct bufferevent *bufev, short events);
void bufferevent_disable(struct bufferevent *bufev, short events);
short bufferevent_get_enabled(struct bufferevent *bufev);
可以啟用或者禁用bufferevent上的EV_READ、EV_WRITE或者EV_READ | EV_WRITE事件。沒有啟用讀取或者寫入事件時,bufferevent將不會試圖進行數(shù)據(jù)讀取或者寫入。
沒有必要在輸出緩沖區(qū)空時禁用寫入事件:bufferevent將自動停止寫入,然后在有數(shù)據(jù)等待寫入時重新開始。
類似地,沒有必要在輸入緩沖區(qū)高于高水位時禁用讀取事件:bufferevent將自動停止讀取,然后在有空間用于讀取時重新開始讀取。
默認情況下,新創(chuàng)建的bufferevent的寫入是啟用的,但是讀取沒有啟用。
可以調(diào)用bufferevent_get_enabled()確定bufferevent上當前啟用的事件。
除了bufferevent_get_enabled()由2.0.3-alpha版引入外,這些函數(shù)都由0.8版引入。
接口
void bufferevent_setwatermark(struct bufferevent *bufev, short events,
size_t lowmark, size_t highmark);
bufferevent_setwatermark()函數(shù)調(diào)整單個bufferevent的讀取水位、寫入水位,或者同時調(diào)整二者。(如果events參數(shù)設置了EV_READ,調(diào)整讀取水位。如果events設置了EV_WRITE標志,調(diào)整寫入水位)
對于高水位,0表示“無限”。
這個函數(shù)首次出現(xiàn)在1.4.4版。
示例
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <stdlib.h>
#include <errno.h>
#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 from %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;
} 6.3 操作bufferevent中的數(shù)據(jù)
如果只是通過網(wǎng)絡讀取或者寫入數(shù)據(jù),而不能觀察操作過程,是沒什么好處的。bufferevent提供了下列函數(shù)用于觀察要寫入或者讀取的數(shù)據(jù)。(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.)
接口
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
這兩個函數(shù)提供了非常強大的基礎:它們分別返回輸入和輸出緩沖區(qū)。關于可以對evbuffer類型進行的所有操作的完整信息,請看下一章。
如果寫入操作因為數(shù)據(jù)量太少而停止(或者讀取操作因為太多數(shù)據(jù)而停止),則向輸出緩沖區(qū)添加數(shù)據(jù)(或者從輸入緩沖區(qū)移除數(shù)據(jù))將自動重啟操作。
這些函數(shù)由2.0.1-alpha版引入。
接口
int bufferevent_write(struct bufferevent *bufev,
const void *data, size_t size);
int bufferevent_write_buffer(struct bufferevent *bufev,
struct evbuffer *buf);
這些函數(shù)向bufferevent的輸出緩沖區(qū)添加數(shù)據(jù)。bufferevent_write()將內(nèi)存中從data處開始的size字節(jié)數(shù)據(jù)添加到輸出緩沖區(qū)的末尾。bufferevent_write_buffer()移除buf的所有內(nèi)容,將其放置到輸出緩沖區(qū)的末尾。成功時這些函數(shù)都返回0,發(fā)生錯誤時則返回-1。
這些函數(shù)從0.8版就存在了。
接口
size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);
int bufferevent_read_buffer(struct bufferevent *bufev,
struct evbuffer *buf);
這些函數(shù)從bufferevent的輸入緩沖區(qū)移除數(shù)據(jù)。bufferevent_read()至多從輸入緩沖區(qū)移除size字節(jié)的數(shù)據(jù),將其存儲到內(nèi)存中data處。函數(shù)返回實際移除的字節(jié)數(shù)。bufferevent_read_buffer()函數(shù)抽空輸入緩沖區(qū)的所有內(nèi)容,將其放置到buf中,成功時返回0,失敗時返回-1。
注意,對于bufferevent_read(),data處的內(nèi)存塊必須有足夠的空間容納size字節(jié)數(shù)據(jù)。
bufferevent_read()函數(shù)從0.8版就存在了;bufferevnet_read_buffer()由2.0.1-alpha版引入。
示例
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#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 starts 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 from one connection (on
bev), and write 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);
}
6.4 讀寫超時
跟其他事件一樣,可以要求在一定量的時間已經(jīng)流逝,而沒有成功寫入或者讀取數(shù)據(jù)的時候調(diào)用一個超時回調(diào)。
接口
void bufferevent_set_timeouts(struct bufferevent *bufev,
const struct timeval *timeout_read, const struct timeval *timeout_write);
設置超時為NULL會移除超時回調(diào)。
試圖讀取數(shù)據(jù)的時候,如果至少等待了timeout_read秒,則讀取超時事件將被觸發(fā)。試圖寫入數(shù)據(jù)的時候,如果至少等待了timeout_write秒,則寫入超時事件將被觸發(fā)。
注意,只有在讀取或者寫入的時候才會計算超時。也就是說,如果bufferevent的讀取被禁止,或者輸入緩沖區(qū)滿(達到其高水位),則讀取超時被禁止。類似的,如果寫入被禁止,或者沒有數(shù)據(jù)待寫入,則寫入超時被禁止。
讀取或者寫入超時發(fā)生時,相應的讀取或者寫入操作被禁止,然后超時事件回調(diào)被調(diào)用,帶有標志BEV_EVENT_TIMEOUT | BEV_EVENT_READING或者BEV_EVENT_TIMEOUT | BEV_EVENT_WRITING。
這個函數(shù)從2.0.1-alpha版就存在了,但是直到2.0.4-alpha版才對于各種bufferevent類型行為一致。
6.5 對bufferevent發(fā)起清空操作
接口
int bufferevent_flush(struct bufferevent *bufev,
short iotype, enum bufferevent_flush_mode state)
清空bufferevent要求bufferevent強制從底層傳輸端口讀取或者寫入盡可能多的數(shù)據(jù),而忽略其他可能保持數(shù)據(jù)不被寫入的限制條件。函數(shù)的細節(jié)功能依賴于bufferevent的具體類型。
iotype參數(shù)應該是EV_READ、EV_WRITE或者EV_READ | EV_WRITE,用于指示應該處理讀取、寫入,還是二者都處理。state參數(shù)可以是BEV_NORMAL、BEV_FLUSH或者BEV_FINISHED。BEV_FINISHED指示應該告知另一端,沒有更多數(shù)據(jù)需要發(fā)送了;而BEV_NORMAL和BEV_FLUSH的區(qū)別依賴于具體的bufferevent類型。
失敗時bufferevent_flush()返回-1,如果沒有數(shù)據(jù)被清空則返回0,有數(shù)據(jù)被清空則返回1。
當前(2.0.5-beta版)僅有一些bufferevent類型實現(xiàn)了bufferevent_flush()。特別是,基于套接字的bufferevent沒有實現(xiàn)。
7 類型特定的bufferevent函數(shù)
這些bufferevent函數(shù)不能支持所有bufferevent類型。
接口
int bufferevent_priority_set(struct bufferevent *bufev, int pri);
int bufferevent_get_priority(struct bufferevent *bufev);
這個函數(shù)調(diào)整bufev的優(yōu)先級為pri。關于優(yōu)先級的更多信息請看event_priority_set()。
成功時函數(shù)返回0,失敗時返回-1。這個函數(shù)僅能用于基于套接字的bufferevent。
這個函數(shù)由1.0版引入。
接口
int bufferevent_setfd(struct bufferevent *bufev, evutil_socket_t fd);
evutil_socket_t bufferevent_getfd(struct bufferevent *bufev);
這些函數(shù)設置或者返回基于fd的事件的文件描述符。只有基于套接字的bufferevent支持setfd()。兩個函數(shù)都在失敗時返回-1;setfd()成功時返回0。
bufferevent_setfd()函數(shù)由1.4.4版引入;bufferevent_getfd()函數(shù)由2.0.2-alpha版引入。
接口
struct event_base *bufferevent_get_base(struct bufferevent *bev);
這個函數(shù)返回bufferevent的event_base,由2.0.9-rc版引入。
接口
struct bufferevent *bufferevent_get_underlying(struct bufferevent *bufev);
這個函數(shù)返回作為bufferevent底層傳輸端口的另一個bufferevent。關于這種情況,請看關于過濾型bufferevent的介紹。
這個函數(shù)由2.0.2-alpha版引入。
8 手動鎖定和解鎖
有時候需要確保對bufferevent的一些操作是原子地執(zhí)行的。為此,libevent提供了手動鎖定和解鎖bufferevent的函數(shù)。
接口
void bufferevent_lock(struct bufferevent *bufev);
void bufferevent_unlock(struct bufferevent *bufev);
注意:如果創(chuàng)建bufferevent時沒有指定BEV_OPT_THREADSAFE標志,或者沒有激活libevent的線程支持,則鎖定操作是沒有效果的。
用這個函數(shù)鎖定bufferevent將自動同時鎖定相關聯(lián)的evbuffer。這些函數(shù)是遞歸的:鎖定已經(jīng)持有鎖的bufferevent是安全的。當然,對于每次鎖定都必須進行一次解鎖。
這些函數(shù)由2.0.6-rc版引入。
9 已廢棄的bufferevent功能
從1.4到2.0版,bufferevent的后端代碼一直在進行修訂。在老的接口中,訪問bufferevent結(jié)構(gòu)體的內(nèi)部是很平常的,并且還會使用依賴于這種訪問的宏。
更復雜的是,老的代碼有時候?qū)?#8220;evbuffer”前綴用于bufferevent功能。
這里有一個在2.0版之前使用過的東西的概要:
老的函數(shù)定義在event.h中,而不是在event2/bufferevent.h中。
如果仍然需要訪問bufferevent結(jié)構(gòu)體內(nèi)部的某些公有部分,可以包含event2/bufferevent_struct.h。但是不建議這么做:不同版本的Libevent中bufferevent結(jié)構(gòu)體的內(nèi)容可能會改變。本節(jié)描述的宏和名字只有在包含了event2/bufferevent_compat.h時才能使用。
較老版本中用于設置bufferevent的接口有所不同:
接口
struct bufferevent *bufferevent_new(evutil_socket_t fd,
evbuffercb readcb, evbuffercb writecb, everrorcb errorcb, void *cbarg);
int bufferevent_base_set(struct event_base *base, struct bufferevent *bufev);
bufferevent_new()函數(shù)僅僅在已經(jīng)廢棄的“默認”event_base上創(chuàng)建一個套接字bufferevent。調(diào)用bufferevent_base_set()可以調(diào)整套接字bufferevent的event_base。
較老版本不使用timeval結(jié)構(gòu)體設置超時,而是使用秒數(shù):
接口
void bufferevent_settimeout(struct bufferevent *bufev,
int timeout_read, int timeout_write);
最后要指出的是,2.0之前版本中的evbuffer實現(xiàn)是極其低效的,這對將bufferevent用于高性能應用是一個問題。