干货来袭系列之一:select poll epoll

好久没有写文章,好久没有总结自己的学习了。最近,会带了一系列干货,总结过去,勉励以后。废话不多说,先来一系列I/O复用浅析。

先思考一个问题:你觉得I/O复用有什么好处?有没有用过select、poll、epoll?epoll为什么比较好?

活跃你的思维,想一想,如果是你,你会怎么回答这几个问题?

那么,我想先说一下,什么是I/O多路复用机制?

所谓I/O多路复用机制就是说通过一种机制,可以监视多个文件描述符,一旦某个文件描述符就绪,能够通知程序进行相应的读写操作。(I/O复用虽然能同时监听多个文件描述符,但是它本身却是阻塞的。并且当一个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依次处理其中的每个文件描述符,这样就相当于服务端工作方式为串行的;所以必须采用多进程或多线程来实现并发。)那么,我选择其中的select、poll、epoll三组I/O复用系统调用进行解说。

首先,说说select

select系统调用的用途:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写、异常事件。(摘自《Linux高性能服务器编程》)

#include <sys/select.h>int select(int nfds, fd_set* readfds, fd_set* writefds,fd_set* exceptfds, struct timeval *timeout); 这是select的函数原型,关于返回值,select成功时返回就绪(可读、可写、异常)文件描述符的总数;如果在超时时间内没有任何文件描述符就绪,select将返回0;失败返回-1并设置errno。如果在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。

参数:

ndfs指定被监听的文件描述符的总数(int);

readfds、writefds、exceptfds参数分别指向可读、可写、异常时间对应的文件描述符集合,其中fd_set结构体包含一个整型数组,该数组的每个元素的每1位标记一个文件描述符,并且fd_set能容纳的文件描述符数量由FD_SIZE指定,所以说select所能同时处理的文件描述符总量是有一定限制的(默认1024);

timeout参数用来设置select函数的超时时间,类型是一个timeval类型的指针,如果给timeout变量的tv_sec成员和tv_usec成员都传递0,则sleect立即返回;如果给timeout传递NULL,则select将一直阻塞,直到某个文件描述符就绪。

那么,select实现同时监听多个文件描述符效果真的很好吗,它真的能够提供效率吗?

答案是否定的,select也有一些缺点:

<1>每次调用select,都需要将fd集合从用户态拷贝到内核态,这个开销在fd很多时也很大;

<2>每次调用select,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大;

<3>select支持的文件描述符数量太小了,默认的是1024

再说说poll

poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。

#include <poll.h>int poll(struct pollfd* fds, nfds_t nfds, int timeout);这是poll的函数原型,关于返回值的含义与select相同。

参数:

fds参数是一个pollfd结构类型的数组,struct pollfd{int fd;/*文件描述符*/ short events; /*注册的事件*/ short revents; /*实际发生的事件由内核填充*/};

nfds参数指定被监听集合fds的大小;

timeout参数指定poll的超时值;当timeout为-1时,poll调用将永远阻塞,直到某个事件的发生;当timeout为0时,poll调用将立即返回。

事实上,poll和select没有什么大的区别,最主要的区别就是poll只有一个事件集参数,统一处理所有事件类型;其次,poll所最大支持的文件描述符数是65535(目前没有验证,内容来自《Linux高性能服务器编程》)

虽然说poll支持的文件描述符的数目有所增加,但是,,,,它的效率还是不能满足编程人员的需求啊。。。。

接下来就是,epoll的引入(底层实现是“mmap、红黑树、链表”)

epoll原型如下:

#include<sys/epoll.h>int epoll_create(int size);//创建文件描述符,标识内核中的事件列表

该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。(创建好后,会占用一个fd的值,所以在使用完epoll后,,必须调用close关闭,否则可能导致df被耗尽)

参数:

size现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。

#include<sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);//操作epoll的内核事件表epoll_ctl函数调用成功时返回0,失败则返回-1并设置errno。(不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是先注册要监听的事件类型)

参数:

epfd参数是epoll_create函数的返回值;

fd参数是要操作的文件描述符;

op参数则指定操作类型(EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL);

event参数指定事件;它是epoll_event结构指针类型

其中epoll_event结构体定义如下:

struct epoll_event{__uint21_t events;//epoll事件epoll_data_t data;//用户数据};typedef <span style="color:#ff0000;">union</span> epoll{//公用体void *ptr;//通用指针类型,也就意味着ptr的类型可以由使用者定义int fd;//使用最多,指定事件所从属的目标文件描述符uint32_t u32;//typedef unsigned int uint32_t; 用来保证平台的通用性,增加可移植性uint64_t u64;//同上}epoll_data_t;

一个fd被添加到epoll中之后(EPOLL_ADD),内核会为它生成一个对应的epitem结构对象,epitem被添加到epoll_event的红黑树中。(红黑树的作用是使用者调用EPOLL_MOD操作时能快速找到fd对应的epitem)。

我们首先去了象鼻山,那里景色秀丽神奇,

干货来袭系列之一:select poll epoll

相关文章:

你感兴趣的文章:

标签云: