C-libev学习笔记-事件库源码阅读4-API-ev_supported_backends()等,ev_time(),ev_sleep()

上篇文章讲述的两个函数放到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是秒。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙子砰砰枪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值