Libevent学习记录(2)
Libevent官方例程hello-world代码分析
通过libevent提供的Hello_World demo简单学习基于libevent的TCP服务器的实现。
注释源码:
以下注释均为自己学习的时候进行的注释,有错的欢迎指出
/*
This example program provides a trivial server program that listens for TCP
connections on port 9995. When they arrive, it writes a short message to
each client connection, and closes each connection once it is flushed.
Where possible, it exits cleanly in response to a SIGINT (ctrl-c).
*/
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#ifndef _WIN32
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
# include <arpa/inet.h>
# endif
#include <sys/socket.h>
#endif
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>
static const char MESSAGE[] = "Hello, World!";
static const int PORT = 9995;
static void listener_cb(struct evconnlistener *, evutil_socket_t,
struct sockaddr *, int socklen, void *);
static void conn_writecb(struct bufferevent *, void *);
static void conn_eventcb(struct bufferevent *, short, void *);
static void signal_cb(evutil_socket_t, short, void *);
int
main(int argc, char **argv)
{
struct event_base *base; //定义event_base变量
struct evconnlistener *listener;//定义一个evconnlistener来监测端口(9995)
struct event *signal_event; //定义一个处理信号的事件来处理ctrl+c的信号
struct sockaddr_in sin = {0}; //socket连接的协议地址结构体
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif
base = event_base_new();
if (!base) {
fprintf(stderr, "Could not initialize libevent!\n");
return 1;
}
//创建socket连接,设置sockaddr_in对应参数
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
//参数说明:base是event_base事件集;listener_cb是回调函数函数名;(void *)base提供给回调函数的参数;标志位;backlog这里一般设置为-1;后面两个参数就是const struct sockaddr *指针和对应大小
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);
//判断signal_event是否创建成功,成功则并将其加入libevent事件链表上
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);//实际上是调用了event_base_loop();
//依次释放listener,signal_event和event_base的资源,结束
evconnlistener_free(listener);
event_free(signal_event);
event_base_free(base);
printf("done\n");
return 0;
}
//参数说明:监听器;用于和客户端连接的socketfd;sockaddr结构体;sockaddr对应长度;用户传递过来的参数;
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;
//开始需要调用 bufferevent_socket_new 创建一个bufferevent;BEV_OPT_CLOSE_ON_FREE:释放bufferevent时关闭底层传输端口
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,设置当已经写入足够的数据时调用回调函数conn_writecb,发生错误时调用conn_eventcb
bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
//启用写事件
bufferevent_enable(bev, EV_WRITE);
//禁用读事件
bufferevent_disable(bev, EV_READ);
//调用 bufferevent_write 写入数据,从data处开始的size字节数据从内存中移除,并添加到输出缓冲区的末尾
bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}
static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
//获取eventbuffer的输出缓冲区bev
struct evbuffer *output = bufferevent_get_output(bev);
//当输出缓冲区没有数据时释放bev
if (evbuffer_get_length(output) == 0) {
//释放bev
printf("flushed answer\n");
bufferevent_free(bev);
}
}
static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
//BEV_EVENT_EOF遇到文件结束指示
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
}
//BEV_EVENT_ERROR操作时发生错误
else if (events & BEV_EVENT_ERROR) {
printf("Got an error on the connection: %s\n",
strerror(errno));/*XXX win32*/
}
/* None of the other events can happen here, since we haven't enabled
* timeouts */
bufferevent_free(bev);
}
static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
struct event_base *base = user_data;
struct timeval delay = { 2, 0 };//定义delay为2s
printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");
event_base_loopexit(base, &delay);//2s后结束事件主循环
}
整体框架
对于这个TCP服务器,整体的逻辑如下:
• 新建一个event_base,然后在其上绑定一个listener来监听特定端口(9995);
• 新建一个处理信号的事件signal_event,并与上一步中的event_base绑定;
• 调用event_base_dispatch来监控两个事件,包括指定的TCP连接及SIGINT信号:
当监听到一个连接时,libevent会触发listen_callback,即为上面的listener_cb函数,该函数首先基于底层的socket建立一个bufferevent,并设置为可写不可读,最后调用bufferevent_write函数向其中缓存区写。bufferevent_write函数写后会触发写回调函数conn_writecb,由于该例子中不需要其他操作,所以conn_writecb函数直接释放这个连接。
当中断信号(Ctrl+C)出现时,libevent会触发signal_event的回调函数signal_cb,该函数会在2秒后停止event_base的监听,并退回到主函数;
• 主函数依次释放listener,signal_event和event_base的资源,结束。
相关说明:
这里只对相关函数进行简单的功能描述,详细的说明:Libevent学习记录(3)
event_base
当前线程中所有事件的一个管理者;位于event-internal.h中。
一个event_base就是一个Reactor框架。我们在调用任何Libevent的函数前,我们都是需要先申请 event_base 结构体。对于一个event_base结构来说,它会保存一系列的event事件并且以轮训的方式去关注每一个事件,去查看哪一个事件是就绪的。
event_base 相当于是一个底座,只要向底座上插入事件,然后不断的监控事件,等待事件发生调用回调函数即可
evconnlistener
连接监听器,基于event和event_base已经可以写一个CS模型了。但是对于服务器端来说,仍然需要用户自行调用socket、bind、listen、accept等步骤。这个过程有点繁琐,为此在2.0.2-alpha版本的Libevent推出了一些对应的封装函数。
用户只需初始化struct sockaddr_in结构体变量,然后把它作为参数传给函数evconnlistener_new_bind即可。该函数会完成上面说到的那4个过程。即对socket、bind、listen、accept的一个封装。
当服务器端监听到一个客户端的连接请求后,就会调用listener_cb这个回调函数。这个回调函数是在evconnlistener_new_bind函数中设置的。要注意的是:当这个回调函数被调用时,Libevent已经帮我们accept了这个客户端。所以,该回调函数有一个参数是文件描述符fd。我们直接使用这个fd即可。真是方便。
bufferevent
通常已连接的套接字除了相应事件之外,应用还希望做一定的数据缓冲。这种缓冲IO模式很通用,libevent为此提供了一种通用机制即bufferevent。
bufferevent由一个底层的传输端口(如已连接套接字)、一个读取缓冲区和一个写入缓冲区组成。
与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后再调用用户提供的回调。
用户通过bufferevent不必处理系统底层IO操作,仅仅从bufferevent中读取相应数据即可。
evconnlistener是为了简化服务器从创建套接字到accept成功这个繁琐的过程。这里讲述的bufferevent则是为了简化在accpet成功返回已连接套接字之后,从已连接套接字接收数据和发送数据(这也需要通过epoll处理)需要自建立缓冲区的过程,bufferevent通过链表建立自己的缓冲区,使得用户不必为缓冲数据的处理而烦恼,用户仅仅需要在回调函数里面通过bufferevent_read或者bufferevent_write读出或写入数据即可。
evsignal
ev_signal是libevent提供的对信号处理的一个模块,基本上是对sigaction函数的一个封装,并将本身是异步的信号转化为同步。信号本质上来说,是libevent事件中的子集,因此是兼容event的api的,不过为了清晰,内部还是进行了简单的封装。其实就是一种特殊的event。
hello-world客户端例程
hello-world-client.c:
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void eventcb(struct bufferevent *bev, short events, void *ptr)
{
if (events & BEV_EVENT_CONNECTED) {
} else if (events & BEV_EVENT_ERROR) {
/* An error occured while connecting. */
}
}
static void
conn_readcb(struct bufferevent *bev, void *user_data)
{
struct evbuffer *input = bufferevent_get_input(bev);
size_t len = evbuffer_get_length(input);
char msg[1024];
memset(msg,0,1024);
bufferevent_read(bev, msg, len);
msg[len] = '\0';
printf("recv %s from server\n", msg);
}
int main(int argc,char **argv)
{
if(argc!=3){
printf("请输入正确的ip地址和端口号\n");
exit(0);
}
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;
inet_aton(argv[1],&sin.sin_addr); /* 127.0.0.1 */
sin.sin_port = htons(atoi(argv[2])); /* Port 9995 */
bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, conn_readcb, NULL, eventcb, NULL);
if (bufferevent_socket_connect(bev,
(struct sockaddr *)&sin, sizeof(sin)) < 0) {
/* Error starting connection */
bufferevent_free(bev);
return -1;
}
bufferevent_disable(bev, EV_WRITE);
bufferevent_enable(bev, EV_READ);
event_base_dispatch(base);
bufferevent_free(bev);
printf("finished\n");
return 0;
}
编译运行:
服务器:
gcc hello-world.c -ohelloServer -levent
./helloServer
客户端:
gcc hello-world-client.c -ohello-world-client -levent
./hello-world-client 127.0.0.1 9995
下一节:Libevent学习记录(3)