• libevent学习——例子.md


    time-test例子

    第一个例子位于libevent源码libevent-2.1.12-stable/sample/time-test.c下面,是一个超时事件回调

    int
    main(int argc, char **argv)
    {
    	struct event timeout;
    	struct timeval tv;
    	struct event_base *base;
    	int flags;
    
    #ifdef _WIN32
    	WORD wVersionRequested;
    	WSADATA wsaData;
    
    	wVersionRequested = MAKEWORD(2, 2);
    
    	(void)WSAStartup(wVersionRequested, &wsaData);
    #endif
    
    	if (argc == 2 && !strcmp(argv[1], "-p")) {
    		event_is_persistent = 1;
    		flags = EV_PERSIST;
    	} else {
    		event_is_persistent = 0;
    		flags = 0;
    	}
    
    	/* Initialize the event library */
    	base = event_base_new();
    
    	/* Initialize one event */
    	event_assign(&timeout, base, -1, flags, timeout_cb, (void*) &timeout);
    
    	evutil_timerclear(&tv);
    	tv.tv_sec = 2;
    	event_add(&timeout, &tv);
    
    	evutil_gettimeofday(&lasttime, NULL);
    
    	setbuf(stdout, NULL);
    	setbuf(stderr, NULL);
    
    	event_base_dispatch(base);
    
    	return (0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    从main函数看起

    1. 创建了三个变量:代表超时事件的timeout、存储时间的tv和用于管理事件的event_base结构体指针变量(使用指针是因为event_base结构体是一个库内部实现的结构体,对外只提供操作接口,不暴露实现细节)。
    2. 根据参数设置事件的flag(是否持久)。
    3. event_base_new():通过接口分配一个event_base结构体,同时也代表着事件库的初始化。
    4. event_assign:初始化timeout事件,指定管理事件的event_base、事件flag、事件触发的回调以及回调参数。
    5. event_add:添加事件并设置事件的超时事件。
    6. event_base_dispatch:开始事件循环,等待回调。
    static void
    timeout_cb(evutil_socket_t fd, short event, void *arg)
    {
    	struct timeval newtime, difference;
    	struct event *timeout = arg;
    	double elapsed;
    
    	evutil_gettimeofday(&newtime, NULL);
    	evutil_timersub(&newtime, &lasttime, &difference);
    	elapsed = difference.tv_sec +
    	    (difference.tv_usec / 1.0e6);
    
    	printf("timeout_cb called at %d: %.3f seconds elapsed.\n",
    	    (int)newtime.tv_sec, elapsed);
    	lasttime = newtime;
    
    	if (! event_is_persistent) {
    		struct timeval tv;
    		evutil_timerclear(&tv);
    		tv.tv_sec = 2;
    		event_add(timeout, &tv);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    接着看回调,回调函数的类型都是固定的typedef void (*event_callback_fn)(evutil_socket_t, short, void *);

    1. 通过传给回调的参数arg获取到之前的event,因为在之前初始化timeout事件的时候将事件本身设置为了回调的参数。
    2. 计算事件触发时的时间并更新lasttime。
    3. 判断是否事件持久,如果事件持久,重新把事件添加到事件循环中。

    实验:

    ./time-test 
    timeout_cb called at 1702305186: 2.002 seconds elapsed.
    timeout_cb called at 1702305188: 2.002 seconds elapsed.
    timeout_cb called at 1702305190: 2.002 seconds elapsed.
    timeout_cb called at 1702305192: 2.002 seconds elapsed.
    
    • 1
    • 2
    • 3
    • 4
    • 5

    signal-test例子

    这个例子位于libevent-2.1.12-stable/sample/signal-test.c,是一个信号事件回调

    int
    main(int argc, char **argv)
    {
    	struct event *signal_int = NULL;
    	struct event_base* base;
    	int ret = 0;
    #ifdef _WIN32
    	WORD wVersionRequested;
    	WSADATA wsaData;
    
    	wVersionRequested = MAKEWORD(2, 2);
    
    	(void) WSAStartup(wVersionRequested, &wsaData);
    #endif
    
    	/* Initialize the event library */
    	base = event_base_new();
    	if (!base) {
    		ret = 1;
    		goto out;
    	}
    
    	/* Initialize one event */
    	signal_int = evsignal_new(base, SIGINT, signal_cb, event_self_cbarg());
    	if (!signal_int) {
    		ret = 2;
    		goto out;
    	}
    	event_add(signal_int, NULL);
    
    	event_base_dispatch(base);
    
    out:
    	if (signal_int)
    		event_free(signal_int);
    	if (base)
    		event_base_free(base);
    	return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    先看main函数

    1. 创建事件event和管理事件的event_base
    2. event_base_new:初始化事件库
    3. evsignal_new:初始化超时事件,虽然叫evsignal_new但本质是event_new创建事件
    4. event_add:添加信号事件
    5. event_base_dispatch:开启事件循环,等待信号事件回调
    6. 释放事件和事件管理器
    static void
    signal_cb(evutil_socket_t fd, short event, void *arg)
    {
    	struct event *signal = arg;
    
    	printf("signal_cb: got signal %d\n", event_get_signal(signal));
    
    	if (called >= 2)
    		event_del(signal);
    
    	called++;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接着看信号事件回调,很简单,就是打印信号并在第三次回调时删除事件

    实验:

     ./signal-test 
    ^Csignal_cb: got signal 2
    ^Csignal_cb: got signal 2
    ^Csignal_cb: got signal 2
    
    • 1
    • 2
    • 3
    • 4

    hello-world例子

    这个例子位于libevent-2.1.12-stable/sample/hello-world.c,是一个tcp服务器,每次收到新连接后进行回调

    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();
    	if (!base) {
    		fprintf(stderr, "Could not initialize libevent!\n");
    		return 1;
    	}
    
    	sin.sin_family = AF_INET;
    	sin.sin_port = htons(PORT);
    
    	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);
    
    	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(listener);
    	event_free(signal_event);
    	event_base_free(base);
    
    	printf("done\n");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    main函数:

    1. 创建管理事件的event_base、一个用于监听服务的evconnlistener、一个用于处理信号的事件event
    2. event_base_new:初始化事件库
    3. 创建并设置基本的监听socket
    4. 通过evconnlistener_new_bind将socket、event_base和监听回调绑定到一起
    5. evsignal_new:初始化超时事件,虽然叫evsignal_new但本质是event_new创建事件
    6. event_add:添加信号事件
    7. event_base_dispatch:开启事件循环,等待信号事件回调
    8. 销毁资源

    先看简单的信号事件回调,和前一个例子基本一样

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    再看新的监听事件回调,一旦有新连接到达就会调用该函数:

    static void
    listener_cb(struct evconnlistener *listener, evutil_scket_t fd,
        struct sockaddr *sa, int socklen, void *user_data)
    {
    	struct event_base *base = user_data;
    	struct bufferevent *bev;
    
    	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(bev, NULL, conn_writecb, conn_eventcb, NULL);
    	bufferevent_enable(bev, EV_WRITE);
    	bufferevent_disable(bev, EV_READ);
    
    	bufferevent_write(bev, MESSAGE, strlen(MESSAGE));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 获取event_base
    2. 创建一个bufferevent
    3. bufferevent_socket_new:创建基于套接字的bufferevent
      struct bufferevent *bufferevent_socket_new(
      			struct event_base *base,
      			evutil_socket_t fd,
      			enum bufferevent_options options);
      
      • 1
      • 2
      • 3
      • 4
      base 是event_base,options 是表示 bufferevent 选项(BEV_OPT_CLOSE_ON_FREE等) 的位掩码, fd是一个可选的表示套接字的文件描述符。如果想以后设置文件描述符,可以设置fd为-1。成功时函数返回一个bufferevent,失败则返回 NULL。
      这里使用的标志是BEV_OPT_CLOSE_ON_FREE: 释放 bufferevent时关闭底层传输端口。这将关闭底层套接字,释放底层bufferevent等。
    4. bufferevent_setcb:数修改 bufferevent 的一个或者多个回调。
      void bufferevent_setcb(struct bufferevent *bufev,
                           bufferevent_data_cb readcb, bufferevent_data_cb writecb,
                           bufferevent_event_cb eventcb, void *cbarg);
      
      • 1
      • 2
      • 3
      readcb、writecb和eventcb函数将分别在缓冲区已经读取足够的数据、已经写入足够的数据或者发生错误时被调用。
      这里设置了writecb即当缓冲区写入足够的数据时调用
    5. bufferevent_enable:启用bufferevent 上的EV_WRITE事件。
    6. bufferevent_disable:禁用bufferevent 上的EV_READ事件。
    7. bufferevent_write:向bufferevent的输出缓冲区添加数据

    接着看bufferevent上的写回调,因为默认写的低水位为0,因此只有当output buffer数据为空时才会触发写回调函数。

    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 获取到bufferevent输出缓冲区
    2. 判断输出缓存区全部写入
    3. 如果全部写入,释放bufferevent,因为设置了 BEV_OPT_CLOSE_ON_FREE 标志,并且 bufferevent 有一个套接字或者底层 bufferevent 作为其传输端口,则释放 bufferevent 将关闭这个传输端口。

    最后看bufferevent上的出错事件回调

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 检查错误事件,打印错误信息
    2. 释放bufferevent

    实验:

    # 服务端
    ./hello-world 
    new connection come
    flushed answer
    
    # 客户端
    nc 127.0.0.1 9995
    Hello, World!
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    总结
    该例子比较简单,流程就是启动一个服务器,每次收到一个连接,回复Hello, World!后断开连接。但是这个例子并没有向我们展示出conn_writecb和conn_eventcb这两个回调的用法,因为回复一句立马断开这个过程太快了。
    可以扩展一下:

    1. conn_writecb中如果不去主动释放bufferevent而是再次写入

    这种情况下,服务端会不停地向客户端输出Hello, World!字符串。如果这个时候关闭客户端,那么conn_eventcb这个错误回调也会被调用打印出具体信息。

    # 服务端
    ./hello-world 
    new connection come
    flushed answer
    ...
    flushed answer
    flushed answer
    Got an error on the connection: Connection reset by peer
    
    # 客户端
    nc 127.0.0.1 9995
    Hello, World!
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
  • 相关阅读:
    MybatisPlus(5)
    pinia安装使用
    MyBatis-Plus批量插入方法saveBatch
    分布式之计算高性能
    大数据学习笔记之Hadoop伪分布式集群变分布式
    软件工程第八周
    GoLong的学习之路(一)语法之变量与常量
    JAVA-编程基础-11-03-java IO 字节流
    python标识符
    MAC电脑运行windows程序或者游戏怎么办,crossover介绍,
  • 原文地址:https://blog.csdn.net/cclethe/article/details/133758823