libevent 源码 hello world
1.说明
程序是个简单的服务端程序,用于监听9995端口上的tcp连接。当有客户端接入,服务端会向客户端发送一条消息。
2.主线程代码解析
// 创建关于libevent变量 struct event_base *base; struct evconnlistener *listener; struct event *signal_event; // 初始化服务端设置 struct sockaddr_in sin = {0}; sin.sin_family = AF_INET; sin.sin_port = htons(PORT); #ifdef _WIN32 WSADATA wsa_data; WSAStartup(0x0201, &wsa_data); #endif // 创建 event_base base = event_base_new(); if (!base) { fprintf(stderr, "Could not initialize libevent!\n"); return 1; } // 创建套接字 // 绑定 // 接受连接请求 listener = evconnlistener_new_bind(base, listener_cb, (void *)base, LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1, (struct sockaddr*)&sin, sizeof(sin)); if (!listener) { fprintf(stderr, "Could not create a listener!\n"); return 1; } // 信号事件 signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base); if (!signal_event || event_add(signal_event, NULL)<0) { fprintf(stderr, "Could not create/add a signal event!\n"); return 1; } // 启动循环监听 event_base_dispatch(base); evconnlistener_free(listener); event_free(signal_event); event_base_free(base); printf("done\n"); return 0;
通过上面的部分代码可以看出,在流程上,tcp通信的建立没有太多变化。
接口变化,新的接口如下:
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)
/** Allocate a new evconnlistener object to listen for incoming TCP connections on a given address.
在给定地址分配一个监听对象,用于监听接入的tcp连接。 @param base The event base to associate the listener with. 和监听对象绑定到一起 @param cb A callback to be invoked when a new connection arrives. If the callback is NULL, the listener will be treated as disabled until the callback is set.
当一个新连接接入,回调会被唤醒,如果回调为NULL,会被视作不支持监听,除非设置了回调。 @param ptr A user-supplied pointer to give to the callback. 提供给回调的指针(用户提供的指针) @param flags Any number of LEV_OPT_* flags. LEV_OPT_类型的指针 @param backlog Passed to the listen() call to determine the length of the acceptable connection backlog. Set to -1 for a reasonable default.
传递给listen()调用的,用于决定可接受连接的长度。默认为-1. @param sa The address to listen for connections on. 监听的地址 @param socklen The length of the address. 地址的长度 */
调用
// 创建套接字 // 绑定 // 接受连接请求 listener = evconnlistener_new_bind(base, listener_cb, (void *)base, LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_FREE, -1, (struct sockaddr*)&sin, sizeof(sin));
第二个参数是回调函数
/**@file event2/listener.h @brief A callback that we invoke when a listener has a new connection. @param listener The evconnlistener 监听 @param fd The new file descriptor 文件描叙符 @param addr The source address of the connection 接入的连接的源地址 @param socklen The length of addr 地址的长度 @param user_arg the pointer passed to evconnlistener_new() 传递给evconnlisterner_new()的指针
上面的5个参数全部是出参。 */ typedef void (*evconnlistener_cb)(struct evconnlistener *, evutil_socket_t, struct sockaddr *, int socklen, void *);
为了进一步了解回调函数的出参是怎么一回事,来看一下对应接口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; int support_keepalive = 1; if (backlog == 0) return NULL; //这里默认值为-1. if (flags & LEV_OPT_CLOSE_ON_EXEC) socktype |= EVUTIL_SOCK_CLOEXEC;
// 文件描叙符 fd = evutil_socket_(family, socktype, 0); if (fd == -1) return NULL;
// 检测 #if defined(_WIN32) && defined(EVENT__HAVE_AFUNIX_H) if (family == AF_UNIX && evutil_check_working_afunix_()) { /* AF_UNIX socket can't set SO_KEEPALIVE option on Win10. * Avoid 10042 error. */ support_keepalive = 0; } #endif if (support_keepalive) { 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; } if (flags & LEV_OPT_BIND_IPV6ONLY) { if (evutil_make_listen_socket_ipv6only(fd) < 0) goto err; }
// 绑定套接字 if (sa) { if (bind(fd, sa, socklen)<0) goto err; }
// 监听是否有客户端接入 listener = evconnlistener_new(base, cb, ptr, flags, backlog, fd); if (!listener) goto err; return listener; err: evutil_closesocket(fd); return NULL; }
在上面的代码中可以看到文件描叙符的创建,对应的接口是:evuntil_socket_
/* Internal wrapper around 'socket' to provide Linux-style support for * syscall-saving methods where available.
* 内部包装socket,以提供linux风格支持 * * In addition to regular socket behavior, you can use a bitwise or to set the * flags EVUTIL_SOCK_NONBLOCK and EVUTIL_SOCK_CLOEXEC in the 'type' argument, * to make the socket nonblocking or close-on-exec with as few syscalls as * possible.
* 除了正常的socket操作,对于'type'参数,也可以按位使用或者将它设置为EVUTIL_SOCK_NONBLOCK 和 EVuTIL_SOCK_CLOEXEC,这样可以尽可能减少对应的系统调用 */ evutil_socket_t evutil_socket_(int domain, int type, int protocol) { evutil_socket_t r; #if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC) r = socket(domain, type, protocol); //可以看到,这里是我们熟悉的创建套接字的接口 if (r >= 0) return r; else if ((type & (SOCK_NONBLOCK|SOCK_CLOEXEC)) == 0) return -1; #endif #define SOCKET_TYPE_MASK (~(EVUTIL_SOCK_NONBLOCK|EVUTIL_SOCK_CLOEXEC)) r = socket(domain, type & SOCKET_TYPE_MASK, protocol); if (r < 0) return -1; if (type & EVUTIL_SOCK_NONBLOCK) { if (evutil_fast_socket_nonblocking(r) < 0) { evutil_closesocket(r); return -1; } } if (type & EVUTIL_SOCK_CLOEXEC) { if (evutil_fast_socket_closeonexec(r) < 0) { evutil_closesocket(r); return -1; } } return r; }
同时,还有一个用于监听是否有客户端接入的接口:evconnlistener_new
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
// 非win32 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; }
分析win32部分,可以看到一个新的接口:evconnlistener_new_async
struct evconnlistener * evconnlistener_new_async(struct event_base *base, evconnlistener_cb cb, void *ptr, unsigned flags, int backlog, evutil_socket_t fd) { struct sockaddr_storage ss; int socklen = sizeof(ss); struct evconnlistener_iocp *lev; int i; flags |= LEV_OPT_THREADSAFE; if (!base || !event_base_get_iocp_(base)) goto err; /* XXXX duplicate code */ if (backlog > 0) { if (listen(fd, backlog) < 0) goto err; } else if (backlog < 0) { if (listen(fd, 128) < 0)//这里可以看到熟悉的监听接口listen.fd是之前我们创建的文件描叙符.通过128知道等待连接的最大长度是128. goto err; } if (getsockname(fd, (struct sockaddr*)&ss, &socklen)) { event_sock_warn(fd, "getsockname"); // 获取已创建好的套接字的本地名称 goto err; } lev = mm_calloc(1, sizeof(struct evconnlistener_iocp)); //申请lev内存 if (!lev) { event_warn("calloc"); goto err; } lev->base.ops = &evconnlistener_iocp_ops; lev->base.cb = cb; // 将cb,用户的定义的回调赋给了申请的lev成员 lev->base.user_data = ptr; lev->base.flags = flags; lev->base.refcnt = 1; lev->base.enabled = 1; lev->port = event_base_get_iocp_(base); lev->fd = fd; //将之前创建的fd,赋给了lev的成员 lev->event_base = base; if (event_iocp_port_associate_(lev->port, fd, 1) < 0) goto err_free_lev; EVTHREAD_ALLOC_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE); lev->n_accepting = N_SOCKETS_PER_LISTENER; lev->accepting = mm_calloc(lev->n_accepting, sizeof(struct accepting_socket *)); if (!lev->accepting) { event_warn("calloc"); goto err_delete_lock; }
// 创建用于接收或者发送消息的套接字 for (i = 0; i < lev->n_accepting; ++i) { lev->accepting[i] = new_accepting_socket(lev, ss.ss_family); if (!lev->accepting[i]) { event_warnx("Couldn't create accepting socket"); goto err_free_accepting; } if (cb && start_accepting(lev->accepting[i]) < 0) { event_warnx("Couldn't start accepting on socket"); EnterCriticalSection(&lev->accepting[i]->lock); free_and_unlock_accepting_socket(lev->accepting[i]); goto err_free_accepting; } ++lev->base.refcnt; } iocp_listener_event_add(lev); return &lev->base; err_free_accepting: mm_free(lev->accepting); /* XXXX free the other elements. */ err_delete_lock: EVTHREAD_FREE_LOCK(lev->base.lock, EVTHREAD_LOCKTYPE_RECURSIVE); err_free_lev: mm_free(lev); err: /* Don't close the fd, it is caller's responsibility. */ return NULL; }
可以看到这部分代码用新接口:new_accepting_socket
static struct accepting_socket * new_accepting_socket(struct evconnlistener_iocp *lev, int family) { struct accepting_socket *res; int addrlen; int buflen; if (family == AF_INET) addrlen = sizeof(struct sockaddr_in); else if (family == AF_INET6) addrlen = sizeof(struct sockaddr_in6); #ifdef EVENT__HAVE_AFUNIX_H else if (family == AF_UNIX && evutil_check_working_afunix_()) addrlen = sizeof(struct sockaddr_un); #endif else return NULL; buflen = (addrlen+16)*2; res = mm_calloc(1,sizeof(struct accepting_socket)-1+buflen); if (!res) return NULL; event_overlapped_init_(&res->overlapped, accepted_socket_cb); res->s = EVUTIL_INVALID_SOCKET; res->lev = lev; //lev,赋给了res的成员 res->buflen = buflen; res->family = family; event_deferred_cb_init_(&res->deferred, event_base_get_npriorities(lev->event_base) / 2, accepted_socket_invoke_user_cb, res); InitializeCriticalSectionAndSpinCount(&res->lock, 1000); return res; }
这里会看到event_deferred_cb_init接口
/** Initialize an empty, non-pending event_callback. @param deferred The struct event_callback structure to initialize. 待初始化的event_callback 结构体 @param priority The priority that the callback should run at. callback的优先级 @param cb The function to run when the struct event_callback executes. 执行event_callback时,需要调用的函数 @param arg The function's second argument. 函数的第二个参数 */ EVENT2_EXPORT_SYMBOL void event_deferred_cb_init_(struct event_callback *, ev_uint8_t, deferred_cb_fn, void *);
在调用event_deferred_cb_init接口中可以看到,回调函数accepted_socket_invoke_user_cb
static void accepted_socket_invoke_user_cb(struct event_callback *dcb, void *arg) { struct accepting_socket *as = arg; struct sockaddr *sa_local=NULL, *sa_remote=NULL; int socklen_local=0, socklen_remote=0; const struct win32_extension_fns *ext = event_get_win32_extension_fns_(); struct evconnlistener *lev = &as->lev->base; evutil_socket_t sock=-1; void *data; evconnlistener_cb cb=NULL; // 定义了一个evconnlistener_cb对象 evconnlistener_errorcb errorcb=NULL; int error; EVUTIL_ASSERT(ext->GetAcceptExSockaddrs); LOCK(lev); EnterCriticalSection(&as->lock); if (as->free_on_cb) { free_and_unlock_accepting_socket(as); listener_decref_and_unlock(lev); return; } ++lev->refcnt; error = as->error; if (error) { as->error = 0; errorcb = lev->errorcb; } else { ext->GetAcceptExSockaddrs( as->addrbuf, 0, as->buflen/2, as->buflen/2, &sa_local, &socklen_local, &sa_remote, &socklen_remote); sock = as->s; cb = lev->cb; //lev的cb成员赋给了cb as->s = EVUTIL_INVALID_SOCKET; /* We need to call this so getsockname, getpeername, and * shutdown work correctly on the accepted socket. */ /* XXXX handle error? */ setsockopt(sock, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char *)&as->lev->fd, sizeof(&as->lev->fd)); } data = lev->user_data; LeaveCriticalSection(&as->lock); UNLOCK(lev); if (errorcb) { WSASetLastError(error); errorcb(lev, data); } else if (cb) { cb(lev, sock, sa_remote, socklen_remote, data); //cb对应的参数 } LOCK(lev); if (listener_decref_and_unlock(lev)) return; EnterCriticalSection(&as->lock); start_accepting(as); LeaveCriticalSection(&as->lock); }
sock:用于接收和发送消息的套接字
sa_remote: 客户端地址
socklen_remote: 客户端地址长度
data:客户端回复的消息
通过接口的说明知道,listener_cb是回调函数,用户自己编写,这里需要了解这个回调函数对应的参数。
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)); }
part2
而在上面的代码里可以看到一个新接口:bufferevent_socket_new
/** Create a new socket bufferevent over an existing socket. 在一个已存在的套接字的基础上,创建一个新的套接字bufferevent. @param base the event base to associate with the new bufferevent. 将和新的bufferevent绑定到一起的base @param fd the file descriptor from which data is read and written to. 文件描叙符,可以从它读取数据和写入数据 This file descriptor is not allowed to be a pipe(2). 文件描叙符不允许设置为2 It is safe to set the fd to -1, so long as you later set it with bufferevent_setfd or bufferevent_socket_connect().
文件描叙符可以设置为-1,只要在后面使用bufferevent_setfd或者bufferevent_socket_connect()来设置。 @param options Zero or more BEV_OPT_* flags 0或者多个WEV_OPT_ *类型的值 @return a pointer to a newly allocated bufferevent struct, or NULL if an error occurred
返回一个指针,该指针指向一个新分配的bufferevent结构体,或者NULL(当有错误发生时) @see bufferevent_free() */ EVENT2_EXPORT_SYMBOL struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, int options);

浙公网安备 33010602011771号