深入浅出:Linux中epoll库之使用方法 (linux epoll库)

Linux中有很多I/O多路复用的机制,其中epoll是比较常用的一种。epoll可以用来监听一个文件描述符,当其中任意一个文件描述符有数据可读或可写时,会通过回调函数通知应用程序进行相应的处理。在高并发的网络编程中,使用epoll可以大幅度提高程序的并发性和响应速度。本文将介绍使用epoll进行网络编程的相关知识和使用方法。

一、epoll的工作原理

epoll的工作原理相对于其他I/O多路复用机制而言比较复杂。当我们调用epoll_create函数时,会返回一个文件描述符epfd。我们可以通过epoll_ctl函数来向epfd中添加或删除文件描述符,或者对其进行一些控制操作。当我们需要对epfd的所有文件描述符进行监听时,可以调用epoll_wt函数,该函数会一直阻塞直到有文件描述符有数据可读或可写。

二、epoll的优点

epoll的优点主要体现在以下几个方面:

1. 支持大量的并发连接,可以监听上万个文件描述符。

2. 支持高效的事件通知机制,当有文件描述符有数据可读或可写时,会立即通知应用程序。

3. 支持边缘触发和水平触发两种模式,可以根据实际需求进行选择。

4. 支持使用epoll_ctl函数对监听的文件描述符进行添加、删除和修改操作,非常灵活。

三、epoll的使用方法

1. 创建epoll对象

我们可以通过epoll_create函数来创建epoll对象,该函数会返回一个文件描述符epfd,该描述符用来操作epoll对象。

“`

#include

int epoll_create(int size);

“`

参数size指定epoll对象中能够监听的文件描述符数量,该参数在Linux 2.6.8以后已经不再使用。如果该参数被设置为0,则epoll_create函数会自动根据系统的默认值创建epoll对象。

2. 向epoll对象中添加文件描述符

我们可以通过epoll_ctl函数向epoll对象中添加或删除文件描述符,或者修改其监听事件的属性。

“`

#include

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

“`

参数epfd指定了需要操作的epoll对象的文件描述符,参数op指定了操作类型,参数fd表示需要监听的文件描述符,参数event则描述了需要监听的事件类型。

下面是event结构体的定义:

“`

struct epoll_event {

uint32_t events; /* 监听的事件类型,可以是EPOLLIN、EPOLLOUT、EPOLLRDHUP等 */

epoll_data_t data; /* 回调函数参数 */

};

“`

我们可以通过events参数指定需要监听的事件类型,比如数据可读、数据可写、关闭连接等。在使用epoll的过程中,常常要用到以下几种事件类型:

“`

EPOLLIN:表示文件可读。

EPOLLOUT:表示文件可写。

EPOLLERR:表示文件出现错误。

EPOLLRDHUP:表示文件的连接被断开。

EPOLLHUP:表示连接被挂起。

“`

3. 进行epoll_wt监听

我们可以通过epoll_wt函数监听epoll对象中的文件描述符,当有文件描述符有数据可读或可写时,会立即通知应用程序。

“`

#include

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

“`

参数epfd指定了需要监听的epoll对象的文件描述符,参数events用来存储已经发生事件的文件描述符和事件类型,参数maxevents表示更大需要监听的事件数量,参数timeout表示epoll_wt函数的超时时间。

epoll_wt函数会阻塞直到有文件描述符有数据可读或可写。当epoll_wt函数返回时,events参数中存储的就是已经发生的事件。

4. 处理epoll_wt返回的事件

在使用epoll进行网络编程时,常常需要在epoll_wt返回后对事件进行处理。通常的做法是遍历事件列表,针对每个事件单独进行处理。

下面是一个示例代码:

“`

struct epoll_event events[1024];

int nfds = epoll_wt(epfd, events, 1024, -1);

for (int i = 0; i

int sockfd = events[i].data.fd;

if (events[i].events & EPOLLIN) { // 可读事件

// 处理数据读取逻辑

} else if (events[i].events & EPOLLOUT) { // 可写事件

// 处理数据发送逻辑

} else {

// 处理其他事件,比如连接关闭、连接错误等

}

}

“`

在实际使用中,我们还需要关闭文件描述符、重新注册事件等一些操作。但以上代码已经涵盖了epoll的基本使用方法。

四、

相关问题拓展阅读:

Linux中select poll和epoll的区别

select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一

个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制伍历宽烂桐,迅速激活这腔亮个文件描述符,当进程调用epoll_wait()

时便得到通知。

浅谈Android之Linux pipe/epoll

管道

管道的概念:

管道是一种最基本的IPC机制,作用于有血缘关系的进程之间腊仔掘,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:

1. 其本质是一个伪文件(实为内核缓冲区)

2. 由两个文件描述符引用,一个表示读端,一个表示写端。

3. 规定数据从管道的写端流入管道,从读端流出。

管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。

管道的局限性:

① 数据自己读不能自己写。

② 数据一旦被读走,便不在管道中存在,不可反复读取。

③ 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。

④ 只能在有公共祖先的进程间使用管道。

常见的通信方式有,单工通信、半双工通信、全双工通信。

简单来说这个管道是一个文件,但又和普通轮核文件不通:管道缓冲区大小一般为1页,即4K字节,管道分读端和写端,读端负责从管道拿数据,当数据为空时则阻塞;写端向管道写数据,当管道缓存区满时则阻塞。

pipe函数

创建管道

    int pipe(int pipefd); 成功:0;失败:-1,设置errno

函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定:fd → r; fd → w,就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区。

管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。如何实现父子进程间通信呢?通常可以采用如下步骤:

1. 父进程调用pipe函数创建管道,得到两个文件描述符fd、fd指向管道的读端和写端。

2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。

3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道戚芹是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

管道的读写行为

    使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):

1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。

2. 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。

4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

总结:

① 读管道: 1. 管道中有数据,read返回实际读到的字节数。

2. 管道中无数据:

(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)

  (2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)

    ② 写管道: 1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)

2. 管道读端没有全部关闭:

(1) 管道已满,write阻塞。

(2) 管道未满,write将数据写入,并返回实际写入的字节数。

Epoll的概念

Epoll可以使用一次等待监听多个描述符的可读/可写状态.等待返回时携带了可读的描述符或者自定义的数据.不需要为每个描述符创建独立的线程进行阻塞读取,

Linux系统中的epoll机制为处理大批量句柄而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率

(01) pipe(wakeFds),该函数创建了两个管道句柄。

(02) mWakeReadPipeFd=wakeFds,是读管道的句柄。

(03) mWakeWritePipeFd=wakeFds 1 ,是写管道的句柄。

(04) epoll_create(EPOLL_SIZE_HINT)是创建epoll句柄。

(05) epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem),它的作用是告诉mEpollFd,它要监控mWakeReadPipeFd文件描述符的EPOLLIN事件,即当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。

回到Android中的epoll大致流程如下:​

Looper.loop -> MessageQueue.nativePollOnce

epoll_create()   epoll_ctl() 注册事件的回调

looper.pollInner() -> epoll_wait() 等待接受事件唤醒的回调

 MessageQueue.enqueueMessage(Message msg, long when)    ->  MessageQueue.nativeWake(long ptr)

参考链接如下

链接:

链接:

linux epoll库的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于linux epoll库,深入浅出:Linux中epoll库之使用方法,Linux中select poll和epoll的区别,浅谈Android之Linux pipe/epoll的信息别忘了在本站进行查找喔。


数据运维技术 » 深入浅出:Linux中epoll库之使用方法 (linux epoll库)