socket网络编程-epoll/select/poll

epoll/select/poll
epoll
int epoll_create(int size);//这个参数被忽略,只要大于0就行了
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
//关于timeout如果为0则立即返回,如果为-1可能是永远阻塞
struct epoll_event
{
    __uint32_t events;//关心的事件
    epoll_data_t data;
};
typedef union epoll_data
{
    void* ptr;
    int fd;
    __int32_t u32;
    __int64_t u64;
}epoll_data_t; 

epoll之所以高效的原理(相比select/poll):

  1. select/poll每次调用时都要传递所要监控的所有fd到内核态(即将所有fd从用户态拷贝到内核态),当fd数量多时会造成低效。而每次调用epoll_wait时不需要传递fd列表给内核,因为在epoll_ctl中已经将需要监控的fd告诉了内核(内核开辟一块缓存将epoll_ctl每次注册的fd已红黑树的形式组织起来,所以不需要每次拷贝到内核中,而只是增加或者减少),即在epoll_create后,内核已经在内核态开始准备数据结构存放要监控的fd了,每次epoll_ctl只是对该数据结构进行简单的增删改操作
  2. 内核为epoll提供了快速的数据结构,当调用epoll_create时,epoll向内核注册了一个文件系统,用于存储上述被监控的fd,即开辟出epoll自己的内核高速cache区,在这里以红黑树的形式将fd保存起来,从而支持快速的插入、删除、查找等操作
  3. select/poll每次返回后都会线性扫描全部的fd集合,这样的效率很低,同时当其返回时将内核的fd又拷贝到用户空间,这种内存拷贝效率低下,而epoll在epoll_create时还会创建一个双向链表,该链用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里面有没有数据即可,有数据则返回,否则sleep,直到超时,而维护这个list的方法就是在epoll_ctl函数不仅将新的fd插入到红黑树中,还把注册一个回调函数(给内核中断处理程序),当该fd可读/可写时,中断,该函数被调用,并将该fd放到list链表中

因此epoll是通过一颗红黑树,一张准备就绪链表ready_list,少量的内核cache就解决了高并发问题

  1. epoll_create 创建红黑树和rdllist链表,即一个epollfd 对应一个struct eppoll_entry结构体,结构体中保存红黑树和准备链表
  2. epoll_ctl,增加fd时,先检查红黑树中是否已经存在,如果不存在则插入,并向内核注册回调函数,一个fd对应一个struct epitem结点,其即为红黑树中的一个结点
  3. epoll_wait立即返回准备就绪链表中的数据即可

https://blog.csdn.net/baiye_xing/article/details/76352935

https://blog.csdn.net/weiyuefei/article/details/53006659

https://blog.csdn.net/xiajun07061225/article/details/9250579

epoll的ET和LT模式

源码级的区别:

ET的触发方式:ET的触发只可能是fd的状态改变时才会触发,而导致读fd的状态改变的条件如下:

  1. 当buffer由不可读变为可读,即读取缓冲区由空变为不空。

  2. 当由新数据到达时,即buffer中待读取的数据增加时。

  3. 当buffer有数据可读且用户对相应的fd进行了epoll_ctl mod操作

而导致写fd状态改变的条件如下:

  1. 当buffer由不可写变为可写时,即由满变为不满。
  2. 当有旧数据被送走时,即buffer中待写的内容变少时。
  3. 当buffer可写且用户对fd进行epoll_ctl mod操作。
  4. 当初始将fd注册到epoll时,如果此时发送缓冲区为空(或者有空闲),那么无论是LE还是ET都会被触发

LT的触发方式:除了上述的ET的触发方式外,对于读,只要buffer中有数据可读,即可触发;对于写,只要buffer中有空间可写,即buffer不满时。

EPOLLONESHOT:只监听一次事件,当监听完这次事件后,如果还需要继续监听这个socket,则需要再次把这个socket mod到这个epoll队列中去,根本目的是防止多个线程同时处理同一个socket,(如果一个socket可读被一个epoll返回,该线程将其读取完毕后准备写入,当时同时该socekt上又有数据到来,将可能触发另外一个线程对其继续读取,从而可能出现问题)

ET模式的好处在于可以减少epoll_wait的触发次数,但是这样也导致缓冲区中的数据如果没有全部读出来,下次将无法继续读,所以需要循环读取,而LT模式则可能会导致多次epoll_wait的触发,特别是在关注写时,即便没有数据要写也可能会导致epoll_wait的返回,所以最好的方法应该是读采用LT而写采用ET

https://blog.csdn.net/I_am_JoJo/article/details/7680316

https://blog.csdn.net/Jammg/article/details/51854436

select
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
//有就绪描述符则返回就绪数目,超时返回0,出错返回-1

具体用法参见UNIX网络编程p127

maxfdp1表示的是监视的最大的文件描述符+1,select通过三个fd_set来为需要监听的描述符注册读写事件,fd_set实际是一个整数数组,每个整数的每一位对应于一个fd,如果将该位打开为1,则可以表示监听该描述符,

select可以监听的最大描述符数量是1024,当然可以通过修改FD_SETSIZE的值来修改这个1024,但是需要注意的是如果修改这个值那么是需要重新编译内核的,另外FD_SETSIZE限定的是文件描述符的最大值只能是FD_SETSIZE,即限定的是maxfdp1,并不是监听的个数最大是FD_SETSIZE,如果从0-1023中的某些fd被其他进程占有,那么select所能监听的最大描述符个数将少于1024

为什么select的最大限制是1024?

因为fd_Set是32个32位整数,一位代表一个套接字,那么一共1024

poll
int poll(struct pollfd *fdarray, unsigned long nfds, int times);
//返回大于0为准备就绪的套接字个数,等于0为超时,-1为发生错误
epoll/select/poll对比
  1. select/poll每次调用时都要传递所要监控的所有fd到内核态(即将所有fd从用户态拷贝到内核态),当fd数量多时会造成低效。而每次调用epoll_wait时不需要传递fd列表给内核,因为在epoll_ctl中已经将需要监控的fd告诉了内核(内核开辟一块缓存将epoll_ctl每次注册的fd已红黑树的形式组织起来,所以不需要每次拷贝到内核中,而只是增加或者减少),即在epoll_create后,内核已经在内核态开始准备数据结构存放要监控的fd了,每次epoll_ctl只是对该数据结构进行简单的增删改操作
  2. 内核为epoll提供了快速的数据结构,当调用epoll_create时,epoll向内核注册了一个文件系统,用于存储上述被监控的fd,即开辟出epoll自己的内核高速cache区,在这里以红黑树的形式将fd保存起来,从而支持快速的插入、删除、查找等操作
  3. select/poll每次返回后都会线性扫描全部的fd集合,这样的效率很低,同时当其返回时将内核的fd又拷贝到用户空间,这种内存拷贝效率低下,而epoll在epoll_create时还会创建一个双向链表,该链用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里面有没有数据即可,有数据则返回,否则sleep,直到超时,而维护这个list的方法就是在epoll_ctl函数不仅将新的fd插入到红黑树中,还把注册一个回调函数(给内核中断处理程序),当该fd可读/可写时,中断,该函数被调用,并将该fd放到list链表中,此时epoll_wait函数返回只需要拷贝活跃的fd到用户态即可

总结起来说select/poll的缺点就是:

  1. select所支持的最大并发数有限,即1024(虽然poll解决了这个问题),
  2. 同时IO的效率会随着并发数的增加而不断降低,因为当有多个连接时,任意时间可能只有部分连接是活跃的,而每次调用select和poll时需要将监视的描述符拷贝到内核态,同时当返回时又将所有的描述符拷贝到用户态,这样是很耗时的,同时由于只有部分连接是活跃的,所以需要线性扫描所有的连接来找出所有的活跃连接
  3. select/poll在寻找有事件连接的复杂度是O(n),而epoll是O(1)

当select相对于epoll也是有优点的:

  1. select可以在不同的操作系统下使用
  2. 在少量高并发下,而且每个连接都很活跃时select比epoll高效,此时select查找活跃连接的效率也是O(1),但是fd_set的大小比epoll_event小很多,所以拷贝效率高一点
发布了23 篇原创文章 · 获赞 4 · 访问量 2128

猜你喜欢

转载自blog.csdn.net/hdadiao/article/details/104614574