概述
/*
This exmple 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>
#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,既是只读的,又是只在当前模块中可见的
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;
//定义一个sockaddr_in结构体
struct sockaddr_in sin;
#ifdef WIN32
WSADATA wsa_data;
WSAStartup(0x0201, &wsa_data);
#endif
//创建一个event_base,失败则返回0,即false
base = event_base_new();
if (!base) {
fprintf(stderr, "Could not initialize libevent!n");
return 1;
}
//用0填充sockaddr结构体,即清空sockaddr
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
//用来将主机字节顺序转化为网络字节顺序
sin.sin_port = htons(PORT);
/*
struct evconnlistener *evconnlistener_new_bind(struct event_base *base,
evconnlistener_cb cb, void *ptr, unsigned flags, int backlog,
const struct sockaddr *sa, int socklen);
base参数是监听器用于监听连接的event_base,cb是收到新连接时要调用的回调函数;如果cb为NULL,
则监听器是禁用的,直到设置了回调函数为止。ptr指针将传递给回调函数。flags参数控制回调函数的行为,
下面会更详细论述。backlog是任何时刻网络栈允许处于还未接受状态的最大未决连接数。
更多细节请查看系统的listen()函数文档。如果backlog是负的,libevent会试图挑选一个较好的值;
如果为0,libevent认为已经对提供的套接字调用了listen()。
*/
/*
可识别的标志
可以给evconnlistener_new()函数的flags参数传入一些标志。可以用或(OR)运算任意连接下述标志:
l LEV_OPT_LEAVE_SOCKETS_BLOCKING
默认情况下,连接监听器接收新套接字后,会将其设置为非阻塞的,以便将其用于libevent。如果不想要这种行为,可以设置这个标志。
l LEV_OPT_CLOSE_ON_FREE
如果设置了这个选项,释放连接监听器会关闭底层套接字。
l LEV_OPT_CLOSE_ON_EXEC
如果设置了这个选项,连接监听器会为底层套接字设置close-on-exec标志。更多信息请查看fcntl和FD_CLOEXEC的平台文档。
l LEV_OPT_REUSEABLE
某些平台在默认情况下,关闭某监听套接字后,要过一会儿其他套接字才可以绑定到同一个端口。设置这个标志会让libevent标记套接字是可重用的,这样一旦关闭,可以立即打开其他套接字,在相同端口进行监听。
l LEV_OPT_THREADSAFE
为监听器分配锁,这样就可以在多个线程中安全地使用了。这是2.0.8-rc的新功能。
*/
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;
}
/*
define evsignal_new(base, signum, callback, arg) event_new(base, signum, EV_SIGNAL|EV_PERSIST, cb, arg)
除了提供一个信号编号代替文件描述符之外,各个参数与event_new()相同。
注意:信号回调是信号发生后在事件循环中被执行的,所以可以安全地调用通常不能在POSIX风格信号处理器中使用的函数。
警告:不要在信号事件上设置超时,这可能是不被支持的。[待修正:真是这样的吗?]
失败返回0,即false
*/
signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
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);
//要释放连接监听器,调用evconnlistener_free()。
evconnlistener_free(listener);
//释放信号事件
event_free(signal_event);
//释放event_base
event_base_free(base);
printf("donen");
return 0;
}
/*
连接监听器回调
接口
typedef void (*evconnlistener_cb)(struct evconnlistener *listener,
evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr);
接收到新连接会调用提供的回调函数。listener参数是接收连接的连接监听器。sock参数是新接收的套接字。
addr和len参数是接收连接的地址和地址长度。ptr是调用evconnlistener_new()时用户提供的指针。
void *user_data是指向event_base的指针
*/
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;
/*
很多时候,除了响应事件之外,应用还希望做一定的数据缓冲。比如说,写入数据的时候,通常的运行模式是:
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。
*/
struct bufferevent *bev;
/*使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base
创建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版引入)
*/
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
if (!bev) {
fprintf(stderr, "Error constructing bufferevent!");
/*
int event_base_loopexit(struct event_base *base,
const struct timeval *tv);
int event_base_loopbreak(struct event_base *base);
event_base_loopexit()让event_base在给定时间之后停止循环。如果tv参数为NULL,event_base会立即停止循环,没有延时。如果event_base当前正在执行任何激活事件的回调,则回调会继续运行,直到运行完所有激活事件的回调之才退出。
event_base_loopbreak()让event_base立即退出循环。它与event_base_loopexit(base,NULL)的不同在于,如果event_base当前正在执行激活事件的回调,它将在执行完当前正在处理的事件后立即退出。
注意event_base_loopexit(base,NULL)和event_base_loopbreak(base)在事件循环没有运行时的行为不同:前者安排下一次事件循环在下一轮回调完成后立即停止(就好像带EVLOOP_ONCE标志调用一样);后者却仅仅停止当前正在运行的循环,如果事件循环没有运行,则没有任何效果。
这两个函数都在成功时返回0,失败时返回-1。
*/
event_base_loopbreak(base);
return;
}
/*设置读写对应的回调函数
void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb, bufferevent_data_cb writecb,
bufferevent_event_cb eventcb, void *cbarg)
eg. bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);
bufferevent_setcb()函数修改bufferevent的一个或者多个回调。readcb、writecb和eventcb函数将分别在已经读取足够的数据、已经写入足够的数据,或者发生错误时被调用。每个回调函数的第一个参数都是发生了事件的bufferevent,最后一个参数都是调用bufferevent_setcb()时用户提供的cbarg参数:可以通过它向回调传递数据。事件回调的events参数是一个表示事件标志的位掩码:
*/
bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
/*
启用读写事件,其实是调用了event_add将相应读写事件加入事件监听队列poll。正如文档所说,如果相应事件不置为true,bufferevent是不会读写数据的
int bufferevent_enable(struct bufferevent *bufev, short event)
eg. bufferevent_enable(bev, EV_READ|EV_WRITE);
可以启用或者禁用bufferevent上的EV_READ、EV_WRITE或者EV_READ | EV_WRITE事件。没有启用读取或者写入事件时,bufferevent将不会试图进行数据读取或者写入。
没有必要在输出缓冲区空时禁用写入事件:bufferevent将自动停止写入,然后在有数据等待写入时重新开始。
类似地,没有必要在输入缓冲区高于高水位时禁用读取事件:bufferevent将自动停止读取,然后在有空间用于读取时重新开始读取。
默认情况下,新创建的bufferevent的写入是启用的,但是读取没有启用。
可以调用bufferevent_get_enabled()确定bufferevent上当前启用的事件。
*/
bufferevent_enable(bev, EV_WRITE);
bufferevent_disable(bev, EV_READ);
/*
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
int bufferevent_write_buffer(struct bufferevent *bufev, struct evbuffer *buf);
这些函数向bufferevent的输出缓冲区添加数据。bufferevent_write()将内存中从data处开始的size字节数据添加到输出缓冲区的末尾。bufferevent_write_buffer()移除buf的所有内容,将其放置到输出缓冲区的末尾。成功时这些函数都返回0,发生错误时则返回-1。
这些函数从0.8版就存在了。
*/
bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}
//bufferevent *bev满时的写回调函数
static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
/*
struct evbuffer *bufferevent_get_input(struct bufferevent *bufev);
struct evbuffer *bufferevent_get_output(struct bufferevent *bufev);
这两个函数提供了非常强大的基础:它们分别返回输入和输出缓冲区。关于可以对evbuffer类型进行的所有操作的完整信息,请看下一章。
如果写入操作因为数据量太少而停止(或者读取操作因为太多数据而停止),则向输出缓冲区添加数据(或者从输入缓冲区移除数据)将自动重启操作。
这些函数由2.0.1-alpha版引入。
*/
struct evbuffer *output = bufferevent_get_output(bev);
//当输出缓冲区的内容长度为0,即全部输出之后
if (evbuffer_get_length(output) == 0) {
printf("flushed answern");
/*
void bufferevent_free(struct bufferevent *bev);
这个函数释放bufferevent。bufferevent内部具有引用计数,所以,如果释放bufferevent时还有未决的延迟回调,则在回调完成之前bufferevent不会被删除。
如果设置了BEV_OPT_CLOSE_ON_FREE标志,并且bufferevent有一个套接字或者底层bufferevent作为其传输端口,则释放bufferevent将关闭这个传输端口。
这个函数由libevent 0.8版引入。
*/
bufferevent_free(bev);
}
}
//发生连接事件时的回调函数
static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{
/*
回调和水位
每个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版新引入。
*/
if (events & BEV_EVENT_EOF) {
printf("Connection closed.n");
} else if (events & BEV_EVENT_ERROR) {
printf("Got an error on the connection: %sn",
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;
//定义一个2秒的事件结构体
struct timeval delay = { 2, 0 };
printf("Caught an interrupt signal; exiting cleanly in two seconds.n");
/*
int event_base_loopexit(struct event_base *base, const struct timeval *tv);
event_base_loopexit()让event_base在给定时间之后停止循环。如果tv参数为NULL,event_base会立即停止循环,没有延时。如果event_base当前正在执行任何激活事件的回调,则回调会继续运行,直到运行完所有激活事件的回调之才退出。
*/
event_base_loopexit(base, &delay);
}
最后
以上就是自由菠萝为你收集整理的libevent中的hello-world.c解讯的全部内容,希望文章能够帮你解决libevent中的hello-world.c解讯所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复