上篇文章讲述的两个函数放到linux跑一下试试:
int version_major=-1;
int version_minor=-1;
version_major = ev_version_major();
version_minor = ev_version_minor();
printf("version_major: %d\n",version_major);
printf("version_minor: %d\n",version_minor);
笔者的环境是CentOS,IDE是Qt Creator,要想跑起来就要在pro链接文件:
LIBS += -lev
后端函数
EV_API_DECL unsigned int ev_supported_backends (void) EV_THROW;//后端
EV_API_DECL unsigned int ev_recommended_backends (void) EV_THROW;
EV_API_DECL unsigned int ev_embeddable_backends (void) EV_THROW;
有了上次的分析经验这次直接切入要害:
ecb_cold
unsigned int
ev_supported_backends (void) EV_THROW
{
unsigned int flags = 0;
if (EV_USE_PORT ) flags |= EVBACKEND_PORT;
if (EV_USE_KQUEUE) flags |= EVBACKEND_KQUEUE;
if (EV_USE_EPOLL ) flags |= EVBACKEND_EPOLL;
if (EV_USE_POLL ) flags |= EVBACKEND_POLL;
if (EV_USE_SELECT) flags |= EVBACKEND_SELECT;
return flags;
}
ecb_cold代表它是冷门函数,编译优化不优先考虑。
最后return flags,但‘ |=’ 是什么意思呢?
a|=b等价于a=a|b;
flags值为0,当b不为0时,值为b,否则值为0.
跑一下看看结果:
unsigned int supported = 0;
unsigned int recommended = 0;
unsigned int enbeddable = 0;
supported = ev_supported_backends();//支持
recommended = ev_recommended_backends();//推荐
enbeddable = ev_embeddable_backends();//可嵌入
printf("supported:%d\nrecommended:%d\nenbeddable:%d\n",supported,recommended,enbeddable);
具体的分析自己深入,这种函数不常用,不再浪费时间解析。
ev_time()
原型:
EV_API_DECL ev_tstamp ev_time (void) EV_THROW;
定义在 ev_win32.c 文件中:
ev_tstamp
ev_time (void)
{
FILETIME ft;
ULARGE_INTEGER ui;
GetSystemTimeAsFileTime (&ft);
ui.u.LowPart = ft.dwLowDateTime;
ui.u.HighPart = ft.dwHighDateTime;
/* msvc cannot convert ulonglong to double... yes, it is that sucky */
return (LONGLONG)(ui.QuadPart - 116444736000000000) * 1e-7;
}
我们前几篇说过,libev里把 double 声明为 ev_tstamp
在定义里面可以看出来 ft 和 ui 应该是两个结构体,并且ui 的成员变量 u 也是一个结构体。
typedef struct _FILETIME {
DWORD dwLowDateTime;
DWORD dwHighDateTime;
} FILETIME, *PFILETIME, *LPFILETIME;
它表示的是文件系统时间戳,该结构体位于minwindef.h文件中,它是系统文件。
#if defined(MIDL_PASS)
typedef struct _ULARGE_INTEGER {
ULONGLONG QuadPart;
} ULARGE_INTEGER;
#else // MIDL_PASS
#if defined(_MAC) && defined(_MAC_INT_64)
typedef __int64 LONGLONG;
typedef unsigned __int64 ULONGLONG;
#define MAXLONGLONG (0x7fffffffffffffff)
#else
typedef double LONGLONG;
typedef double ULONGLONG;
#endif //_MAC and int64
这里定义的是64位无符号整型 或 double
但其实分析到这个函数:GetSystemTimeAsFileTime(),就没有必要再分析了,笔者没猜错的话,这个函数返回的是一个历史时间差。
int num = 10;
while(num){
printf("%f\n",ev_time());
num--;
}
ev_sleep()
声明:
EV_API_DECL void ev_sleep (ev_tstamp delay) EV_THROW; /* sleep for a while */
注意 delay 是 double 类型。
定义:
void
ev_sleep (ev_tstamp delay) EV_THROW
{
if (delay > 0.)
{
#if EV_USE_NANOSLEEP
struct timespec ts;
EV_TS_SET (ts, delay);
nanosleep (&ts, 0);
#elif defined _WIN32
Sleep ((unsigned long)(delay * 1e3));
#else
struct timeval tv;
/* here we rely on sys/time.h + sys/types.h + unistd.h providing select */
/* something not guaranteed by newer posix versions, but guaranteed */
/* by older ones */
EV_TV_SET (tv, delay);
select (0, 0, 0, 0, &tv);
#endif
}
}
EV_USE_NANOSLEEP:
# if HAVE_NANOSLEEP
# ifndef EV_USE_NANOSLEEP
# define EV_USE_NANOSLEEP EV_FEATURE_OS
# endif
# else
# undef EV_USE_NANOSLEEP
# define EV_USE_NANOSLEEP 0
# endif
#define EV_FEATURE_CODE ((EV_FEATURES) & 1)
#define EV_FEATURE_DATA ((EV_FEATURES) & 2)
#define EV_FEATURE_CONFIG ((EV_FEATURES) & 4)
#define EV_FEATURE_API ((EV_FEATURES) & 8)
#define EV_FEATURE_WATCHERS ((EV_FEATURES) & 16)
#define EV_FEATURE_BACKENDS ((EV_FEATURES) & 32)
#define EV_FEATURE_OS ((EV_FEATURES) & 64)
#ifndef EV_FEATURES
# if defined __OPTIMIZE_SIZE__
# define EV_FEATURES 0x7c
# else
# define EV_FEATURES 0x7f
# endif
#endif
可见这个宏使得这个函数适应不同的版本。
满足EV_USE_NANOSLEEP:则创建timespec结构体。
timespec位于系统库 time.h 文件中。
struct timespec
{
time_t tv_sec; // Seconds - >= 0
long tv_nsec; // Nanoseconds - [0, 999999999]
};
涉及到的两个宏:EV_TV_SET(tv, t) 和 EV_TS_SET(ts, t)
#define EV_TV_SET(tv,t) do { tv.tv_sec = (long)t; tv.tv_usec = (long)((t - tv.tv_sec) * 1e6); } while (0)
#define EV_TS_SET(ts,t) do { ts.tv_sec = (long)t; ts.tv_nsec = (long)((t - ts.tv_sec) * 1e9); } while (0)
其中 le6 和 le9 分别是 double类型的 10^6。 用来代表机器时间和秒之间的换算关系是这么多倍。
至于nanosleep() 和 Sleep() 和 select() 都是i系统函数,其中nanosleep() 和 Sleep() 都可以使得当前的线程阻塞特定的事件。
select() 函数稍有特别,它常用于socket编程中,而且它常常是用来完成非阻塞编程的,这涉及到IO多路复用,poll,epoll,select,是经常提到的三个方法,他们可以完成 c10k,poll和select类似,select使用顺序结构存储文件描述符,有大小限制,也不利于查找和复制,poll使用动态数组,没有了文件描述符的数量限制,但是基于服务器的实现,仍然不能无限增大,epoll则使用了高级数据结构红黑树来存储,并且用链表保存有响应事件的文件描述符,即节约了与内核对换的时间空间资源,又节省了搜索时间。
libev也是用于完成IO多路复用的一个工具,它相比上述三个,即将它们作为底层支持,又进行改进,libev的作者的怨念很深,我们不仅一次从注释中看到。
ok那select()是怎么用来‘睡眠’线程或进程的呢?
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout);
这是select()函数的原型,我们可以理解一下select本身的工作原理。
当有多个客户端处于连接状态时,如果想要获取网络事件,就要先获取产生了事件的这个连接(文件描述符-socket),怎么查找呢?linux的做法是把所有连接的文件描述的集合传到内核里,再由内核传回有事件的文件描述符,然后再在用户态里处理这些有事件的请求。
这里功能对应的这些参数:
int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。
fd_set * readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
fd_set * writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
fd_set * errorfds同上面两个参数的意图,用来监视文件错误异常。
struct timeval * timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
以上参数解释来自https://blog.csdn.net/piaojun_pj/article/details/5991968
那么实现阻塞的原理就是他将前四个参数置为空(0,null),保留最后一个参数,那么这个函数将空指针传入内核态后,内核态将阻塞,一直到时间截止,完成睡眠进程的假象,这显然是一个编程老油条的用法。
跑一下:
num = 100000;
int a = 0;
clock_t clock_start = clock();
time_t time_start = time(NULL);
while(num){num--;a++;};
time_t time_end = time(NULL);
clock_t clock_end = clock();
printf("clock:%d\n",(clock_end-clock_start));
printf("time:%d\n",time_end-time_start);
num = 100000;
clock_start = clock();
time_start = time(NULL);
while(num){num--;a++;};
ev_sleep(10);
time_end = time(NULL);
clock_end = clock();
printf("clock:%d\n",(clock_end-clock_start));
printf("time:%d\n",time_end-time_start);
clock是滴答钟,time是秒。