在上一篇文章中,以epoll爲例說到了事件機制,會按順序調用init和dispatch這兩個回調函數,可是,咱們回憶一下網絡編程的過程,首先是須要建立socket、綁定socket、監聽socket的,但目前爲止還並無涉及到,再去看源代碼,會發現裏面有listener.c,這個文件裏面就會去作建立socket的過程。編程
看evconnlistener_new_bind函數,以下:網絡
struct evconnlistener * evconnlistener_new_bind(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, const struct sockaddr *sa, int socklen) { struct evconnlistener *listener; evutil_socket_t fd; int on = 1; int family = sa ? sa->sa_family : AF_UNSPEC; int socktype = SOCK_STREAM | EVUTIL_SOCK_NONBLOCK; if (backlog == 0) return NULL; if (flags & LEV_OPT_CLOSE_ON_EXEC) socktype |= EVUTIL_SOCK_CLOEXEC; //調用socket函數 fd = evutil_socket_(family, socktype, 0); if (fd == -1) return NULL; //設置存貨檢測 if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void*)&on, sizeof(on))<0) goto err; //設置地址重用 if (flags & LEV_OPT_REUSEABLE) { if (evutil_make_listen_socket_reuseable(fd) < 0) goto err; } //設置端口重用 if (flags & LEV_OPT_REUSEABLE_PORT) { if (evutil_make_listen_socket_reuseable_port(fd) < 0) goto err; } //設置延遲接收 if (flags & LEV_OPT_DEFERRED_ACCEPT) { if (evutil_make_tcp_listen_socket_deferred(fd) < 0) goto err; } //調用bind函數 if (sa) { if (bind(fd, sa, socklen)<0) goto err; } //evconnlistener_new函數裏面會調用listen函數 listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd); if (!listener) goto err; return listener; err: evutil_closesocket(fd); return NULL; }
上面的代碼我加了註釋,說的很清楚,從建立、綁定、設置屬性一直到監聽整個都調用了,這裏再也不多說。socket
evconnlistener_new函數不僅會調用listen,還會註冊一個監聽回調函數,以下:async
struct evconnlistener * evconnlistener_new(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd) { struct evconnlistener_event *lev; #ifdef _WIN32 if (base && event_base_get_iocp_(base)) { const struct win32_extension_fns *ext = event_get_win32_extension_fns_(); if (ext->AcceptEx && ext->GetAcceptExSockaddrs) return evconnlistener_new_async(base, cb, ptr, flags, backlog, fd); } #endif if (backlog > 0) { if (listen(fd, backlog) < 0) return NULL; } else if (backlog < 0) { if (listen(fd, 128) < 0) return NULL; } lev = mm_calloc(1, sizeof(struct evconnlistener_event)); if (!lev) return NULL; lev->base.ops = &evconnlistener_event_ops; //註冊回調函數,當監聽到有新的鏈接時,就會調用該函數 lev->base.cb = cb; lev->base.user_data = ptr; lev->base.flags = flags; lev->base.refcnt = 1; lev->base.accept4_flags = 0; if (!(flags & LEV_OPT_LEAVE_SOCKETS_BLOCKING)) lev->base.accept4_flags |= EVUTIL_SOCK_NONBLOCK; if (flags & LEV_OPT_CLOSE_ON_EXEC) lev->base.accept4_flags |= EVUTIL_SOCK_CLOEXEC; if (flags & LEV_OPT_THREADSAFE) { EVTHREAD_ALLOC_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE); } event_assign(&lev->listener, base, fd, EV_READ|EV_PERSIST, listener_read_cb, lev); if (!(flags & LEV_OPT_DISABLED)) evconnlistener_enable(&lev->base); return &lev->base; }
evconnlistener_cb回調函數聲明以下:tcp
typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);
該回調函數須要咱們本身實現,看sample目錄中hello-world.c中實現的該函數代碼以下:函數
static void listener_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *sa, int socklen, void *user_data) { struct event_base *base = user_data; struct bufferevent *bev; bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE); if (!bev) { fprintf(stderr, "Error constructing bufferevent!"); event_base_loopbreak(base); return; } bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL); bufferevent_enable(bev, EV_WRITE); bufferevent_disable(bev, EV_READ); bufferevent_write(bev, MESSAGE, strlen(MESSAGE)); }
libevent是基於事件的,它的不少動做都是調用事先註冊好的回調函數來解決的,讀寫數據也不例外。oop
看上面第一節中,監聽回調函數裏面使用了bufferevent_setcb,這個函數會註冊讀寫事件的回調函數,以下:spa
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg) { BEV_LOCK(bufev); bufev->readcb = readcb; bufev->writecb = writecb; bufev->errorcb = eventcb; bufev->cbarg = cbarg; BEV_UNLOCK(bufev); }
當有可讀事件時會調用readcb函數,當有可寫事件時調用writecb函數,發生錯誤時調用eventcb函數。
bufferevent_data_cb聲明以下:code
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);
仍是看看sample目錄中hello-world.c中對該函數的定義,以下:事件
static void conn_writecb(struct bufferevent *bev, void *user_data) { struct evbuffer *output = bufferevent_get_output(bev); if (evbuffer_get_length(output) == 0) { printf("flushed answer\n"); bufferevent_free(bev); } }
咱們本身使用時能夠參照sample目錄中的例子,這裏就再也不細說了。
文章同步發表在cpp加油站(ID:xy13640954449), 歡迎關注!