I/O多路复用之select、poll、epoll分析

select

select机制问题

1、每次调用,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合过大,开销也会很大

2、同时每次调用select都需要在内核遍历传过来的fd_set,同事遍历时间复杂度为O(n)

3、所以为了性能,内核对fd_set做了限制,大小不可变(32位机默认是1024个,64为2048)

int select(
    int maxfdp1, //int maxfdp1 指定待测试的文件描述字个数,它的值是待测试的最大描述字加1。

    //fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄
    fd_set *readset,

    fd_set *writeset,
    fd_set *exceptset,
    const struct timeval *timeout);

【返回值】
int 若有就绪描述符返回其数目,若超时则为0,若出错则为-1

select()通过fd_set的数据结构,实际上是一个long类型的数组,每一个数组元素都能与一打开的文件句柄建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一Socket或文件可读。

每次调用select,都需要把fd_set集合从用户态copy到内核态(还有内核态->用户态),如果集合很大,开销也大

poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

typedef struct pollfd {
        int fd;                         // 需要被检测或选择的文件描述符
        short events;                   // 对文件描述符fd上感兴趣的事件
        short revents;                  // 文件描述符fd上当前实际发生的事件
} pollfd_t;

与select不同的是,采用链表pollfd替换原来的fd_set,没有连接数限制,基本与select一致

epoll

//创建一个epoll句柄,参数size表明内核要监听的描述符数量。
//调用成功时返回一个epoll句柄描述符,失败时返回-1。
int epoll_create(int size);

int epoll_ctl(
//epfd 表示epoll句柄
int epfd, 

//表示fd操作类型
//EPOLL_CTL_ADD 注册新的fd到epfd中
//EPOLL_CTL_MOD 修改已注册的fd的监听事件
//EPOLL_CTL_DEL 从epfd中删除一个fd
int op, 

//描述符
int fd,

//监听的事件
struct epoll_event *event);

int epoll_wait(
int epfd, 

//从内核得到的就绪事件集合
struct epoll_event * events, 

//内核events的大小
int maxevents, 

//等待的超时事件
int timeout);

epoll两种工作方式

水平触发(LT):默认工作模式,即当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次通知此事件

边缘触发(ET): 当epoll_wait检测到某描述符事件就绪并通知应用程序时,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次通知此事件。(直到你做了某些操作导致该描述符变成未就绪状态了,也就是说边缘触发只在状态由未就绪变为就绪时只通知一次)

由此可见:ET模式比LT模式的效率要高,如果使用ET模式,要保证每次进行数据处理的时候,要将其处理完,不要造成数据丢失,ET模式只支持非阻塞的读写,为了保证数据完整性

总结:

猜你喜欢

转载自blog.csdn.net/weixin_39082432/article/details/105756692