多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
C程序在进行真正的编译之前都要进行预编译。 我们看看fdevent系统中的一些宏: ~~~ #if defined(HAVE_EPOLL_CTL) && defined(HAVE_SYS_EPOLL_H) # if defined HAVE_STDINT_H # include <stdint.h> # endif # define USE_LINUX_EPOLL # include <sys/epoll.h> #endif #if defined HAVE_POLL && (defined(HAVE_SYS_POLL_H) || defined(HAVE_POLL_H)) # define USE_POLL # ifdef HAVE_POLL_H # include <poll.h> # else # include <sys/poll.h> # endif # if defined HAVE_SIGTIMEDWAIT && defined(__linux__) # define USE_LINUX_SIGIO # include <signal.h> # endif #endif //…… ~~~ 上面的宏判断系统中是否有对应的多路IO系统,如果有,就定义对应的USE_XXX宏。 预编译完这些宏以后,对于当前系统中有的多路IO系统,就会有对应的USE_XXX符号被定义。预编译器接着运行,将那些不需要的代码都忽略。 fdevent.h中对所有可能的多路IO系统都定义了初始化函数: ~~~ int fdevent_select_init(fdevents * ev); int fdevent_poll_init(fdevents * ev); int fdevent_linux_rtsig_init(fdevents * ev); int fdevent_linux_sysepoll_init(fdevents * ev); int fdevent_solaris_devpoll_init(fdevents * ev); int fdevent_freebsd_kqueue_init(fdevents * ev); ~~~ 因此,对于系统中没有的多路IO系统对应的初始化函数,预编译结束后,这些初始化函数被定义为报错函数。如epoll对应的为: ~~~ #ifdef USE_LINUX_EPOLL /* 当定义了epoll时,epoll的函数实现代码 */ #else /* 当未定义epoll时,epoll只实现init函数(这是必须的,因为该函数在fdevent.h中定义了),并将它实现为报错函数 */ int fdevent_linux_sysepoll_init(fdevents *ev) { UNUSED(ev); fprintf(stderr, "%s.%d: linux-sysepoll not supported, try to set server.event-handler = \"poll\" or \"select\"\n", __FILE__, __LINE__); return -1; } #endif ~~~ 预编译后,开始真正的编译。我们假设系统中只有epoll。 首先,我们看一看配置中有关fdevent的设置。进入configfile.c文件中的config_set_defaults()函数。函数的一开始就有这么一个定义: ~~~ struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] = { /* * - poll is most reliable - select works everywhere - * linux-* are experimental */ #ifdef USE_POLL {FDEVENT_HANDLER_POLL, "poll"}, #endif #ifdef USE_SELECT {FDEVENT_HANDLER_SELECT, "select"}, #endif #ifdef USE_LINUX_EPOLL {FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll"}, #endif #ifdef USE_LINUX_SIGIO {FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig"}, #endif #ifdef USE_SOLARIS_DEVPOLL {FDEVENT_HANDLER_SOLARIS_DEVPOLL, "solaris-devpoll"}, #endif #ifdef USE_FREEBSD_KQUEUE {FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue"}, {FDEVENT_HANDLER_FREEBSD_KQUEUE, "kqueue"}, #endif {FDEVENT_HANDLER_UNSET, NULL} }; ~~~ 上面定义了一个struct ev_map类型的数组。数组的内容是当前系统中存在的多路IO系统的类型和名称。这里排序很有意思,从注释中可以看出,poll排在最前因为最可靠,select其次因为支持最广泛,epoll第三因为是最好的。 在这里,如果配置文件有配置,那么按配置文件来,如果没配置,则取上面数组中的第一个。以下是配置文件的格式: ~~~ ## set the event-handler (read the performance ##section in the manual) server.event-handler = "freebsd-kqueue" # needed on OS X ~~~ 接下来我们看server.c中的main函数。 前面有一些是设置fd数量的,其中select比较特殊需要特别处理,fd数量一个是系统的限制,一个是用户配置的限制。 当程序产生子进程后,在子进程中执行的第一条语句就是初始化fdevent系统: ~~~ if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1, srv->event_handler))) { log_error_write(srv, __FILE__, __LINE__, "s", "fdevent_init failed"); return -1; } ~~~ 进入fdevent_init()函数: ~~~ /** * 初始化文件描述符事件数组fdevent */ fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type) { fdevents *ev; //内存被初始化为0 ev = calloc(1, sizeof(*ev)); //分配数组 ev->fdarray = calloc(maxfds, sizeof(*ev->fdarray)); ev->maxfds = maxfds; //根据设定的多路IO的类型进行初始化。 switch (type) { case FDEVENT_HANDLER_POLL: if (0 != fdevent_poll_init(ev)) { fprintf(stderr, "%s.%d: event-handler poll failed\n", __FILE__, __LINE__); return NULL; } break; case FDEVENT_HANDLER_SELECT: if (0 != fdevent_select_init(ev)) { fprintf(stderr, "%s.%d: event-handler select failed\n", __FILE__, __LINE__); return NULL; } break; case FDEVENT_HANDLER_LINUX_RTSIG: if (0 != fdevent_linux_rtsig_init(ev)) { fprintf(stderr, "%s.%d: event-handler linux-rtsig failed, try to set server.event-handler = \"poll\" or \"select\"\n", __FILE__, __LINE__); return NULL; } break; case FDEVENT_HANDLER_LINUX_SYSEPOLL: if (0 != fdevent_linux_sysepoll_init(ev)) { fprintf(stderr, "%s.%d: event-handler linux-sysepoll failed, try to set server.event-handler = \"poll\" or \"select\"\n", __FILE__, __LINE__); return NULL; } break; case FDEVENT_HANDLER_SOLARIS_DEVPOLL: if (0 != fdevent_solaris_devpoll_init(ev)) { fprintf(stderr, "%s.%d: event-handler solaris-devpoll failed, try to set server.event-handler = \"poll\" or \"select\"\n", __FILE__, __LINE__); return NULL; } break; case FDEVENT_HANDLER_FREEBSD_KQUEUE: if (0 != fdevent_freebsd_kqueue_init(ev)) { fprintf(stderr, "%s.%d: event-handler freebsd-kqueue failed, try to set server.event-handler = \"poll\" or \"select\"\n", __FILE__, __LINE__); return NULL; } break; default: fprintf(stderr, "%s.%d: event-handler is unknown, try to set server.event-handler = \"poll\" or \"select\"\n", __FILE__, __LINE__); return NULL; } return ev; } ~~~ fdevent_init()函数根据fdevent_handler_t的值调用相应的初始化函数。我们进入fdevent_linux_sysepoll_init()函数: ~~~ int fdevent_linux_sysepoll_init(fdevents * ev) { ev->type = FDEVENT_HANDLER_LINUX_SYSEPOLL; #define SET(x) \ ev->x = fdevent_linux_sysepoll_##x; /* 通过SET宏对fdevents结构体中的函数指针赋值。然后创建epoll,最后做一些设置。*/ SET(free); SET(poll); SET(event_del); SET(event_add); SET(event_next_fdndx); SET(event_get_fd); SET(event_get_revent); //创建epoll if (-1 == (ev->epoll_fd = epoll_create(ev->maxfds))) { fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n", __FILE__, __LINE__, strerror(errno)); return -1; } //设置epoll_fd为运行exec()函数时关闭。 if (-1 == fcntl(ev->epoll_fd, F_SETFD, FD_CLOEXEC)) { fprintf(stderr, "%s.%d: epoll_create failed (%s), try to set server.event-handler = \"poll\" or \"select\"\n", __FILE__, __LINE__, strerror(errno)); close(ev->epoll_fd); return -1; } //创建fd事件数组。在epoll_wait函数中使用。 //存储发生了IO事件的fd和对应的IO事件。 ev->epoll_events = malloc(ev->maxfds * sizeof(*ev->epoll_events)); return 0; } ~~~ 至此fdevent的初始化工作全部完成。