我们已经在几个例子中看到,我们可以使用线程来允许服务器同时为多个客户端服务。
使用线程的另一种选择是使用多路I/O来构建一个服务器,该服务器能够仅使用单个线程为多个客户机提供服务。这种编程风格被称为事件驱动编程。在事件驱动的赢博体育程序中,赢博体育程序具有一个称为事件循环的主循环。在循环的每次迭代中,赢博体育程序检查是否发生了需要处理的事件。如果有新的事件可用,赢博体育程序将处理它,然后返回等待新的事件到达。如果当前没有事件可用,则主循环将阻塞,直到事件到达。
在教材的第二章中,作者演示了如何使用select()系统调用来监视一组文件描述符,并在任何文件描述符具有可读取的数据时自动通知。使用这种方法,我们可以设计一个服务器,通过为每个客户端使用套接字来同时服务多个客户端,然后在其中一个客户端有数据准备处理时使用select()来通知。
下面是一个很好的在线示例,演示了如何使用select()创建一个可以同时处理多个客户机的单线程服务器。
使用select()的更现代的替代方案。
#include <sys/ epoll_h > int epoll_create(int size);
size参数必须大于0,并且将被忽略。Epoll_create()返回一个文件描述符,如果发生错误则返回-1。
Int epoll_ctl(Int epfd, Int op, Int fd, struct epoll_event *event);
将文件描述符添加到此epoll实例的监视集。将从epoll_create()接收到的文件描述符作为第一个参数传递。传递要监视的文件描述符作为第三个参数。
op参数的有效值为:
EPOLL_CTL_ADD向监视列表添加一个条目。EPOLL_CTL_MOD修改与fd相关的设置。EPOLL_CTL_DEL从监视列表中删除fd。
第四个形参是指向结构体的指针
Struct epoll_event {uint32_t events;/* Epoll事件*/ epoll_data_t data/*用户数据变量*/};
在最常见的用法中,事件将是EPOLLIN,数据将被设置为要监视的文件描述符。这将为文件描述符上可读取的数据设置监视。
Int epoll_wait(Int epfd, struct epoll_event *events, Int maxevents, Int timeout);
等待一个或多个监视文件描述符上的事件。传递epoll_create()返回的epoll文件描述符作为第一个参数。超时值是epoll_wait()在返回之前将阻塞的毫秒数。传递超时值-1将导致epoll_wait()阻塞,直到事件发生。events参数是指向一个结构的指针,该结构将接收有关生成的事件的信息。
Maxevents是您希望接收的最大事件数,并且必须大于0。Epoll_wait()返回可用于请求操作的监视文件描述符数量的计数。
events形参是一个指向epoll_events结构数组的指针。此数组中的条目数必须大于或等于传递给maxevents参数的值。
#include <sys/ inotify_h > int inotify_init(void);
Inotify_init()返回一个整型文件描述符,用于此文件监视。
Int inotify_add_watch(Int fd,const char *pathname,uint32_t mask);
与inotify_init()返回的文件描述符一起使用。路径名可以指向文件或目录。如果它指向一个目录,则监视将赢博体育于该目录中的赢博体育文件。您可以通过设置掩码来指定要监视的事件。
一些事件的例子:
IN_CREATE在监视目录下创建的文件/目录。IN_DELETE从监视目录中删除的文件/目录。IN_DELETE_SELF被监视的文件/目录本身被删除。IN_MODIFY文件被修改。
Inotify_add_watch()返回一个整数监视描述符。
要获取文件监视事件,可以从inotify_init()返回的文件描述符中读取。如果在inotify_init()提供的文件描述符上调用read(),则读取将阻塞,直到发生事件。您还可以使用epoll系统告诉您何时在通知文件描述符上有数据可用。
read将返回以下结构的一个或多个实例:
Struct inotify_event {int wd;/*观察描述符*/ uint32_t掩码;/*掩码描述事件*/ uint32_t cookie;/*唯一的cookie关联相关事件(对于rename(2)) */ uint32_t len;/* name字段的大小*/ char name[];/*可选以空结束的名称*/};
有关演示如何读取一个或多个事件结构的示例代码,请参阅文本。
在这个家庭作业中,我们将在文件夹监视系统中使用事件驱动编程。特别是,我们将建立一个基于通知系统的文件夹监视系统。我们还将使用epoll来等待通知事件。
同样,这个作业将从课本上的阅读作业开始。您应该首先阅读教材第八章末尾的“监视文件事件”一节,以及第四章的“事件轮询”一节。
在本作业中,您将设置一个新版本的文件夹监视系统。与您为作业四设置的程序一样,文件夹监视程序将从/etc/fwd.conf读取一个配置文件,该文件列出了要监视的一组文件夹。不是为每个文件夹设置一个单独的线程来监视,而是设置一个inotify监视来监视每个目录中文件的写事件。要监视目录中的写事件,您只需要在目录本身上设置一个inotify监视,并将监视设置为在发生创建事件或写事件时通知您。
在程序的主循环中,您将使用epoll机制来等待您设置的手表上的事件。当收到写事件发生的通知时,您需要像往常一样将该事件记录到fwd.log文件中。您将写入日志文件的消息的一部分是标记。您将需要设置一个数据结构,允许您将inotify文件描述符或监视描述符映射到标记,以便在发生inotify事件时将正确的标记写入日志。消息的另一部分是事件的时间。由于接收通知事件的时间应该与所讨论的文件发生更改的时间非常接近,因此只获取当前时间并将其写入日志就足够了。
就像在作业四中一样,您还需要将程序设置为在接收到SIGINT或SIGTERM信号时优雅地关闭。这一次,您将不能使用sigwait()来等待这些信号,因为您将使用epoll机制来等待通知事件。这意味着您需要安装一个信号处理程序来响应这些信号。
下面是如何让赢博体育程序在出现信号时优雅关闭的大纲:
//全局标志int running;//信号处理程序无效处理程序(int sig) {running = 0;} int main() {running = 1;//安装信号处理程序//打开日志文件//读取配置文件并在(运行)时设置手表{//调用epoll_wait //阻塞信号//处理事件//取消阻塞信号}//关闭日志文件}
如果关闭信号到达,而主循环被epoll_wait()调用阻塞,epoll_wait()将立即返回,计数为-1。如果看到计数为-1,表示发生了错误,则可以检查errno是否为EINTR—这将确认对epoll_wait()的调用因信号到达而中断。