libevent 网络编程需要初始化或者屏蔽的操作
#include
#ifndef _WIN32
#include
#endif
using namespace std;
int main()
{
#ifdef _WIN32//64 32
WSADATA wsa;//init win socket
WSAStartup(MAKEWORD(2,2), &wsa);
#else
//ignore pipe signal, because send data to closed socket and dump
if(signal(SIGPIPE, SIG_IGN)==SIG_ERR){
return 0;
}
#endif
...
#ifdef _WIN32
getchar();
WSAClentup();
#endif
windows iocp只支持windows //epoll的加强版,内部有线程池,epoll的话线程池需要自己实现
select
linux支持 devepoll 。。。
select 1. 有连接数限制FD_SIZE,且遍历所有连接,询问内核的方式存在用户态和内核态间的来回切换和内存copy,当连接数很大,活跃数比较小的时候会有性能问题。
2. linux 、windows都支持
3. 接口简单
poll 类似select,但是没有像select一样有连接数限制FD_SIZE
epoll 1. 遍历连接状态,用户空间到内核空间在开始的时候只copy一次,通过共享内存方式实现,内核存储用红黑数
2. 只返回可读的连接数,减少了遍历的数量
3. 解决了最大监听数的限制
4. 支持边缘触发edge-triggered和水平触发level-triggered(后面加的)
备注:应该说各个模型都有各自的特点和优势,关键在于根据实际场景选择,select跨平台性好,代码简单,连接数少的时候更好用。epoll适合高并发场景。
1. base 创建event上下文
event_base_new //替换了老版本里面的event_init 老版线程不安全,新函数线程安全
内部实现 event_config_new 和 event_base_new_with_config
2 环境配置和初始化
event_config_new
2.1. event_config_set_flag
EVENT_BASE_FLAG_NOLOCK(在创建base的时候不加锁)、_IGNORE_ENV (忽略环境变量,不受环境变量而影响)、
_START_IOCP(win设置cpu数量启用任何必需iocp分发逻辑,设置cpu数量event_config_set_num_.. evthread_use_windows_thread)
用iocp需要设置使用windows线程(会创建线程池)
evthread_use_windows_thread();
设置cpu数量
SYSTEM_INFO si;
GetSystemInfo(&si);//windows获取cpu数量
event_config_set_num_cpus_hint(config, si.dwNumberOfProcessors);
_NO_CACHE_TIME、_EPOLL_USE_CHANGELIST(epoll下多次触发调用一次,fd复制有bug不能用)
_PRECISE_TIMER(win \linux 都是非实时系统)
2.2. event_config_require_features(config, EV_....) 特征的设置, event_method_feature(ET(LT) 、FEATURE_01 是否要求添加删除事件或激活的时间复杂度位O1、
FDS支持任意套接字(网络和文件都ok,win不行,和epoll互斥)、EARLY_CLOSE(检测连接关闭支持,不一定能支持))
获取已经设置的默认特征 event_base_get_features(base) 返回枚举值的位运算结果
2.3. event_config_avoid_methed(conf , "epoll") //选择使用的网络模型,把前面的avoid掉直到自己要的
2.3.1. 获取当前的网络模型:event_base_get_method
2.3.2 获取支持哪些模型,通过event_get_supported_methods方法获取模型
3. event_base_new_with_config(config)
4. 销毁环境配置
event_config_free(config)
5. base的销毁(上下文的销毁)
event_base_free(base)
6. event_assign //替换老版本的event_set 、event_base_set //但是debug模式下存在内存泄漏问题,推荐使用event_new函数
7.event_new函数 (事件IO)
1. event_new 创建event -------- innitialized(nopending)
(base 上下文, fd 描述符, which 事件标志 EV_TIMEOUT| EV_READ| EV_WRITE| EV_PERSIST, ev_cb事件回调函数, arg回调函数的参数)
EV_PERSIST 持久化,不用触发后再调用add,大部分都设置持久
2. event_add(ev, 超时时间可以传0) ---------------------pending, 设置超时可以当定时器用?
even 的base初始化的时候有是否加锁,在event_add中就会判断,多线程调用event_add的时候base初始化要打开锁
3. 触发 ----------------active
4. event_del -------------disable事件,nopending状态
5. event_free(ev) 才是真的销毁
信号
event *csig = evsignal_new(base,SIGINT, cb_func, base) //event_new(base, SIGINT, EV_SIGNAL|EV_PERSIST, cb_func, arg)
//event_self_cbarg() 就是event自己,在event_new时可以当arg传给事件回调函数
//event_pengding( ev, NULL) 是否处于待决状态
if ( 0 != event_add(csig, 0) ){//error
}
event_base_dispatch(base); //into event loop
event_free(csig)
event_base_free(base);
定时器
//ev = event_new(base, -1, EV_PERSIST, timer2, 0); //持久的
ev = evtimer_new(base, timer1, event_self_cbarg()); //默认非持久的,可以用pending判断后del再add变持久化效果
evtimer_del(ev);
evtimer_add(ev, &t1); //event_add (超时统计,含了定时开始后业务处理消耗的时间)
//add time_out for client can nomsg to here //event_add(ev, 0); timeval t = {10, 0}; event_add(ev, &t);
evtimer_pending(ev, &t1)
t1 struct timeval { tv_sec; tv_usec}
定时器事件存储,默认是二叉堆(binary heap)的数据结构 O(log n)
大量相同超时值的定时任务时 用event_base_init_common_timeout返回值,即使用双向队列存储, 不同超时值的情况,用这个时间复杂度O(n)
static timeval tv_in = {3, 0};
const timeval* t3 = event_base_init_common_timeout(base, &tv_in);
event_add(ev3, t3);
否则用默认的二叉堆存储方式性能更高 O(log n)
文件监听事件
event_config* config = event_config_new();
event_config_require_features(config, EV_FEATURE_FDS); //支持文件描述符可监听?
event_base * base = event_base_new_with_config(config);
event_config_free(config);;
int fd = open(filepath+filename, O_RDONLY|O_NONBLOCK, 0); //非阻塞只读打开文件
lseek(fd, 0, SEEK_END);//移动到文件尾
event * fev = event_new(base, fd, FV_PERSIST|FV_READ, read_cb, event_self_cbarg()); //initial it , tip: read_cb可能一直进入,需要通过判断是否读到内容,再进行后续处理,网上看到有libevent+ inotify的结合使用。
event_add(fev, 0);//pending it
event_base_dispach(base);//loop dispatch it
event_free(fev);
event_base_free(base);
8. 缓冲IO befferEvent (对多线程的支持不是很好,如果不在线程中使用,需要添加声明开启多线程支持, event需要支持BEV_OPT_THREADSAFE)
https://www.cnblogs.com/adinosaur/p/7113631.html
eg:
int fd = socket(AF_INET, SOCK_STREAM, 0);
#ifdef WIN32
evthread_use_windows_threads();//win上设置
#else
evthread_use_pthreads();//unix上设置
#endif
struct event_base *base = event_base_new();
//文件描叙符可以设置为-1,只要稍后用bufferevent_setfd或者bufferevent_socket_connect()来设置它
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_THREADSAFE);
bufferevent_socket_connect(bev, (struct sockaddr*)&sin, sizeof(sin));
bufferevent_setcb(bev, event_on_read, event_on_write, event_on_connect, base);//回调函数不允许阻塞影响loop,所以接收完数据后启动子线程去处理业务
bufferevent_enable(bev, EV_READ | EV_WRITE);
stClient.bev = bev; /我这里设置多线程支持是因为我要在其他线程用这个bev去发送数据//bufferevent_write(stClient.bev, datapackage, packlen);
event_base_dispatch(base);
eg:server
evconnlistener_new_bind
连接进入,调用socket_listenercb //存储参数fd,后续进行数据回发
event_base *base = evconnlistener_get_base(listener);
int32_t enable = 1;
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const char*) &enable, sizeof(enable));
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (const char*) &enable, sizeof(enable));
evutil_make_socket_nonblocking(fd);//把连接描述符置成非阻塞模式,边沿触发while读取的时候(recv)才不会阻塞
bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, socket_readcb, NULL, socket_eventcb, user_data);
bufferevent_enable(bev, EV_READ | EV_WRITE | EV_PERSIST);
9. 循环(loop)事件的分发处理
event_base_dispatch //替换老版本的event_dispatch
10. event_reinit 上下文的reinit
fork进程后有些资源的copy是无效的,使用event_reinit可以让copy是生效的,保证fork之后工作正常且释放event_base内部分配的空间及本身对象的空间,而不释放事件、socket和回调函数中申请的空间。但是其实可以在fork之后再去创建event,就不会有这种情况,不会需要用到event_reinit
1. close_socket 、closee -> evutil_closesocket()
2. evutil_make_socket_nonblocking //置文件描述符为不阻塞,此时recv时读不到会返回0
3. evutil_make_listen_socket_reuseable //SO_REUSEADDR...
//event2/listen.h
event* ev= evconnlistener_new_bind //create sock 、bind and listen绑定事件的封装
base //libevent的上下文
listen_cb //listen接到连接的回调函数指针
arg //listen 回调函数获取的参数,可以传base
flag //LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE 地址重用及关闭连接时做相应的关闭释放
num //缓冲队列最多连接个数(排队,不应太大,否则处理不过来容易造成阻塞)
struct socketaddr_in* //绑定ip地址和端口 ipv4 ipv6
len //sizeof(sockaddr)
event_base_dispatch(base)
evconnlistener_free(ev) //事件的销毁
event_base_free(上下文的销毁)