学习libevent的使用的时候,在http://unx.ca/log/libevent_echosrv1c/上看到有三个例子,第一个例子最简单,只是介绍一下大概的使用方法;第二个例子是自己处理异步socket出现可读可写;第三个例子是使用libevent内部提供的bufferd来处理可读可写的事件。
第一个例子:
libevent_echosrv1.c
/* * libevent echo server example. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> /* For inet_ntoa. */ #include <arpa/inet.h> /* Required by event.h. */ #include <sys/time.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <fcntl.h> #include <errno.h> #include <err.h> /* Libevent. */ #include <event.h> /* Port to listen on. */ #define SERVER_PORT 5555 /** * 这个结构是指定的客户端数据,这个例子中只指定了读事件对象。 */ struct client { struct event ev_read; }; /** * 将一个socket设置成非阻塞模式 */ int setnonblock(int fd) { int flags; flags = fcntl(fd, F_GETFL); if (flags < 0) return flags; flags |= O_NONBLOCK; if (fcntl(fd, F_SETFL, flags) < 0) return -1; return 0; } /** * 这个函数当客户端的socket可读时由libevent调用 */ void on_read(int fd, short ev, void *arg) { struct client *client = (struct client *)arg; u_char buf[8196]; int len, wlen; len = read(fd, buf, sizeof(buf)); if (len == 0) { /* 客户端断开连接,在这里移除读事件并且释放客户数据结构 */ printf("Client disconnected.\n"); close(fd); event_del(&client->ev_read); free(client); return; } else if (len < 0) { /* 出现了其它的错误,在这里关闭socket,移除事件并且释放客户数据结构 */ printf("Socket failure, disconnecting client: %s", strerror(errno)); close(fd); event_del(&client->ev_read); free(client); return; } /* 为了简便,我们直接将数据写回到客户端。通常我们不能在非阻塞的应用程序中这么做, * 我们应该将数据放到队列中,等待可写事件的时候再写回客户端。 */ wlen = write(fd, buf, len); if (wlen < len) { /* 我们没有把所有数据都写回。如果我们有适当的队列/缓存机制, * 我们能够在再次的可写事件中再次写入剩余的数据。因为这是一个简单的例子, * 我们仅仅舍去了没有写入的数据。 */ printf("Short write, not all data echoed back to client.\n"); } } /** * 当有一个连接请求准备被接受时,这个函数将被libevent调用。 */ void on_accept(int fd, short ev, void *arg) { int client_fd; struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); struct client *client; /* 接受新的连接 */ client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_len); if (client_fd == -1) { warn("accept failed"); return; } /* 设置客户端socket为非阻塞模式。 */ if (setnonblock(client_fd) < 0) warn("failed to set client socket non-blocking"); /* 我们接受了一个新的客户,分配一个新的客户数据结构对象来保存这个客户的状态。 */ client = calloc(1, sizeof(*client)); if (client == NULL) err(1, "malloc failed"); /* 设置读事件,libevent将在客户端socket可读时调用on_read函数。 * 我们也会我们也会不断的响应读事件,所以我们不用在每次读取时再次添加读事件。 */ event_set(&client->ev_read, client_fd, EV_READ|EV_PERSIST, on_read, client); /* 设置的事件并没有激活,使用添加事件让其激活。 */ event_add(&client->ev_read, NULL); printf("Accepted connection from %s\n", inet_ntoa(client_addr.sin_addr)); } int main(int argc, char **argv) { int listen_fd; struct sockaddr_in listen_addr; int reuseaddr_on = 1; /* 接受连接请求的事件对象。 */ struct event ev_accept; /* 初始化 libevent. */ event_init(); /* 创建我们监听的socket。 */ listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd < 0) err(1, "listen failed"); if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr_on, sizeof(reuseaddr_on)) == -1) err(1, "setsockopt failed"); memset(&listen_addr, 0, sizeof(listen_addr)); listen_addr.sin_family = AF_INET; listen_addr.sin_addr.s_addr = INADDR_ANY; listen_addr.sin_port = htons(SERVER_PORT); if (bind(listen_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0) err(1, "bind failed"); if (listen(listen_fd, 5) < 0) err(1, "listen failed"); /* 设置socket为非阻塞模式,使用libevent编程这是必不可少的。 */ if (setnonblock(listen_fd) < 0) err(1, "failed to set server socket to non-blocking"); /* 我们现在有了一个监听的socket,我们创建一个读事件,当有客户连接时,接收通知。 */ event_set(&ev_accept, listen_fd, EV_READ|EV_PERSIST, on_accept, NULL); event_add(&ev_accept, NULL); /* 开始 libevent 的事件循环。 */ event_dispatch(); return 0; }
|
第二个例子:
|
|
我认为第二个例子是非常有用的,因为我们可以在服务器端将内存池集成进去。但是第二个例子仅仅考虑了写入时的队列问题,
并没有考虑到读取时,如果一次没有读取完所有的数据怎么办,其实和写入一样处理就可以了,就是记录下读取的偏移。