Linux-epoll笔记

    linux 网络编程中 epoll 是 linux 内核提供的高效 IO多路复用模型

    epoll 相比于 select 和 poll 高效的原因可以归纳如下

    (1)select/poll 每次让操作系统去查询套接字上是否有事件发生,并将数据从内核态复制到用户态,这一过程的消耗非常大。而 epoll 在内核通过红黑树(高效的数据结构)管理,并且使用一个就绪事件链表,每次从就绪链表中即可知道发生事件的套接字。而 select 返回的是一个包含所有发生事件套接字的 set ,然后在用户态去判断每个套接字 上是否有事件发生。

    (2)select 每次在调用  select 时,内核要去检测每个套接字集合里的套接字是否有时间发生,而 epoll 是向内核注册了一个回调函数,每当相应的套接字上有事件发生时,将其加入到就绪事件链表中,这样每次调用 epoll 时只需将内核中的就绪事件链表返回。


----------------------------------------------------------------------------------------------------------

epoll 的 ET 和 LT 工作模式

LT模式的特点是:

  •   若数据可读,epoll返回可读事件
  •   若开发者没有把数据完全读完,epoll会不断通知数据可读,直到数据全部被读取。
  •   若socket可写,epoll返回可写事件,而且是只要socket发送缓冲区未满,就一直通知可写事件。
  •   优点是对于read操作比较简单,只要有read事件就读,读多读少都可以。
  •   缺点是write相关操作较复杂,由于socket在空闲状态发送缓冲区一定是不满的,故若socket一直在epoll wait列表中,则epoll会一直通知write事件,所以必须保证没有数据要发送的时候,要把socket的write事件从epoll wait列表中删除。而在需要的时候在加入回去,这就是LT模式的最复杂部分。

ET模式的特点是:

  •   若socket可读,返回可读事件
  •   若开发者没有把所有数据读取完毕,epoll不会再次通知epoll read事件,也就是说存在一种隐患,如果开发者在读到可读事件时,如果没有全部读取所有数据,那么可能导致epoll在也不会通知该socket的read事件。(其实这个问题并没有听上去难,参见下文)。
  •   若发送缓冲区未满,epoll通知write事件,直到开发者填满发送缓冲区,epoll才会在下次发送缓冲区由满变成未满时通知write事件。
  •   ET模式下,只有socket的状态发生变化时才会通知,也就是读取缓冲区由无数据到有数据时通知read事件,发送缓冲区由满变成未满通知write事件。
  •   缺点是epoll read事件触发时,必须保证socket的读取缓冲区数据全部读完(事实上这个要求很容易达到)
  •   优点:对于write事件,发送缓冲区由满到未满时才会通知,若无数据可写,忽略该事件,若有数据可写,直接写。Socket的write事件可以一直发在epoll的wait列表。Man epoll中我们知道,当向socket写数据,返回的值小于传入的buffer大小或者write系统调用返回EWouldBlock时,表示发送缓冲区已满。



总结:

LT:可读,一直通知;缓冲区可写,一直通知;

ET:可读,一次通知;由写缓冲区满到不满,通知一次;



----------------------------------------------------------------------------------------------------------

1.创建 epoll

int epoll_create(int size);

创建一个 epoll ,size 告诉内核这个 epoll 能监听的最大数量。epoll 本身占用一个 fd,所以能监听的最大数量为 size-1。使用完 epoll 后应该要注意 close(),因为 epoll 本身占用一个 fd ,防止 fd 被耗尽。


2. epoll_ctl 函数

int epoll_ctl(int epfd,int op,int fd, struct epoll_event *event);

epfd 为要操作的 epoll 的标识符

op 为要在该 epoll 上执行的操作,有 3 个可选操作

    (1)EPOLL_CTL_ADD :注册一个新的 fd 到 epfd 中

    (2)EPOLL_CTL_DEL   : 从 epfd 中删除一个 fd

    (3)EPOLL_CTL_MOD : 修改一个已经注册的 fd 的侦听事件

fd 为被添加/删除/修改 的 fd

event 的结构如下

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里


3. epoll_wait 函数

int epoll_wait(int epfd, struct epoll_event *events, int maxevents,int timeout);

类似于 select 多路复用模型中 调用 select() 函数, 返回的是需要处理的事件的个数,如返回 0 则表示超时。

events 用来从内核得到事件的集合

maxevents 告诉内核这个 events 有多大

timeout 表示超时时间(以毫秒为单位,0会立即返回,-1表示一直等下去知道时间发生)








猜你喜欢

转载自blog.csdn.net/bobbymly/article/details/79489377