1. FTK的主循环设计
    1. 批处理程序与GUI程序的不同
      1. 循环的流程
    2. FTK与windows的不同
      1. windows的主循环
        1. while(GetMessage(&msg)) { TranslateMessage(&msg); DispatchMessage(&msg); }
        2. FTK作者的看法
          1. 当前线程的消息是由另一线程传递到当前线程的,GUI需要引入多线程
          2. 如果把消息看做是一个对象,那WINDOWS里则把消息 单纯看做是一个数据,事件的响应放在wndProc处理函数中
        3. 看的不多,对以上持保留意见
      2. FTK的主循环
        1. ftk_run();
        2. FTK作者的看法
          1. 单线程处理多个事件源
          2. 增加新事件源时不会影响事件分发的框架(WINDOWS会影响?)
          3. FTK主循环方式
    3. main.c的写法
  2. Select机制实现(MTK有吗?)
  3. ftk_main_loop_run
    1. 有select方式实现
      1. 涉及到阻塞与非阻塞的编程
        1. 阻塞方式block,顾名思义, 就是进程或是线程执行到这些函数时必须等待某个事件的发生, 如果事件没有发生,进程或线程就被阻塞,函数不能立即返回
        2. 非阻塞方式non-block, 就是进程或线程执行此函数时不必非要等待事件的发生, 一旦执行肯定返回,以返回值的不同来反映函数的执行情况, 如果事件发生则与阻塞方式相同, 若事件没有发生则返回一个代码来告知事件未发生, 而进程或线程继续执行,所以效率较高
      2. fd_set
        1. int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
        2. struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数
        3. int maxfdp是一个整数值,是指集合中所有文件描述符的范围 ,即所有文件描述符的最大值加1,不能错! 在Windows中这个参数的值无所谓,可以设置不正确
        4. fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符, 我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了, 如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读, 如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间, select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化
        5. fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符, 我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了, 如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写, 如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间, select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化
        6. struct timeval* timeout是select的超时时间,这个参数至关重要, 它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构, 就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化 为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述 符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三, timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时 间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述
          1. struct timeval { __time_t tv_sec; /* Seconds. */ __suseconds_t tv_usec; /* Microseconds. */ };
        7. 函数返回值:负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件
        8. fd_set可理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄
        9. fd_set集合可以通过一些宏由人为来操作
        10. 初始化集合FD_ZERO(fd_set *)
        11. 文件描述符加入集合之中FD_SET(int ,fd_set *)
        12. 文件描述符从集合中删除FD_CLR(int ,fd_set*)
        13. 检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )
    2. 无select方式实现
    3. main_loop的执行过程
      1. 第一步 注册事件源的文件描述符fd到文件描符集中。 每一个整件源都会有自已的文件描述符,定时事件源文件描述符为-1(也就是一无效的文件描述符) static int ftk_source_timer_get_fd(FtkSource* thiz) { return -1; } 在mainloop里我们会首先将每一个source的fd(要求该文件描述符>= 0)进行注册到一个fd文件描述符集, if((fd = ftk_source_get_fd(source)) >= 0) { FD_SET(fd, &thiz->fdset); if(mfd < fd) mfd = fd; n++; } 第二步 设置select的阻塞时间。 select 的超时时间wait_time,初始值设为3000ms(也就是3s) 我们遍历所有的定时事件源的, 如果该事件源的定时时间小于wait_time,则设置为select 的wait_time为此定时事件源的定时时间,代码如下所示, source_wait_time = ftk_source_check(source); if(source_wait_time >= 0 && source_wait_time < wait_time) { wait_time = source_wait_time; } 第三步 调用select函数, 此时select函数是阻塞的,只有两种情况才会跳出select阻塞,执行后面的函数,1 当有fd文件描述符集中的某一个fd可读时,2 select time out超时。 ret = select(mfd + 1, &thiz->fdset, NULL, NULL, &tv); 第四步 接下来遍历每一个事件源,对每一个事件源(source)做如下处理 1 查询事件源的文件描述符fd可读,如果可读,那么调用相应的处理函数,同时直接返回到while循环的第一步. 2 ftk_source_check(source) 是否返回0, ftk_source_check(source)返回0, ,也就是说事件源为定时事件源,同时该定时时间已到,处理定时事件源的定时处理函数。 对应定时时间为1.5s的定时事件源, 我们设置select的阻塞时间为1.5s, 调用select函数后, 有两种情况会发生 1 假设在1秒时,有一个事件源的fd可读, 此时直接调用该事件源的处理函数,并且进入下一个while循环,在下一次循环里,对于定时时间这1.5s的定时事件源,它的定时时间会更新为0.5s. 2 select超时, 1.5内无任何事件源的fd可读,此时对每一个事件源来说, 首先会校验ftk_source_check(source) 是否返回0,返回0,调用说明该事件源为定时事件源,同时定时时间已到,应该调用事件源的超时处理函数。