LiEvent中文幫助文檔--第13章【連接監聽器:接受一個TCP連接】
Libevent
快速可移植非阻塞式網絡編程
修訂歷史
版本
日期
作者
備註
V1.0
2016-11-15
周勇
Libevent編程中文幫助文檔
文檔是2009-2012年由Nick-Mathewson基於Attribution-Noncommercial-Share Alike許可協議3.0創建,未來版本將會使用約束性更低的許可來創建.
此外,本文檔的源代碼示例也是基於BSD的"3條款"或"修改"條款.詳情請參考BSD文件全部條款.本文檔最新下載地址:
英文:http://libevent.org/
中文:http://blog.csdn.net/zhouyongku/article/details/53431750
請下載並運行"gitclonegit://github.com/nmathewson/libevent- book.git"獲取本文檔描述的最新版本源碼.
13.連接監聽器:接受一個TCP連接
evconnlistener機制提供了一種監聽並接受到來的TCP連接的方法.
所有的函數和類型這裏都定義在event2/listener.h中.除非特別說明,它們首次出現在都在LibEvent2.0.2-alpha中.
13.1創建或釋放一個evconnlistener
接口
struct evconnlistener* evconnlistener_new(struct event_base * base,
evconnlistener_cb cb,
void* ptr,
unsigned flags,
int backlog,
evutil_socket_t fd);
struct evconnlistener* evconnlistener_new_bind(struct event_base * base,
evconnlistener_cb cb,
void* ptr,
unsigned flags,
int backlog,
const struct sockaddr* sa,
int socklen);
void evconnlistener_free(struct evconnlistener* lev);
兩個 evconnlistener_new*()函數都分配和返回一個新的連接監聽器對象.連接監聽器使用event_base來得知什麼時候在給定的監聽套接字上有新的TCP連接.新連接到達時,監聽器調用你給出的回調函數.
兩個函數中,base參數都是監聽器用於監聽連接的event_base.cb是收到新連接時要調用的回調函數;如果cb爲NULL,則監聽器是禁用的,直到設置了回調函數爲止.ptr指針將傳遞給回調函數.flags參數控制回調函數的行爲,下面會更詳細論述.backlog是任何時刻網絡棧允許處於還未接受狀態的最大未決連接數.更多細節請查看系統的listen()函數文檔.如果backlog是負的,libevent會試圖挑選一個較好的值;如果爲0,libevent認爲已
經對提供的套接字調用了 listen().
兩個函數的不同在於如何建立監聽套接字.evconnlistener_new()函數假定已經將套接字綁
定到要監聽的端口,然後通過fd傳入這個套接字.如果要libevent分配和綁定套接字,可
以調用 evconnlistener_new_bind(),傳輸要綁定到的地址和地址長度.
____________________________________________________________________________________________________
注意
當使用evconnlistener_new的時候務必確認你的監聽socket是非阻塞模式,使用evutil_make_socket_nonblocking或手動設置socket的正確選項.當監聽socket處於阻塞模式,將會導致一些無法預知的行爲發生.
____________________________________________________________________________________________________
要釋放連接監聽器,應調用evconnlistener_free().
13.1.1可識別的標誌
可以給 evconnlistener_new()函數的flags參數傳入一些標誌.可以用或(OR)運算任意連接下述標誌:
-
LEV_OPT_LEAVE_SOCKETS_BLOCKING:默認情況下,連接監聽器接收新套接字後,會將其設置爲非阻塞的,以便將其用於libevent .如果不想要這種行爲,可以設置這個標誌.
-
LEV_OPT_CLOSE_ON_FREE:如果設置了這個選項,釋放連接監聽器會關閉底層套接字.
-
LEV_OPT_CLOSE_ON_EXEC:如果設置了這個選項,連接監聽器會爲底層套接字設置close-on-exec標誌.更多信息請查看fcntl和FD_CLOEXEC的平臺文檔.
-
LEV_OPT_REUSEABLE:某些平臺在默認情況下,關閉某監聽套接字後,要過一會兒其他套接字纔可以綁定到同一個端口.設置這個標誌會讓libevent標記套接字是可重用的,這樣一旦關閉,可以立即打開其他套接字,在相同端口進行監聽.
-
LEV_OPT_THREADSAFE:爲監聽器分配鎖,這樣就可以在多個線程中安全地使用了.這是2.0.8-rc的新功能.
13.1.2連接監聽器回調
接口
typedef void ( * evconnlistener_cb)(struct evconnlistener* listener,
evutil_socket_t sock,
struct sockaddr* addr,
int len,
void * ptr);
當一個新的連接到來,提供的回調函數將會被調用.參數listener是用於接受連接的連接監聽器.產生sock是新的socket.參數addr和len分別是接受的連接地址和地址的長度.參數ptr是傳到evconnlistener_new()的用戶提供的指針.
13.2啓用和禁用evconnlistener
接口
int evconnlistener_disable(struct evconnlistener* lev);
int evconnlistener_enable(struct evconnlistener* lev);
這兩個函數臨時啓用或禁用監聽新的連接的功能.
13.3調整evconnlistener的回調函數
接口
void evconnlistener_set_cb(struct evconnlistener* lev,
evconnlistener_cb cb,
void* arg);
該函數調整已有evconnlistener的回調函數和回調函數參數.該函數出現在LibEvent2.0.9-rc版本中.
13.4檢查evconnlistener
接口
evutil_socket_t evconnlistener_get_fd(struct evconnlistener* lev);
struct event_base* evconnlistener_get_base(struct evconnlistener * lev);
這些函數分別返回了監聽器關聯的socket和event_base.
evconnlistener_get_fd()函數出現在LibEvent2.0.3-alpha版本中.
13.5檢查錯誤
設置一個錯誤回調函數,當在監聽器上調用accept()函數調用失敗的時候收集錯誤信息.在當面臨一個不解決就可能鎖掉進程的錯誤條件的時候則顯得非常重要.
接口
typedef void ( * evconnlistener_errorcb)(struct evconnlistener* lis, void * ptr);
void evconnlistener_set_error_cb(struct evconnlistener* lev,
evconnlistener_errorcb errorcb);
如果調用 evconnlistener_set_error_cb()函數爲監聽器設置一個錯誤回調函數,每次監聽器出現錯誤的時候該函數都會返回.該函數接收監聽器作爲其第一個參數,傳遞到evconnlistener_new()接口的參數ptr作爲其第二個參數.
函數首次出現在LibEvent2.0.8-rc版本中.
13.6示例程序:一個echo服務器
示例
#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
static void echo_read_cb(struct bufferevent* bev, void * ctx)
{
/* This callback is invoked when there is data to read on bev.*/
struct evbuffer* input = bufferevent_get_input(bev);
struct evbuffer* output = bufferevent_get_output(bev);
/* Copy all the data from the input buffer to the output buffer.*/
evbuffer_add_buffer(output, input);
}
static void echo_event_cb(struct bufferevent* bev, short events, void * ctx)
{
if (events & BEV_EVENT_ERROR)
perror("Error from bufferevent");
if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR))
{
bufferevent_free(bev);
}
}
static voidaccept_conn_cb(struct evconnlistener* listener,
evutil_socket_t fd,
struct sockaddr* address,
int socklen,
void* ctx)
{
/* We got a new connection! Set up a bufferevent for it.*/
struct event_base* base = evconnlistener_get_base(listener);
struct bufferevent* bev = bufferevent_socket_new(
base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, echo_read_cb, NULL, echo_event_cb, NULL);
bufferevent_enable(bev, EV_READ|EV_WRITE);
}
static void accept_error_cb(struct evconnlistener* listener, void * ctx)
{
struct event_base* base = evconnlistener_get_base(listener);
int err = EVUTIL_SOCKET_ERROR();
fprintf(stderr, "Got an error %d (%s) on the listener. "
"Shutting down.\n", err, evutil_socket_error_to_string(err));
event_base_loopexit(base, NULL);
}
int main(int argc, char** argv)
{
struct event_base* base;
struct evconnlistener* listener;
struct sockaddr_in sin;
int port = 9876;
if (argc > 1)
{
port = atoi(argv[1]);
}
if (port<=0 || port>65535)
{
puts("Invalid port");
return 1;
}
base = event_base_new();
if (!base)
{
puts("Couldn’t open event base");
return 1;
}
/* Clear the sockaddr before using it, in case there are extra*
platform-specific fields that can mess us up.*/
memset(&sin, 0, sizeof(sin));
/* This is an INET address*/
sin.sin_family = AF_INET;
/* Listen on 0.0.0.0*/
sin.sin_addr.s_addr = htonl(0);
/* Listen on the given port.*/
sin.sin_port = htons(port);
listener = evconnlistener_new_bind(base, accept_conn_cb, NULL,
LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
(struct sockaddr * )&sin, sizeof(sin));
if (!listener)
{
perror("Couldn’t create listener");
return 1;
}
evconnlistener_set_error_cb(listener, accept_error_cb);
event_base_dispatch(base);
return 0;
}