libevent 及网络连接管理模块 bufferevent 详解
2022-04-017.9k 阅读
libevent 简介
libevent 是一个用 C 语言编写的、轻量级的开源高性能事件通知库,它提供了一个跨平台的事件驱动编程模型,用于处理网络事件、文件描述符事件、信号以及定时事件等。它的设计目标是简化事件驱动编程,提高程序的性能和可扩展性,尤其适用于网络编程领域。
libevent 主要有以下几个特点:
- 跨平台:支持多种操作系统,包括 Linux、Windows、Mac OS 等,这使得开发者可以编写可移植的网络应用程序。
- 高性能:采用高效的数据结构和算法,能够在高并发环境下保持较低的资源消耗和较高的处理效率。例如,它使用了时间堆来管理定时事件,使得添加和删除定时事件的时间复杂度为 O(log n),极大提高了定时事件管理的效率。
- 事件驱动模型:基于事件驱动的编程模型,使得程序可以在事件发生时作出响应,而不是采用传统的轮询方式,从而避免了不必要的 CPU 开销。
- 丰富的事件类型支持:除了网络事件外,还支持文件描述符事件(如标准输入输出、管道等)、信号事件以及定时事件。
libevent 的核心组件
- 事件循环(event loop)
- 事件循环是 libevent 的核心部分,它负责监听注册的事件,并在事件发生时调用相应的回调函数。事件循环会不断地检查是否有事件发生,一旦有事件触发,就会调用预先设置好的回调函数来处理该事件。
- 在 libevent 中,通过
event_base_dispatch
函数启动事件循环。例如:
#include <event2/event.h>
int main() {
struct event_base *base;
base = event_base_new();
if (!base) {
return 1;
}
event_base_dispatch(base);
event_base_free(base);
return 0;
}
- 在上述代码中,首先通过
event_base_new
创建一个事件基(event base),然后调用event_base_dispatch
启动事件循环,最后使用event_base_free
释放事件基资源。
- 事件(event)
- 事件是 libevent 中表示特定类型事件的结构体,包括事件发生的文件描述符、事件类型(如读事件、写事件等)以及事件触发时要调用的回调函数等信息。
- 可以通过
event_new
函数来创建一个事件。例如:
#include <event2/event.h>
#include <stdio.h>
void read_callback(evutil_socket_t fd, short what, void *arg) {
char buf[1024];
int len = recv(fd, buf, sizeof(buf), 0);
if (len > 0) {
buf[len] = '\0';
printf("Received: %s\n", buf);
}
}
int main() {
struct event_base *base;
struct event *ev;
evutil_socket_t sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
return 1;
}
base = event_base_new();
if (!base) {
close(sockfd);
return 1;
}
ev = event_new(base, sockfd, EV_READ | EV_PERSIST, read_callback, NULL);
if (!ev) {
close(sockfd);
event_base_free(base);
return 1;
}
event_add(ev, NULL);
event_base_dispatch(base);
event_free(ev);
event_base_free(base);
close(sockfd);
return 0;
}
- 在上述代码中,
event_new
创建了一个针对套接字sockfd
的读事件ev
,当读事件发生时,会调用read_callback
回调函数。EV_PERSIST
标志表示事件触发后不会自动删除,会一直监听该事件。event_add
函数将事件添加到事件基中。
- 事件基(event base)
- 事件基是管理所有事件的容器,它包含了事件循环所需的各种资源和状态信息,如文件描述符集合、定时器队列等。一个应用程序通常只有一个事件基,但也可以根据需要创建多个事件基。
- 事件基负责维护一个事件列表,当事件发生时,它会从事件列表中找到对应的事件,并调用其回调函数。例如前面代码中的
event_base_new
创建事件基,event_base_dispatch
在事件基上启动事件循环,event_base_free
释放事件基资源。
bufferevent 概述
bufferevent 是 libevent 中用于管理网络连接的模块,它基于事件驱动模型,提供了一种方便的方式来处理网络数据的读写操作。bufferevent 主要有以下优点:
- 缓冲管理:自动处理网络数据的缓冲,减少了用户手动管理缓冲区的复杂性。它内部维护了读缓冲区和写缓冲区,当从网络读取数据时,数据会先存储在读缓冲区中,用户可以从读缓冲区中读取数据,而不必担心数据的部分读取问题。同样,当向网络发送数据时,数据会先写入写缓冲区,libevent 会负责将写缓冲区中的数据发送出去。
- 事件驱动:与 libevent 的事件循环紧密结合,通过注册事件回调函数,当网络连接状态发生变化(如连接建立、断开、有数据可读、可写等)时,会自动触发相应的回调函数,使得网络编程更加简洁高效。
- 协议无关:可以用于处理多种网络协议,如 TCP、UDP 等,开发者只需要关注数据的读写和连接状态的处理,而不需要关心具体协议的细节。
bufferevent 的创建
- 基于 TCP 套接字创建 bufferevent
- 在 libevent 中,可以通过
bufferevent_socket_new
函数基于 TCP 套接字创建一个 bufferevent。例如:
- 在 libevent 中,可以通过
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
void read_callback(struct bufferevent *bev, void *ctx) {
char buf[1024];
size_t len = bufferevent_read(bev, buf, sizeof(buf));
if (len > 0) {
buf[len] = '\0';
printf("Received: %s\n", buf);
}
}
void event_callback(struct bufferevent *bev, short events, void *ctx) {
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
bufferevent_free(bev);
} else if (events & BEV_EVENT_ERROR) {
printf("Some other error occurred.\n");
bufferevent_free(bev);
}
}
int main() {
struct event_base *base;
struct bufferevent *bev;
evutil_socket_t sockfd;
struct sockaddr_in servaddr;
base = event_base_new();
if (!base) {
return 1;
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
event_base_free(base);
return 1;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8080);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
close(sockfd);
event_base_free(base);
return 1;
}
bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
close(sockfd);
event_base_free(base);
return 1;
}
bufferevent_setcb(bev, read_callback, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
event_base_dispatch(base);
bufferevent_free(bev);
event_base_free(base);
return 0;
}
- 在上述代码中,首先创建了一个 TCP 套接字
sockfd
并连接到服务器。然后通过bufferevent_socket_new
创建了一个 buffereventbev
,BEV_OPT_CLOSE_ON_FREE
选项表示当 bufferevent 被释放时,对应的套接字也会被关闭。接着通过bufferevent_setcb
设置了读回调函数read_callback
和事件回调函数event_callback
。read_callback
用于处理从网络读取的数据,event_callback
用于处理连接状态变化的事件,如连接关闭(BEV_EVENT_EOF
)或发生错误(BEV_EVENT_ERROR
)。最后通过bufferevent_enable
启用读和写事件,并启动事件循环。
- 基于 UDP 套接字创建 bufferevent
- 创建基于 UDP 套接字的 bufferevent 与 TCP 类似,但使用
bufferevent_socket_new
时需要指定BEV_OPT_UDP
选项。例如:
- 创建基于 UDP 套接字的 bufferevent 与 TCP 类似,但使用
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
void read_callback(struct bufferevent *bev, void *ctx) {
char buf[1024];
size_t len = bufferevent_read(bev, buf, sizeof(buf));
if (len > 0) {
buf[len] = '\0';
printf("Received: %s\n", buf);
}
}
void event_callback(struct bufferevent *bev, short events, void *ctx) {
if (events & BEV_EVENT_ERROR) {
printf("Some error occurred.\n");
bufferevent_free(bev);
}
}
int main() {
struct event_base *base;
struct bufferevent *bev;
evutil_socket_t sockfd;
struct sockaddr_in servaddr;
base = event_base_new();
if (!base) {
return 1;
}
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1) {
event_base_free(base);
return 1;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8080);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
bev = bufferevent_socket_new(base, sockfd, BEV_OPT_UDP | BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
close(sockfd);
event_base_free(base);
return 1;
}
bufferevent_setcb(bev, read_callback, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ);
event_base_dispatch(base);
bufferevent_free(bev);
event_base_free(base);
close(sockfd);
return 0;
}
- 在这段代码中,创建了一个 UDP 套接字
sockfd
,然后通过bufferevent_socket_new
创建 buffereventbev
,并设置了读回调函数和事件回调函数。由于 UDP 是无连接的协议,这里没有连接操作,并且在bufferevent_enable
中只启用了读事件,因为 UDP 通常不需要像 TCP 那样主动处理写事件(除非是需要发送数据的情况)。
bufferevent 的回调函数
- 读回调函数
- 读回调函数在 bufferevent 有数据可读时被调用。在回调函数中,可以通过
bufferevent_read
函数从读缓冲区中读取数据。例如前面代码中的read_callback
:
- 读回调函数在 bufferevent 有数据可读时被调用。在回调函数中,可以通过
void read_callback(struct bufferevent *bev, void *ctx) {
char buf[1024];
size_t len = bufferevent_read(bev, buf, sizeof(buf));
if (len > 0) {
buf[len] = '\0';
printf("Received: %s\n", buf);
}
}
bufferevent_read
函数会从bev
的读缓冲区中读取数据到buf
中,返回实际读取的字节数。如果读取成功且有数据,则将数据以字符串形式打印出来。
- 写回调函数
- 写回调函数在 bufferevent 的写缓冲区有空间可写,或者之前写入的数据已经成功发送时被调用。通常用于处理数据发送的后续操作,比如检查是否所有数据都已发送完毕。例如:
void write_callback(struct bufferevent *bev, void *ctx) {
// 可以检查写缓冲区是否已空,确认数据已全部发送
if (bufferevent_write_buffer_empty(bev)) {
printf("All data has been sent.\n");
}
}
- 在上述代码中,
bufferevent_write_buffer_empty
函数用于检查写缓冲区是否为空,如果为空则表示之前写入的数据已全部发送。
- 事件回调函数
- 事件回调函数用于处理网络连接状态的变化,如连接关闭、发生错误等。例如:
void event_callback(struct bufferevent *bev, short events, void *ctx) {
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
bufferevent_free(bev);
} else if (events & BEV_EVENT_ERROR) {
printf("Some other error occurred.\n");
bufferevent_free(bev);
}
}
- 在
event_callback
中,通过events
参数来判断发生的事件类型。BEV_EVENT_EOF
表示连接关闭,BEV_EVENT_ERROR
表示发生错误。当这些事件发生时,通常需要释放 bufferevent 资源。
bufferevent 的数据读写
- 读数据
- 如前面读回调函数示例中所示,通过
bufferevent_read
函数从 bufferevent 的读缓冲区中读取数据。bufferevent_read
函数原型为:
- 如前面读回调函数示例中所示,通过
size_t bufferevent_read(struct bufferevent *bufev, void *dst, size_t size);
bufev
是要读取数据的 bufferevent,dst
是存储读取数据的缓冲区,size
是要读取的最大字节数。函数返回实际读取的字节数。如果返回 0,表示没有数据可读;如果返回 -1,表示发生错误。
- 写数据
- 使用
bufferevent_write
函数向 bufferevent 的写缓冲区写入数据。例如:
- 使用
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
void read_callback(struct bufferevent *bev, void *ctx) {
char buf[1024];
size_t len = bufferevent_read(bev, buf, sizeof(buf));
if (len > 0) {
buf[len] = '\0';
printf("Received: %s\n", buf);
}
}
void event_callback(struct bufferevent *bev, short events, void *ctx) {
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
bufferevent_free(bev);
} else if (events & BEV_EVENT_ERROR) {
printf("Some other error occurred.\n");
bufferevent_free(bev);
}
}
int main() {
struct event_base *base;
struct bufferevent *bev;
evutil_socket_t sockfd;
struct sockaddr_in servaddr;
base = event_base_new();
if (!base) {
return 1;
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
event_base_free(base);
return 1;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8080);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
close(sockfd);
event_base_free(base);
return 1;
}
bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
close(sockfd);
event_base_free(base);
return 1;
}
bufferevent_setcb(bev, read_callback, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
const char *msg = "Hello, Server!";
bufferevent_write(bev, msg, strlen(msg));
event_base_dispatch(base);
bufferevent_free(bev);
event_base_free(base);
close(sockfd);
return 0;
}
- 在上述代码中,
bufferevent_write
函数将字符串 "Hello, Server!" 写入到 bufferevent 的写缓冲区。bufferevent_write
函数原型为:
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
bufev
是要写入数据的 bufferevent,data
是要写入的数据缓冲区,size
是要写入的数据字节数。函数返回 0 表示成功,返回 -1 表示失败。
bufferevent 的状态管理
- 连接状态
- 通过事件回调函数中的
events
参数来判断连接状态。例如,BEV_EVENT_EOF
表示连接关闭,BEV_EVENT_CONNECTED
表示连接已建立(对于主动连接的情况)。
- 通过事件回调函数中的
void event_callback(struct bufferevent *bev, short events, void *ctx) {
if (events & BEV_EVENT_CONNECTED) {
printf("Connection established.\n");
} else if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
bufferevent_free(bev);
} else if (events & BEV_EVENT_ERROR) {
printf("Some other error occurred.\n");
bufferevent_free(bev);
}
}
- 在上述代码中,当
BEV_EVENT_CONNECTED
事件发生时,表明连接已成功建立,可以在此时进行一些初始化操作,比如发送初始数据等。
- 缓冲区状态
- 可以通过
bufferevent_read_buffer_empty
和bufferevent_write_buffer_empty
函数来检查读缓冲区和写缓冲区的状态。
- 可以通过
void write_callback(struct bufferevent *bev, void *ctx) {
if (bufferevent_write_buffer_empty(bev)) {
printf("All data has been sent.\n");
}
}
- 在写回调函数中,
bufferevent_write_buffer_empty
用于检查写缓冲区是否为空,从而判断数据是否已全部发送。同样,bufferevent_read_buffer_empty
可用于检查读缓冲区是否已无数据可读。
bufferevent 的应用场景
- 客户端开发
- 在网络客户端开发中,bufferevent 可以方便地处理与服务器的连接、数据读写以及连接状态管理。例如开发一个简单的 HTTP 客户端,通过 bufferevent 可以轻松地发送 HTTP 请求并接收服务器的响应。
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
void read_callback(struct bufferevent *bev, void *ctx) {
char buf[1024];
size_t len = bufferevent_read(bev, buf, sizeof(buf));
if (len > 0) {
buf[len] = '\0';
printf("Received: %s\n", buf);
}
}
void event_callback(struct bufferevent *bev, short events, void *ctx) {
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
bufferevent_free(bev);
} else if (events & BEV_EVENT_ERROR) {
printf("Some other error occurred.\n");
bufferevent_free(bev);
}
}
int main() {
struct event_base *base;
struct bufferevent *bev;
evutil_socket_t sockfd;
struct sockaddr_in servaddr;
base = event_base_new();
if (!base) {
return 1;
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
event_base_free(base);
return 1;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(80);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
close(sockfd);
event_base_free(base);
return 1;
}
bev = bufferevent_socket_new(base, sockfd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
close(sockfd);
event_base_free(base);
return 1;
}
bufferevent_setcb(bev, read_callback, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
const char *http_req = "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n";
bufferevent_write(bev, http_req, strlen(http_req));
event_base_dispatch(base);
bufferevent_free(bev);
event_base_free(base);
close(sockfd);
return 0;
}
- 在上述代码中,构建了一个简单的 HTTP GET 请求,并通过 bufferevent 发送到服务器,同时在回调函数中处理服务器的响应。
- 服务器开发
- 在服务器端,bufferevent 可以管理多个客户端连接,高效地处理并发请求。例如开发一个简单的 echo 服务器:
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
void read_callback(struct bufferevent *bev, void *ctx) {
char buf[1024];
size_t len = bufferevent_read(bev, buf, sizeof(buf));
if (len > 0) {
bufferevent_write(bev, buf, len);
}
}
void event_callback(struct bufferevent *bev, short events, void *ctx) {
if (events & BEV_EVENT_EOF) {
printf("Connection closed.\n");
bufferevent_free(bev);
} else if (events & BEV_EVENT_ERROR) {
printf("Some other error occurred.\n");
bufferevent_free(bev);
}
}
void accept_connection(evutil_socket_t listener, short event, void *arg) {
struct event_base *base = (struct event_base *)arg;
struct sockaddr_storage ss;
socklen_t slen = sizeof(ss);
evutil_socket_t fd = accept(listener, (struct sockaddr *)&ss, &slen);
if (fd < 0) {
perror("accept");
return;
}
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
close(fd);
return;
}
bufferevent_setcb(bev, read_callback, NULL, event_callback, NULL);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}
int main() {
struct event_base *base;
struct event *listener_event;
evutil_socket_t listener;
struct sockaddr_in sin;
base = event_base_new();
if (!base) {
return 1;
}
listener = socket(AF_INET, SOCK_STREAM, 0);
if (listener == -1) {
event_base_free(base);
return 1;
}
evutil_make_socket_nonblocking(listener);
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(8080);
sin.sin_addr.s_addr = INADDR_ANY;
if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) == -1) {
close(listener);
event_base_free(base);
return 1;
}
if (listen(listener, 16) == -1) {
close(listener);
event_base_free(base);
return 1;
}
listener_event = event_new(base, listener, EV_READ | EV_PERSIST, accept_connection, base);
if (!listener_event) {
close(listener);
event_base_free(base);
return 1;
}
event_add(listener_event, NULL);
event_base_dispatch(base);
event_free(listener_event);
close(listener);
event_base_free(base);
return 0;
}
- 在上述代码中,服务器监听指定端口,当有新的客户端连接时,通过
bufferevent_socket_new
创建一个 bufferevent 来管理该连接,在read_callback
回调函数中,将客户端发送的数据回显给客户端。
总结
libevent 作为一个高性能的事件通知库,为后端网络编程提供了强大的支持。而 bufferevent 作为 libevent 中管理网络连接的重要模块,通过其缓冲管理、事件驱动以及协议无关等特性,大大简化了网络编程的复杂性,提高了开发效率和程序的性能。无论是开发客户端还是服务器端应用,libevent 和 bufferevent 都能在处理网络连接和数据读写方面发挥重要作用,是后端开发中不可或缺的工具。在实际应用中,开发者需要深入理解其原理和使用方法,结合具体的业务需求,合理运用这些技术来构建高效、稳定的网络应用程序。