由于最近遇到一个关于局域网通信的项目,主要是4个client端传送数据至1个server端,每个client端的PC均由网线连接一个网络转串口服务器,每个服务器分配1个ip,每个ip最多虚拟8个串口端,这些串口端获取的数据将作为数据源被处理,最终通过网络传输至server端。于是乎,我对socket进行封装处理,所有关于socket的初始化,组包和收发包均通过这里进行,thread也是单独封装了,在server端一个进程共由5个线程组成,主线程作ui相关处理,其他4个线程分别对不同的client端进行通信处理。虽然,整个小小的架构做了两三次优化调整,但是总觉得哪里有所欠缺。于是,好奇心害死人,libevent登场了。
我对libevent也只是刚刚接触,很多东西正在慢慢研究整理中,有些自我认知可能是有问题的,如有发现,还望指正。这几天从libevent里sample中最简单的定时器事件开始接触。
#include <sys/types.h>
#include <event2/event-config.h>
#include <sys/stat.h>
#ifndef _WIN32
#include <sys/queue.h>
#include <unistd.h>
#endif
#include <time.h>
#ifdef EVENT__HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <event2/event.h>
#include <event2/event_struct.h>
#include <event2/util.h>
#ifdef _WIN32
#include <winsock2.h>
#endif
struct timeval tm;
int main()
{
struct event_base *base = NULL;
struct event timeout;
#ifdef _WIN32
WORD wVersion = MAKEWORD(2, 2);
WSADATA wsaData;
WSAStartup(wVersion, &wsaData);
#endif
return 0;
}
libevent关于网络,总归要WSAStartup进行初始化一下。每个libevent下的base似乎如同一个事件集或者事件根,新建出来的事件都得添加、绑定至base内,这里我先从定时器事件开始测试的。接下来:
// libevent version
const char *version = event_get_version();
// creat base
base = event_base_new();
if(!base)
{
printf("event_base_new occurs an error...\n");
return -1;
}
short flags = EV_TIMEOUT | EV_PERSISIT;
int result = event_assign(&timeout, base, -1, flags, timeout_cb, (void*)timeout);
if(result < 0)
{
// error msg and how to deal
return -1;
}
从event_assign(struct event* ,struct event_base*, evutil_socket_t, short, event_callback_fn, void *)函数声明,基本可以看出在base下分配event(当前是time event),通过event_callback_fn回调函数处理所要做的事情,回调函数是要进行传参的。这里的flags确定事件的类型,基本由如下几种组成:
#define EV_TIMEOUT 0x01
#define EV_READ 0x02
#define EV_WRITE 0x04
#define EV_SIGNAL 0x08
#define EV_PERSIST 0x10
#define EV_ET 0x20
#define EV_FINALIZE 0x40
再看这里的回调函数
void timeout_cb(evutil_socket_t fd, short ev_type, void *arg)
{
struct timeval new_tm, diff_tm;
evutil_gettimeofday(&new_tm, NULL);
// get delt time(second)
evutil_timersub(&new_tm, &tm, &diff_tm);
double t = diff_tm.tv_sec + diff_tm.tv_usec/1.0e6;
printf("timeout_cb occurs : hello, libevent...\n", t);
tm = new_tm;
}
这样,时间回调函数写好了,经过测试似乎event_assign(struct event* ,struct event_base*, evutil_socket_t, short, event_callback_fn, void *)函数中的evutil_socket_t和void*都传递给回调函数中对应的evutil_socket_t和void*,既然是定时事件,
那么ev_type自然是EV_TIMEOUT类型。定时回调函数既然完成,为定时器添加定时时间,接下来:
tv.tv_sec = 5;
tv.tv_usec = 0;
event_add(&timeout, &tv);
evutil_gettimeofday(&tm, NULL);
event_base_dispatch(base);
//event_base_loop(base, ...);
开始监听定时事件,这里的event_base_dispatch(struct event_base *base)是阻塞模式。event_base_loop(struct event_base *base, int flags)相对更为灵活,flags有如下几种类型:
#define EVLOOP_ONCE 0x01
#define EVLOOP_NONBLOCK 0x02
#define EVLOOP_NO_EXIT_ON_EMPTY 0x04
EVLOOP_ONCE: 被监听的一个或多个定时事件,任意一个事件处于激活状态被执行之后,立即跳出对base下的所有event循环监听状态;
EVLOOP_NONBLOCK:仅仅监听事件是否激活,非阻塞状态,所以若当前没有事件被激活,直接结束。这点可以通过修改tv.tv_sec = 0使定时器事件立即被激活、触发即可验证;
EVLOOP_NO_EXIT_ON_EMPTY:等同于event_base_dispatch(event_base*),循环监听事件。
如果,想结束循环状态,可以通过如下两个函数:
// exit by base
event_base_loopbreak(struct event_base*);
// exit by base and time
event_base_loopexit(struct event_base*, timeval*);
最后,通过event_base_free(struct event_base*)释放base所占用的资源。