每日一练(二十五)
文章目录
12.26Linux 查找字符串所在行 输出行
grep可以实现,grep可以进行模糊查找和精确查找,可以只输内容,也可以同时输出行号:
grep -n "gq" ./gq.txt ;模糊搜索,输出包含字符串的行内容和行号
grep -nw "gq" ./gq.txt ;精准搜索,输出字符串的行内容以及行号
12.27 select 系统调用
select系统调用是可以,监听用户所感兴趣的文件描述符上的可读、可写、异常事件,然后阻塞给出反馈文件描述符,用户根据select反馈的文件描述符来判断是否发生了感兴趣文件描述符上的可读】可写、异常事件等。
select系统调用的原型如下:
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
参数:
- nfds:指定被监听的文件描述符总数,通常设置为被监听文件描述符中最大的fd+1,因为文件描述符是从零开始的
- readfds、writefds、exceptfds:分别指向可读、可写、异常事件对应的文件描述符集合。调用select的时候,通过这三个指针传递要监听的文件描述符的集合,select返回的时候,内核会修改这些指针指向的文件描述符的集合来通知用户哪些文件描述符已经就绪!
- timeout:设置超时时间,传入NULL后select在没有收到事件的时候会一直阻塞,设置超时时间后,select会根据设置的时间来等待,如图:
其中用于设置监听文件描述符集合函数就是:
void FD_CLR(int fd, fd_set *set); //清除集合中的指定文件描述符
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set); //向集合中添加文件描述符
void FD_ZERO(fd_set *set); //清空集合中的所有文件描述符
关于select中监听的文件描述符的就绪条件,在网络编程中的情况如下:
设定非阻塞IO的方法:
对于给定描述符,有两种方法指定非阻塞IO:
12.28 poll 系统调用
poll与select类似,也是轮询文件描述符集合,测试其中是否有就绪的文件描述符,其函数原型如下:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
参数:
- fds:是一个pollfd类型的数组,指定所有我们感兴趣的文件描述符上的可读、可写、异常事件。其中fd代表监听的文件描述符,events代表要监听的事件(可读?可写?异常?),revents是内核修改的,通知用户发送了哪些事件。
- nfds:用来表示监听事件集合的大小
- timeout:设置超时时间,与select类似
poll支持的事件如下:
在读文件的时候还要考虑被文件中断时候的情况,可以采用do-while的形式来写代码
12.29 epoll 系统调用
epoll是通过一组函数来实现IO复用的,epoll的简介如下:
epoll可以打开的文件描述符是系统所能够打开的文件描述符的最大上限。
epoll中的文件描述符采用通知机制,每个fd都可以注册一个callback函数,只有活跃的fd才会调用callback函数,所以epoll是异步的,select和poll可以理解为同步。
而且epoll是通过共享内存mmap来实现内核空间与用户空间的交互的,相比于select和poll的内存拷贝就很高效。
epoll的内部实现不是维护一个文件描述符表的,而是红黑树如下:
将每个文件描述符挂载在红黑树的结点上,还可以挂载回调函数。
就绪的事件会放在一个就绪链表中,所以epoll的实现依靠两种数据结构:红黑树+就绪链表
Linux中epoll的相关函数有三个:
- epoll_create(),返回epoll的文件描述符,实际上是建立了一个红黑树,将epoll的文件描述符放在红黑树的根部
- epoll_ctl(),对epoll的一些属性控制
- epoll_wait(),等待事件的到来
最后一个参数是属性,每个红黑树节点上不仅仅有文件描述符,还有对应的回调函数
wait可以阻塞也可以设置为非阻塞(超时时间),注意event与events的区别
wait返回的是就绪的事件数量,根据返回的数量进行遍历就绪事件,判断用户等待的事件是否发生。通过事件的结构体成员即可查询事件
最重要的就是epoll_wait()函数:
还有epoll的工作模式:
12.30 select、poll、epoll 区别
先看一下整体对比的区别:
下面列出三者之间的关系:
- 三个都是系统调用,都可以同时监听多个文件描述符
- 三个都可以由timeout指定超时时间,知道有事件发送返回,返回值就是就绪的文件描述符的数量
下面通过事件集、最大支持文件描述符数量、工作模式、实现方式等四个方面来比较三者的异同:
事件集
首先,三个系统调用都是通过结构体变量来告诉内核,要监听哪些文件描述符上的哪些事件,并且使用相应类型的结构体来获取内核处理的结果,也就是就绪的文件描述符事件集合。
- select没有将文件描述符和事件绑定,所以只能单独通过三个参数来传递可读、可写、异常事件;另外,由于内核对fd_set集合是在线修改的,所以应用程序下次调用select之前就要重置这三个fd_set事件集合了。
- poll相比select好了一些,将文件描述符和事件集都定义在pollfd结构体中,事件被统一处理,使得编程接口简单不少;而且内核每次修改的都是pollfd结构体中的revents成员,不会影响events,所以下次调用的时候无需重置pollfd类型结构体。
- epoll使用与select和poll完全不同的事件管理方式,epoll在内核中维护一个事件表,并且提供了一个独立的系统调用epoll_wait来添加、删除、修改事件。每次epoll_wait都是通过mmap直接从内核的事件表中取得事件,无需像select和poll一样反复从用户空间中读入事件,epoll_wait的events参数仅仅用来返回就绪的事件,这使得程序索引就绪文件描述符的时间复杂度达到O(1),而select和poll由于是轮询机制,所以程序索引就绪文件描述符的时间复杂度是O(n)。
最大支持文件描述符数量
poll和epoll可以使用nfds和maxevents参数来指定最多监听的文件描述符和事件,这两个数值的大小取决于系统能够打开的最大值。但是select允许打开的文件描述符通常是有限的,虽然可以修改这个限制,但是可能导致不可预期后果。
工作模式
select和poll都只能工作在低效的LT模式,epol可以工作在高效的ET模式,并且epoll还支持EPOLLONESHOT事件,该事件可以进一步减少可读、可写、异常事件被触发的次数。
实现原理
从实现原理上来说, select和poll采用的都是轮询的方式,即每次调用都要扫描整个注册文件描述符集合,并将其中就绪的文件描述符返回给用户程序,因此它们检测就绪事件的算法的时间复杂度是O(n)。epoll_wait不同,它采用的是回调的方式。内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入内核就绪事件队列。内核最后在适当的时机将该就绪事件队列中的内容拷贝到用户空间。因此 epoll_wai无须轮询整个文件描述符集合来检测哪些事件已经就绪,所以其算法时间复杂度是O(1)。
但是,当活动连接比较多的时候, epoll_wait f的效率未必比 select和pol高,因为此时回调函数被触发得过于频繁。所以 epoll_wait适用于连接数量多,但活动连接较少的情况。