很多时候,除了响应事件之外,应用还希望做一定的数据缓冲。比如说,写入数据的时候,通常的运行模式是:
l 决定要向连接写入一些数据,把数据放入到缓冲区中
l 等待连接可以写入
l 写入尽量多的数据
l 记住写入了多少数据,如果还有更多数据要写入,等待连接再次可以写入
这种缓冲IO模式很通用,libevent为此提供了一种通用机制,即bufferevent。bufferevent由一个底层的传输端口(如套接字),一个读取缓冲区和一个写入缓冲区组成。与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调。
有多种共享公用接口的bufferevent类型,编写本文时已存在以下类型:
l 基于套接字的bufferevent:使用event_*接口作为后端,通过底层流式套接字发送或者接收数据的bufferevent
l 异步IO bufferevent:使用Windows IOCP接口,通过底层流式套接字发送或者接收数据的bufferevent(仅用于Windows,试验中)
l 过滤型bufferevent:将数据传输到底层bufferevent对象之前,处理输入或者输出数据的bufferevent:比如说,为了压缩或者转换数据。
l 成对的bufferevent:相互传输数据的两个bufferevent。
注意:截止2.0.2-alpha版,这里列出的bufferevent接口还没有完全正交于所有的bufferevent类型。也就是说,下面将要介绍的接口不是都能用于所有bufferevent类型。libevent开发者在未来版本中将修正这个问题。
也请注意:当前bufferevent只能用于像TCP这样的面向流的协议,将来才可能会支持像UDP这样的面向数据报的协议。
本节描述的所有函数和类型都在event2/bufferevent.h中声明。特别提及的关于evbuffer的函数声明在event2/buffer.h中,详细信息请参考下一章。
1 bufferevent和evbuffer
每个bufferevent都有一个输入缓冲区和一个输出缓冲区,它们的类型都是“struct evbuffer”。有数据要写入到bufferevent时,添加数据到输出缓冲区;bufferevent中有数据供读取的时候,从输入缓冲区抽取(drain)数据。
evbuffer接口支持很多种操作,后面的章节将讨论这些操作。
2 回调和水位
每个bufferevent有两个数据相关的回调:一个读取回调和一个写入回调。默认情况下,从底层传输端口读取了任意量的数据之后会调用读取回调;输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用。通过调整bufferevent的读取和写入“水位(watermarks)”可以覆盖这些函数的默认行为。
每个bufferevent有四个水位:
l 读取低水位:读取操作使得输入缓冲区的数据量在此级别或者更高时,读取回调将被调用。默认值为0,所以每个读取操作都会导致读取回调被调用。
l 读取高水位:输入缓冲区中的数据量达到此级别后,bufferevent将停止读取,直到输入缓冲区中足够量的数据被抽取,使得数据量低于此级别。默认值是无限,所以永远不会因为输入缓冲区的大小而停止读取。
l 写入低水位:写入操作使得输出缓冲区的数据量达到或者低于此级别时,写入回调将被调用。默认值是0,所以只有输出缓冲区空的时候才会调用写入回调。
l 写入高水位:bufferevent没有直接使用这个水位。它在bufferevent用作另外一个bufferevent的底层传输端口时有特殊意义。请看后面关于过滤型bufferevent的介绍。
bufferevent也有“错误”或者“事件”回调,用于向应用通知非面向数据的事件,如连接已经关闭或者发生错误。定义了下列事件标志:
l BEV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。
l BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。
l BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调用EVUTIL_SOCKET_ERROR()。
l BEV_EVENT_TIMEOUT:发生超时。
l BEV_EVENT_EOF:遇到文件结束指示。
l BEV_EVENT_CONNECTED:请求的连接过程已经完成。
上述标志由2.0.2-alpha版新引入。
3 延迟回调
默认情况下,bufferevent的回调在相应的条件发生时立即被执行。(evbuffer的回调也是这样的,随后会介绍)在依赖关系复杂的情况下,这种立即调用会制造麻烦。比如说,假如某个回调在evbuffer A空的时候向其中移入数据,而另一个回调在evbuffer A满的时候从中取出数据。这些调用都是在栈上发生的,在依赖关系足够复杂的时候,有栈溢出的风险。
要解决此问题,可以请求bufferevent(或者evbuffer)延迟其回调。条件满足时,延迟回调不会立即调用,而是在event_loop()调用中被排队,然后在通常的事件回调之后执行。
(延迟回调由libevent 2.0.1-alpha版引入)
4 bufferevent的选项标志
创建bufferevent时可以使用一个或者多个标志修改其行为。可识别的标志有:
l BEV_OPT_CLOSE_ON_FREE:释放bufferevent时关闭底层传输端口。这将关闭底层套接字,释放底层bufferevent等。
l BEV_OPT_THREADSAFE:自动为bufferevent分配锁,这样就可以安全地在多个线程中使用bufferevent。
l BEV_OPT_DEFER_CALLBACKS:设置这个标志时,bufferevent延迟所有回调,如上所述。
l BEV_OPT_UNLOCK_CALLBACKS:默认情况下,如果设置bufferevent为线程安全的,则bufferevent会在调用用户提供的回调时进行锁定。设置这个选项会让libevent在执行回调的时候不进行锁定。
(BEV_OPT_UNLOCK_CALLBACKS由2.0.5-beta版引入,其他选项由2.0.1-alpha版引入)
5 与基于套接字的bufferevent一起工作
基于套接字的bufferevent是最简单的,它使用libevent的底层事件机制来检测底层网络套接字是否已经就绪,可以进行读写操作,并且使用底层网络调用(如readv、writev、WSASend、WSARecv)来发送和接收数据。
5.1 创建基于套接字的bufferevent
可以使用bufferevent_socket_new()创建基于套接字的bufferevent。
接口
base是event_base,options是表示bufferevent选项(BEV_OPT_CLOSE_ON_FREE等)的位掩码,fd是一个可选的表示套接字的文件描述符。如果想以后设置文件描述符,可以设置fd为-1。
成功时函数返回一个bufferevent,失败则返回NULL。
bufferevent_socket_new()函数由2.0.1-alpha版新引入。
5.2 在基于套接字的bufferevent上启动连接
如果bufferevent的套接字还没有连接上,可以启动新的连接。
接口
address和addrlen参数跟标准调用connect()的参数相同。如果还没有为bufferevent设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的。
如果已经为bufferevent设置套接字,调用bufferevent_socket_connect()将告知libevent套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。
连接完成之前可以向输出缓冲区添加数据。
如果连接成功启动,函数返回0;如果发生错误则返回-1。
示例
bufferevent_socket_connect()函数由2.0.2-alpha版引入。在此之前,必须自己手动在套接字上调用connect(),连接完成时,bufferevent将报告写入事件。
注意:如果使用bufferevent_socket_connect()发起连接,将只会收到BEV_EVENT_CONNECTED事件。如果自己调用connect(),则连接上将被报告为写入事件。
这个函数在2.0.2-alpha版引入。
5.3 通过主机名启动连接
常常需要将解析主机名和连接到主机合并成单个操作,libevent为此提供了:
接口
这个函数解析名字hostname,查找其family类型的地址(允许的地址族类型有AF_INET,AF_INET6和AF_UNSPEC)。如果名字解析失败,函数将调用事件回调,报告错误事件。如果解析成功,函数将启动连接请求,就像bufferevent_socket_connect()一样。
dns_base参数是可选的:如果为NULL,等待名字查找完成期间调用线程将被阻塞,而这通常不是期望的行为;如果提供dns_base参数,libevent将使用它来异步地查询主机名。关于DNS的更多信息,请看第九章。
跟bufferevent_socket_connect()一样,函数告知libevent,bufferevent上现存的套接字还没有连接,在名字解析和连接操作成功完成之前,不应该对套接字进行读取或者写入操作。
函数返回的错误可能是DNS主机名查询错误,可以调用bufferevent_socket_get_dns_error()来获取最近的错误。返回值0表示没有检测到DNS错误。
示例:简单的HTTP v0客户端
6 通用bufferevent操作
本节描述的函数可用于多种bufferevent实现。
6.1 释放bufferevent 接口
这个函数释放bufferevent。bufferevent内部具有引用计数,所以,如果释放bufferevent时还有未决的延迟回调,则在回调完成之前bufferevent不会被删除。
如果设置了BEV_OPT_CLOSE_ON_FREE标志,并且bufferevent有一个套接字或者底层bufferevent作为其传输端口,则释放bufferevent将关闭这个传输端口。
这个函数由libevent 0.8版引入。
6.2 操作回调、水位和启用/禁用 接口
bufferevent_setcb()函数修改bufferevent的一个或者多个回调。readcb、writecb和eventcb函数将分别在已经读取足够的数据、已经写入足够的数据,或者发生错误时被调用。每个回调函数的第一个参数都是发生了事件的bufferevent,最后一个参数都是调用bufferevent_setcb()时用户提供的cbarg参数:可以通过它向回调传递数据。事件回调的events参数是一个表示事件标志的位掩码:请看前面的“回调和水位”节。
要禁用回调,传递NULL而不是回调函数。注意:bufferevent的所有回调函数共享单个cbarg,所以修改它将影响所有回调函数。
这个函数由1.4.4版引入。类型名bufferevent_data_cb和bufferevent_event_cb由2.0.2-alpha版引入。
接口
可以启用或者禁用bufferevent上的EV_READ、EV_WRITE或者EV_READ | EV_WRITE事件。没有启用读取或者写入事件时,bufferevent将不会试图进行数据读取或者写入。
没有必要在输出缓冲区空时禁用写入事件:bufferevent将自动停止写入,然后在有数据等待写入时重新开始。
类似地,没有必要在输入缓冲区高于高水位时禁用读取事件:bufferevent将自动停止读取,然后在有空间用于读取时重新开始读取。
默认情况下,新创建的bufferevent的写入是启用的,但是读取没有启用。
可以调用bufferevent_get_enabled()确定bufferevent上当前启用的事件。
除了bufferevent_get_enabled()由2.0.3-alpha版引入外,这些函数都由0.8版引入。
接口
bufferevent_setwatermark()函数调整单个bufferevent的读取水位、写入水位,或者同时调整二者。(如果events参数设置了EV_READ,调整读取水位。如果events设置了EV_WRITE标志,调整写入水位)
对于高水位,0表示“无限”。
这个函数首次出现在1.4.4版。
示例
6.3 操作bufferevent中的数据
如果只是通过网络读取或者写入数据,而不能观察操作过程,是没什么好处的。bufferevent提供了下列函数用于观察要写入或者读取的数据。(Reading and writing data from the network does you no good if you can't look at it.Bufferevents give you these methods to give them data to write,and to get the data to read.)
接口
这两个函数提供了非常强大的基础:它们分别返回输入和输出缓冲区。关于可以对evbuffer类型进行的所有操作的完整信息,请看下一章。
如果写入操作因为数据量太少而停止(或者读取操作因为太多数据而停止),则向输出缓冲区添加数据(或者从输入缓冲区移除数据)将自动重启操作。
这些函数由2.0.1-alpha版引入。
接口
这些函数向bufferevent的输出缓冲区添加数据。bufferevent_write()将内存中从data处开始的size字节数据添加到输出缓冲区的末尾。bufferevent_write_buffer()移除buf的所有内容,将其放置到输出缓冲区的末尾。成功时这些函数都返回0,发生错误时则返回-1。
这些函数从0.8版就存在了。
接口
这些函数从bufferevent的输入缓冲区移除数据。bufferevent_read()至多从输入缓冲区移除size字节的数据,将其存储到内存中data处。函数返回实际移除的字节数。bufferevent_read_buffer()函数抽空输入缓冲区的所有内容,将其放置到buf中,成功时返回0,失败时返回-1。
注意,对于bufferevent_read(),data处的内存块必须有足够的空间容纳size字节数据。
bufferevent_read()函数从0.8版就存在了;bufferevnet_read_buffer()由2.0.1-alpha版引入。
示例
6.4 读写超时
跟其他事件一样,可以要求在一定量的时间已经流逝,而没有成功写入或者读取数据的时候调用一个超时回调。
接口
设置超时为NULL会移除超时回调。
试图读取数据的时候,如果至少等待了timeout_read秒,则读取超时事件将被触发。试图写入数据的时候,如果至少等待了timeout_write秒,则写入超时事件将被触发。
注意,只有在读取或者写入的时候才会计算超时。也就是说,如果bufferevent的读取被禁止,或者输入缓冲区满(达到其高水位),则读取超时被禁止。类似的,如果写入被禁止,或者没有数据待写入,则写入超时被禁止。
读取或者写入超时发生时,相应的读取或者写入操作被禁止,然后超时事件回调被调用,带有标志BEV_EVENT_TIMEOUT | BEV_EVENT_READING或者BEV_EVENT_TIMEOUT | BEV_EVENT_WRITING。
这个函数从2.0.1-alpha版就存在了,但是直到2.0.4-alpha版才对于各种bufferevent类型行为一致。
6.5 对bufferevent发起清空操作 接口
清空bufferevent要求bufferevent强制从底层传输端口读取或者写入尽可能多的数据,而忽略其他可能保持数据不被写入的限制条件。函数的细节功能依赖于bufferevent的具体类型。
iotype参数应该是EV_READ、EV_WRITE或者EV_READ | EV_WRITE,用于指示应该处理读取、写入,还是二者都处理。state参数可以是BEV_NORMAL、BEV_FLUSH或者BEV_FINISHED。BEV_FINISHED指示应该告知另一端,没有更多数据需要发送了;而BEV_NORMAL和BEV_FLUSH的区别依赖于具体的bufferevent类型。
失败时bufferevent_flush()返回-1,如果没有数据被清空则返回0,有数据被清空则返回1。
当前(2.0.5-beta版)仅有一些bufferevent类型实现了bufferevent_flush()。特别是,基于套接字的bufferevent没有实现。
7 类型特定的bufferevent函数
这些bufferevent函数不能支持所有bufferevent类型。
接口
这个函数调整bufev的优先级为pri。关于优先级的更多信息请看event_priority_set()。
成功时函数返回0,失败时返回-1。这个函数仅能用于基于套接字的bufferevent。
这个函数由1.0版引入。
接口
这些函数设置或者返回基于fd的事件的文件描述符。只有基于套接字的bufferevent支持setfd()。两个函数都在失败时返回-1;setfd()成功时返回0。
bufferevent_setfd()函数由1.4.4版引入;bufferevent_getfd()函数由2.0.2-alpha版引入。
接口
这个函数返回bufferevent的event_base,由2.0.9-rc版引入。
接口
这个函数返回作为bufferevent底层传输端口的另一个bufferevent。关于这种情况,请看关于过滤型bufferevent的介绍。
这个函数由2.0.2-alpha版引入。
8 手动锁定和解锁
有时候需要确保对bufferevent的一些操作是原子地执行的。为此,libevent提供了手动锁定和解锁bufferevent的函数。
接口
注意:如果创建bufferevent时没有指定BEV_OPT_THREADSAFE标志,或者没有激活libevent的线程支持,则锁定操作是没有效果的。
用这个函数锁定bufferevent将自动同时锁定相关联的evbuffer。这些函数是递归的:锁定已经持有锁的bufferevent是安全的。当然,对于每次锁定都必须进行一次解锁。
这些函数由2.0.6-rc版引入。
9 已废弃的bufferevent功能
从1.4到2.0版,bufferevent的后端代码一直在进行修订。在老的接口中,访问bufferevent结构体的内部是很平常的,并且还会使用依赖于这种访问的宏。
更复杂的是,老的代码有时候将“evbuffer”前缀用于bufferevent功能。
这里有一个在2.0版之前使用过的东西的概要:
老的函数定义在event.h中,而不是在event2/bufferevent.h中。
如果仍然需要访问bufferevent结构体内部的某些公有部分,可以包含event2/bufferevent_struct.h。但是不建议这么做:不同版本的Libevent中bufferevent结构体的内容可能会改变。本节描述的宏和名字只有在包含了event2/bufferevent_compat.h时才能使用。
较老版本中用于设置bufferevent的接口有所不同:
接口
bufferevent_new()函数仅仅在已经废弃的“默认”event_base上创建一个套接字bufferevent。调用bufferevent_base_set()可以调整套接字bufferevent的event_base。
较老版本不使用timeval结构体设置超时,而是使用秒数:
接口
最后要指出的是,2.0之前版本中的evbuffer实现是极其低效的,这对将bufferevent用于高性能应用是一个问题。
相关推荐
libevent是一个高性能的事件通知库,主要由Nick Mathewson和Abdul Razzaq开发,用C语言编写。它基于Reactor设计模式,并提供了一个轻量级、跨平台、多线程的事件循环,广泛应用于网络服务器的开发中,能够处理多种I/...
为方便阅读,把blog上的libevent源码深度剖析系列文章整合成一个pdf。
标题"libevent-vs2017编译"指的是使用Visual Studio 2017在Windows环境下编译开源库Libevent。Libevent是一个事件通知库,广泛用于编写高性能网络服务器,它提供了异步事件处理的能力,允许程序高效地处理大量并发...
本文将深入探讨如何在多线程环境中使用Libevent进行事件处理,并分享一个基于Libevent的多线程实现案例。 首先,理解Libevent的核心机制至关重要。Libevent提供了一个事件基础结构,它能够将来自不同来源的事件(如...
libevent是一个开源的事件通知库,它在C语言中实现,广泛应用于网络编程,尤其是服务器端的高并发处理。它通过提供一个统一的API,使得开发者能够方便地处理各种I/O事件,如网络套接字、信号、定时器等。在本资源中...
libevent2是libevent的第二个主要版本,带来了许多改进和新特性,旨在提高性能和易用性。 1. **事件模型** Libevent的核心是其事件模型,它基于一种非阻塞I/O模型。这种模型允许程序在等待I/O操作完成时继续执行...
《学习libevent入门电子书》是一本针对libevent这一开源事件库的学习资料,它对于深入理解和使用libevent具有极大的价值。libevent是一个高度可移植的库,它为编写高性能、异步网络应用提供了基础。在现代软件开发,...
在这个“运用libevent的缓存示例”中,我们将深入探讨如何利用libevent的API来创建和管理一个简单的缓存系统。 首先,让我们了解libevent的核心概念。它基于事件驱动模型,主要通过三种方式处理事件:水平触发...
libevent2.1.7在Linux安装过程 libevent是一个开源的异步I/O库,广泛应用于服务器端编程和网络编程中。安装libevent2.1.7需要遵循特定的步骤,以确保正确安装。下面将详细介绍libevent2.1.7在Linux安装过程。 一...
libevent库,文字版,很清晰,附带libevent参考手册(中文版) libevent源码深度剖析,根据libevent开源代码框架进行剖析,很不错值得学习借鉴,还有libevent中C语言的功底值得学习揣摩!
标题"libevent 参考手册中文版及源码解析"表明了本次学习的主题,重点是libevent库,包含了中文参考手册和源码的深度解析。libevent是一个开源的事件通知库,它使开发者能够方便地处理各种网络事件,如TCP、UDP、...
### libevent中文参考手册知识点详解 #### 一、概述与设计目标 **libevent**是一款专为编写高性能、跨平台的非阻塞IO应用程序而设计的C语言库。该库旨在帮助开发者解决在网络编程中常见的多路复用问题,通过高效地...
Libevent 是一个高度可移植的事件通知库,它允许程序员编写高效的网络服务器和并发应用程序。这个库通过将事件处理机制抽象化,使开发者能够轻松地处理各种网络事件,如读写、连接完成、信号和定时器等。在本文中,...
libevent是著名的开源网络库,被广泛应用于高性能网络服务器的开发,其主要目标是封装底层的网络调用,提供简洁易用的接口给开发人员。libevent库支持多平台,具有良好的跨平台特性,其设计目标是通过事件驱动的方式...
《编程与Libevent》这本书是关于使用Libevent库进行网络编程的重要参考资料。Libevent是一个开源的事件通知库,它提供了一种高效的方式来处理大量的并发I/O操作,特别适合于网络服务器的开发。以下是对该书内容及...
"libevent" 标签明确了技术核心,即libevent库,它是一个跨平台的事件通知库,可以处理TCP/UDP套接字、信号、时间事件等多种I/O事件。 "多线程" 标签表明了服务器采用了多线程编程模型,通过线程池或并发线程处理...
c++版本libevent,仿照libevent写的一个服务器框架,libevent的基本功能已实现,暂时不能在windows平台上使用,定时器是纯粹的timer wheel方式,与libevent的小根堆不一样,而且最大定时时间是固定的,暂时不支持...
《深入理解libevent-devel及其在CentOS 7中的应用》 libevent是一个开源的、跨平台的库,它提供了一种高效的方式来处理时间驱动和事件驱动的编程。这个库允许程序处理大量的并发连接,而无需复杂的多线程或异步编程...
《libevent参考手册(中文版)》和《libevent源码深度剖析》是两本针对libevent库的重要参考资料。libevent是一个开源的事件通知库,它使得开发者能够编写高性能、可扩展的网络服务器或者客户端应用。这个库的核心...
libevent是一个高性能的事件通知库,它使用IO多路复用技术为网络服务器提供了一种事件驱动的机制,让服务器能够高效地处理大量并发连接。libevent的设计目标是为了解决网络编程中的一些常见问题,如:支持多平台、...