Libevent学习记录(2)

Libevent官方例程hello-world代码分析

通过libevent提供的Hello_World demo简单学习基于libevent的TCP服务器的实现。

注释源码:

以下注释均为自己学习的时候进行的注释,有错的欢迎指出

/*
  This example 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>
#include <unistd.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!";

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;	//定义event_base变量
	struct evconnlistener *listener;//定义一个evconnlistener来监测端口(9995)
	struct event *signal_event;	//定义一个处理信号的事件来处理ctrl+c的信号

	struct sockaddr_in sin = {0};	//socket连接的协议地址结构体
#ifdef _WIN32
	WSADATA wsa_data;
	WSAStartup(0x0201, &wsa_data);
#endif

	base = event_base_new();
	if (!base) {
		fprintf(stderr, "Could not initialize libevent!\n");
		return 1;
	}
	//创建socket连接,设置sockaddr_in对应参数
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	//参数说明:base是event_base事件集;listener_cb是回调函数函数名;(void *)base提供给回调函数的参数;标志位;backlog这里一般设置为-1;后面两个参数就是const struct sockaddr *指针和对应大小
	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;
	}

	signal_event = evsignal_new(base, SIGINT, signal_cb, (void *)base);
	//判断signal_event是否创建成功,成功则并将其加入libevent事件链表上
	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);//实际上是调用了event_base_loop();
	//依次释放listener,signal_event和event_base的资源,结束
	evconnlistener_free(listener);
	event_free(signal_event);
	event_base_free(base);

	printf("done\n");
	return 0;
}
//参数说明:监听器;用于和客户端连接的socketfd;sockaddr结构体;sockaddr对应长度;用户传递过来的参数;
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;
	//开始需要调用 bufferevent_socket_new 创建一个bufferevent;BEV_OPT_CLOSE_ON_FREE:释放bufferevent时关闭底层传输端口
	bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
	if (!bev) {
		fprintf(stderr, "Error constructing bufferevent!");
		event_base_loopbreak(base);//结束事件主循环
		return;
	}
	//使用bufferevent_setcb,设置当已经写入足够的数据时调用回调函数conn_writecb,发生错误时调用conn_eventcb
	bufferevent_setcb(bev, NULL, conn_writecb, conn_eventcb, NULL);
	//启用写事件
	bufferevent_enable(bev, EV_WRITE);
	//禁用读事件	
	bufferevent_disable(bev, EV_READ);

	//调用 bufferevent_write 写入数据,从data处开始的size字节数据从内存中移除,并添加到输出缓冲区的末尾
	bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
}

static void
conn_writecb(struct bufferevent *bev, void *user_data)
{
	//获取eventbuffer的输出缓冲区bev
	struct evbuffer *output = bufferevent_get_output(bev);
	
	//当输出缓冲区没有数据时释放bev
	if (evbuffer_get_length(output) == 0) {
		//释放bev
		printf("flushed answer\n");
		bufferevent_free(bev);
		
	}
}

static void
conn_eventcb(struct bufferevent *bev, short events, void *user_data)
{	
	//BEV_EVENT_EOF遇到文件结束指示
	if (events & BEV_EVENT_EOF) {
		printf("Connection closed.\n");
	}
	//BEV_EVENT_ERROR操作时发生错误 
	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);
}

static void
signal_cb(evutil_socket_t sig, short events, void *user_data)
{
	struct event_base *base = user_data;
	struct timeval delay = { 2, 0 };//定义delay为2s

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

	event_base_loopexit(base, &delay);//2s后结束事件主循环
}

整体框架

对于这个TCP服务器,整体的逻辑如下:
• 新建一个event_base,然后在其上绑定一个listener来监听特定端口(9995);
• 新建一个处理信号的事件signal_event,并与上一步中的event_base绑定;
• 调用event_base_dispatch来监控两个事件,包括指定的TCP连接及SIGINT信号:
  当监听到一个连接时,libevent会触发listen_callback,即为上面的listener_cb函数,该函数首先基于底层的socket建立一个bufferevent,并设置为可写不可读,最后调用bufferevent_write函数向其中缓存区写。bufferevent_write函数写后会触发写回调函数conn_writecb,由于该例子中不需要其他操作,所以conn_writecb函数直接释放这个连接。
  当中断信号(Ctrl+C)出现时,libevent会触发signal_event的回调函数signal_cb,该函数会在2秒后停止event_base的监听,并退回到主函数;
• 主函数依次释放listener,signal_event和event_base的资源,结束。

相关说明:

这里只对相关函数进行简单的功能描述,详细的说明:Libevent学习记录(3)

event_base

当前线程中所有事件的一个管理者;位于event-internal.h中。

  一个event_base就是一个Reactor框架。我们在调用任何Libevent的函数前,我们都是需要先申请 event_base 结构体。对于一个event_base结构来说,它会保存一系列的event事件并且以轮训的方式去关注每一个事件,去查看哪一个事件是就绪的。

  event_base 相当于是一个底座,只要向底座上插入事件,然后不断的监控事件,等待事件发生调用回调函数即可

evconnlistener

  连接监听器,基于event和event_base已经可以写一个CS模型了。但是对于服务器端来说,仍然需要用户自行调用socket、bind、listen、accept等步骤。这个过程有点繁琐,为此在2.0.2-alpha版本的Libevent推出了一些对应的封装函数。
  用户只需初始化struct sockaddr_in结构体变量,然后把它作为参数传给函数evconnlistener_new_bind即可。该函数会完成上面说到的那4个过程。即对socket、bind、listen、accept的一个封装。
  当服务器端监听到一个客户端的连接请求后,就会调用listener_cb这个回调函数。这个回调函数是在evconnlistener_new_bind函数中设置的。要注意的是:当这个回调函数被调用时,Libevent已经帮我们accept了这个客户端。所以,该回调函数有一个参数是文件描述符fd。我们直接使用这个fd即可。真是方便。

bufferevent

  通常已连接的套接字除了相应事件之外,应用还希望做一定的数据缓冲。这种缓冲IO模式很通用,libevent为此提供了一种通用机制即bufferevent。
  bufferevent由一个底层的传输端口(如已连接套接字)、一个读取缓冲区和一个写入缓冲区组成。
  与通常的事件在底层传输端口已经就绪,可以读取或者写入的时候执行回调不同的是,bufferevent在读取或者写入了足够量的数据之后再调用用户提供的回调。
  用户通过bufferevent不必处理系统底层IO操作,仅仅从bufferevent中读取相应数据即可。
  evconnlistener是为了简化服务器从创建套接字到accept成功这个繁琐的过程。这里讲述的bufferevent则是为了简化在accpet成功返回已连接套接字之后,从已连接套接字接收数据和发送数据(这也需要通过epoll处理)需要自建立缓冲区的过程,bufferevent通过链表建立自己的缓冲区,使得用户不必为缓冲数据的处理而烦恼,用户仅仅需要在回调函数里面通过bufferevent_read或者bufferevent_write读出或写入数据即可。

evsignal

ev_signal是libevent提供的对信号处理的一个模块,基本上是对sigaction函数的一个封装,并将本身是异步的信号转化为同步。信号本质上来说,是libevent事件中的子集,因此是兼容event的api的,不过为了清晰,内部还是进行了简单的封装。其实就是一种特殊的event。

hello-world客户端例程

hello-world-client.c:

#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void eventcb(struct bufferevent *bev, short events, void *ptr)
{
        if (events & BEV_EVENT_CONNECTED) {

        } else if (events & BEV_EVENT_ERROR) {
                /* An error occured while connecting. */
        }
}

static void
conn_readcb(struct bufferevent *bev, void *user_data)
{

        struct evbuffer *input = bufferevent_get_input(bev);
        size_t len = evbuffer_get_length(input);
        char msg[1024];
        memset(msg,0,1024);
        bufferevent_read(bev, msg, len);
        msg[len] = '\0';

        printf("recv %s from server\n", msg);
}
int main(int argc,char **argv)
{
        if(argc!=3){
                printf("请输入正确的ip地址和端口号\n");
                exit(0);
        }

        struct event_base *base;
        struct bufferevent *bev;
        struct sockaddr_in sin;

        base = event_base_new();

        memset(&sin, 0, sizeof(sin));
        sin.sin_family = AF_INET;
        inet_aton(argv[1],&sin.sin_addr); /* 127.0.0.1 */
 		sin.sin_port = htons(atoi(argv[2])); /* Port 9995 */

        bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);

        bufferevent_setcb(bev, conn_readcb, NULL, eventcb, NULL);

        if (bufferevent_socket_connect(bev,
                                (struct sockaddr *)&sin, sizeof(sin)) < 0) {

                /* Error starting connection */
                bufferevent_free(bev);
                return -1;
        }
        bufferevent_disable(bev, EV_WRITE);
        bufferevent_enable(bev, EV_READ);

        event_base_dispatch(base);
        bufferevent_free(bev);
        printf("finished\n");
        return 0;
}

编译运行:
服务器:

gcc hello-world.c -ohelloServer -levent

./helloServer

在这里插入图片描述

客户端:

gcc hello-world-client.c -ohello-world-client -levent

./hello-world-client 127.0.0.1 9995

在这里插入图片描述
下一节:Libevent学习记录(3)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Libevent是一个事件驱动的网络编程框架,而event.h是其核心头文件之一。该头文件定义了事件处理相关的结构体、函数和宏等内容。 下面是event.h中常用的一些定义和函数: ### 1.事件回调函数 ```c typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg); ``` 该类型定义了事件回调函数的原型,其中fd是事件所在的文件描述符,events是事件类型,arg是用户传入的参数。 ### 2.事件结构体 ```c struct event { event_callback_fn ev_callback; // 事件回调函数 int ev_fd; // 事件所在的文件描述符 short ev_events; // 事件类型 short ev_res; // 事件结果 struct event_base *ev_base; // 事件所属的event_base void *ev_arg; // 用户传入的参数 }; ``` 该结构体表示一个事件,其中ev_callback是事件回调函数,ev_fd是事件所在的文件描述符,ev_events是事件类型,ev_res是事件结果,ev_base是事件所属的event_base,ev_arg是用户传入的参数。 ### 3.事件类型 ```c #define EV_TIMEOUT 0x01 #define EV_READ 0x02 #define EV_WRITE 0x04 #define EV_SIGNAL 0x08 #define EV_PERSIST 0x10 #define EV_ET 0x20 ``` 该宏定义了事件类型,分别为超时事件、读事件、写事件、信号事件、持续事件和边缘触发事件。 ### 4.事件处理函数 ```c struct event_base *event_base_new(void); int event_base_dispatch(struct event_base *base); int event_base_loopexit(struct event_base *base, const struct timeval *tv); void event_base_free(struct event_base *base); ``` 这些函数用于创建event_base、处理事件、退出事件循环和释放event_base等操作。 以上是event.h中的一些常用内容,更多细节可以查看源码和官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值