C语言网络库libevent浅析

‌libevent最初由Nick Mathewson在2000年创建‌, 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动高性能;轻量级,专注于网络;源代码相当精炼、易读;跨平台,支持 Windows、 Linux、 *BSD 和 Mac Os;支持多种 I/O 多路复用技术, epoll、 poll、 dev/poll、 select 和 kqueue 等;支持 I/O,定时器和信号等事件;注册事件优先级。

Libevent 已经被广泛的应用,作为底层的网络库;比如 memcached、 Vomit、 Nylon、 Netchat等等。
libevent的不足:
1、全局变量的使用,让libevent很难在多线程环境中使用。
2、HTTP和DNS服务器等组件实现较差,安全性低。
3、定时器不精确,无法很好地处理时间跳变。
4、主结构体很大,包含了I/O,定时器和信号处理器等。

libev是libevent的进化版,移除了http/dns组件,优化了结构,单windows平台实现较差。
nodejs最初基于libev实现,为了满足windows需求创建了libuv。

libevent库编译

linux平台使用cmake编译

git clone git@github.com:libevent/libevent.git
cd libevent
mkdir build && cd build
cmake ..     # Default to Unix Makefiles.
make
make verify  # (optional)

include头文件功能

include/event2/buffer_compat.h
buffer.h 中的过时和不用的版本:仅提供向后兼容。

include/event2/buffer.h
用于缓冲网络发送或接收数据的功能。

include/event2/bufferevent_compat.h
提供兼容性,包含被弃用的 bufferevent_new 等函数。

include/event2/bufferevent_ssl.h
bufferevent 的 OpenSSL 支持。

include/event2/bufferevent_struct.h
bufferevent_struct.h 的使用已完全弃用;这些结构只公开,以便与使用它们的Libevent 2.0之前编写的程序向后兼容。

include/event2/bufferevent.h
用于缓冲网络发送或接收数据的功能。Bufferevents 比 evbuffers 级别更高:每个 Bufferevent 都有一个底层用于读取的 evbuffer 和一个用于写入的 evbuffer ,以及在某些情况下被调用的回调

include/event2/dns_compat.h
dns.h中函数的潜在非线程安全版本:仅为向后兼容性而提供。

include/event2/dns_struct.h
dns的数据结构。使用这些结构可能会损害与更高版本的Libevent的前向兼容性:小心!

include/event2/dns.h
异步DNS解析

include/event2/event_compat.h
event.h中函数的潜在非线程安全版本:仅为向后兼容性而提供。

include/event2/event_struct.h
event.h使用的结构。定义了 event event_callback 结构体。

include/event2/event.h
libevent库的核心功能,等待和接收事件以及使用事件库的核心功能。用于与各种平台特
定的、基于事件的非阻塞 IO 后端进行交互。它可以让你知道套接字什么时候是可读或可写的,
执行基本的超时功能,以及检测操作系统信号。

include/event2/http_compat.h
http.h中函数的潜在非线程安全版本仅用于向后兼容性。

include/event2/http_struct.h
http的数据结构。使用这些结构可能会损害与更高版本的Libevent的前向兼容性:小心!

include/event2/http.h
基于libevent的嵌入式HTTP服务器。

include/event2/keyvalq_struct.h
修复了人们不必使用<sys/queue.h>运行的问题,此代码与event_struct.h重复。

include/event2/listener.h
TCP监听器,监听接入的tcp链接。

include/event2/rpc_compat.h
rpc.h中函数的弃用版本:仅为向后兼容性而提供。

include/event2/rpc_struct.h
rpc.h使用的结构。直接使用这些结构可能会损害前向兼容性:小心!

include/event2/rpc.h
为RPC服务器和客户端提供基本支持。

include/event2/tag_compat.h
tag.h中的过时/弃用函数;仅提供向后兼容性。

include/event2/tag.h
用于将标记数据读取和写入缓冲区的辅助函数。

include/event2/thread.h
使用Libevent的多线程应用程序的函数。

include/event2/util.h
跨平台可移植性,相关套接字操作的常见便利功能,生成随机数。

include/event2/visibility.h
符号导出相关。

include/evdns.h
在Libevent 2.0及更高版本中,<evdns.h>标头已被弃用;请改用<event2/evdns.h>。

include/event.h
在Libevent 2.0及更高版本中,<event.h>标头已被弃用;请改用<event2/event.h>。

include/evhttp.h
在Libevent 2.0及更高版本中,<evhttp.h>标头已被弃用;请改用<event2/http.h>。

include/evrpc.h
在Libevent 2.0及更高版本中,<evrpc.h>标头已被弃用;请改用<event2/rpc.h>。

include/evutil.h
在Libevent 2.0及更高版本中,<evutil.h>标头已被弃用;请改用<event2/util.h>。

libevent库实现tcp服务端

sample/hello-world.c

此示例程序提供了一个简单的tcp服务器程序,用于侦听端口9995上的TCP连接。当有tcp客户端接入时,会向客户端发送一条消息,发送完成后关闭链接。

使用ctrl-c可以优雅地退出程序。


#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <signal.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!\n";//发送给客户端的消息

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;
	struct evconnlistener *listener;
	struct event *signal_event;

	struct sockaddr_in sin = {0};
#ifdef _WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

	base = event_base_new();//1.创建 event_base(include/event2/event.h)
	if (!base) {
		fprintf(stderr, "Could not initialize libevent!\n");
		return 1;
	}

	sin.sin_family = AF_INET;//IPv4 网络协议的套接字类型
	sin.sin_port = htons(PORT);//端口号
	//2.分配一个新的 evconnlistener 对象来监听给定地址上的传入TCP连接。(include/event2/listener.h)
	//LEV_OPT_CLOSE_ON_FREE:当连接监听器释放时,会自动关闭底层的socket。
	//LEV_OPT_REUSEABLE:关闭该socket后,其他socket可以马上使用同一个端口。
	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;
	}
	//3.创建一个信号事件,当执行ctrl-c时响应SIGINT事件,执行signal_cb回调,底层是执行了 event_new(include/event2/event.h)
	signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
	//4.将事件添加到待处理事件集中。(include/event2/event.h)
	if (!signal_event || event_add(signal_event, NULL)<0) {
		fprintf(stderr, "Could not create/add a signal event!\n");
		return 1;
	}
	//5.事件调度循环,此循环将运行事件库,底层运行 event_base_loop(include/event2/event.h)
	event_base_dispatch(base);

	evconnlistener_free(listener);//6.禁用并取消分配的 evconnlistener(include/event2/listener.h)
	event_free(signal_event);//7.释放 event_new 分配返回的结构体event*。(include/event2/event.h)
	event_base_free(base);//8.释放分配与事件库event_base关联的所有内存,并释放该库。(include/event2/event.h)

	printf("done\n");
	return 0;
}
//有tcp链接接入时执行的回调
//接口定义在(include/event2/bufferevent.h)
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_OPT_CLOSE_ON_FREE:当释放此缓冲区,我们关闭底层文件描述符
	bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
	if (!bev) {
		fprintf(stderr, "Error constructing bufferevent!");
		event_base_loopbreak(base);//立即中止活动的 event_base_loop
		return;
	}
	bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);//更改缓冲器的回调。
	bufferevent_enable(bev, EV_WRITE);//启用bufferevent写。
	bufferevent_disable(bev, EV_READ);//禁用bufferevent读。

	bufferevent_write(bev, MESSAGE, strlen(MESSAGE));//将数据写入缓冲区buffer。
}
//已经写入足够的数据(输出缓冲数据低于阈值,由bufferevent_setwatermark设置)执行的回调
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);//释放分配与缓冲器结构相关联的存储空间。
	}
}
//在文件描述符上有事件时调用的回调,主要是socket监听到错误时的回调
static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
	if (events & BEV_EVENT_EOF) {
		printf("Connection closed.\n");
	} 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);//释放分配与缓冲器结构相关联的存储空间。
}
//监听到某个信号时执行的回调(SIGINT)
static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
	struct event_base *base = user_data;
	struct timeval delay = { 2, 0 };

	printf("Caught an interrupt signal; exiting cleanly in two seconds.\n");

	event_base_loopexit(base, &delay);//在指定时间后退出事件循环
}

  • 7
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值