作者: Sam (甄峰) sam_code@hotmail.com
1. 基础介绍:
epoll 是2.6.8 kernel
加入的IO复用模型,它从三个方面改进了select和poll模型。现在我们先尝试使用之。
epoll在实现和使用上与select, poll有很大差异。
A. epoll使用一组而不是一个函数来实现模型。
B.
epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中。无需像select和poll那样每次调用都要重复传递文件描述符集与事件集。(所以需要创建一个文件描述符,来唯一标识内核中的这个事件表)。
C.
将增加减少文件描述符上的事件这个低频率动作(epoll_ctl)和获取有事件的文件描述符这个高频率(epoll_wait)分开。
D.
epoll_wait()返回值表示就绪的文件描述符个数。并把就绪的事件递交给参数。这个参数中所有数据,均为就绪的文件描述符。不像select或poll是全部文件描述符。
2. epoll系列函数介绍:
2.1: 创建内核事件表文件描述符:
int epoll_create(int size);
它创建一个epoll 实例。 参数size虽然被忽视,但必须大于0。
epoll_create()返回一个文件描述符,它代表一个新的epoll实体。
这个文件描述符需要在所有epoll interface中被作为参数使用。
如果不再被使用了,必须被close(2)所关闭。
失败时,返回 -1. errno被设备。
正常时,返回文件描述符。
2.2: 对epoll文件描述符进行操作:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event
*event);
它对参数一epfd所代表的epoll实体进行操作。
把参数二op所代表的操作执行到参数三fd代表的用户关注文件描述符上。
操作op:
EPOLL_CTL_ADD
Register the
target file
descriptor fd
on the epoll instance referred to by the
file
descriptor epfd and associate the event event
with the internal file linked to fd.
注册fd上发生的事件(参数4-event) 到epoll实例.
EPOLL_CTL_MOD
Change the event event associated with the
target file descriptor fd.
EPOLL_CTL_DEL
Remove (deregister) the target file descriptor
fd from the epoll instance referred to
by
epfd. The event is ignored and
can be NULL (but see BUGS below).
参数四:
typedef union epoll_data {
void
*ptr;
int
fd;
uint32_t
u32;
uint64_t
u64;
}
epoll_data_t;
struct
epoll_event {
uint32_t
events;
epoll_data_t data;
};
其中:
uint32_t events;
包含:
EPOLLIN
The associated file is available for read(2)
operations.
有数据可读
EPOLLOUT
The associated file is available for write(2)
operations.
有数据可写
EPOLLRDHUP (since Linux 2.6.17)
Stream socket peer closed connection, or shut
down writing half of connection. (This flag
is especially useful for writing simple code to
detect peer shutdown when using Edge Trig‐
gered monitoring.)
EPOLLPRI
There is urgent data available for read(2)
operations.
EPOLLERR
Error condition happened on the associated file
descriptor. epoll_wait(2)
will always
wait for this event; it is not necessary to set
it in events.
EPOLLHUP
Hang up
happened on the associated file
descriptor. epoll_wait(2) will always wait
for
this event; it is not necessary to set it in
events. Note that when reading from a
chan‐
nel such as
a pipe or a stream socket, this event merely
indicates that the peer closed
its end of the channel.
Subsequent reads from the channel will return
0 (end of
file)
only after all outstanding data in the channel
has been consumed.
EPOLLET
Sets the Edge Triggered behavior for the
associated file descriptor. The default
behavior
for epoll is Level Triggered.
See epoll(7) for more detailed information about
Edge and
Level Triggered event distribution
architectures.
2.3: 等待关注的文件描述符上的I/O Event:
int epoll_wait(int epfd, struct epoll_event *events, int
maxevents, int timeout);
等待epfd所代表的epoll实体中发生 所关注的event.
参数一epfd: epoll 实体 文件描述符。
参数二events: 就绪数组。由Kernel copy过来的。
参数三maxevents: 最多监听的事件数。
参数四timeout:
3. LT和ET模式:
epoll(7) 对文件描述符的操作有两种模式: LT(Level Trigger: 电平触发)和ET(Edge
Trigger: 边沿触发)。
LT模式是缺省模式。
LT模式下,只要在关注的文件描述符上,有符合条件的事件发生,epoll_wait()就会返回并通知。
例如:epoll_ctl()注册了某个文件描述符关心EPOLLIN。
当有数据时,epoll_wati()返回并通知程序有数据。程序读取一部分数据(没有读完)后。
再次调用epoll_wait(). 则立刻返回并通知程序有数据。
因为它是按照Level 来决定触发。
所有有数据,就触发。一直触发到没有数据为止。(当然TCP并不是有数据就触发,而是有个限定值)。
ET模式下,只有在关注的文件描述符上,在状态改变那一瞬间,epoll_wait()会返回并发出通知。
例如:epoll_ctl()注册了某个文件描述符关心EPOLLIN。当有数据时,epoll_wati()返回并通知程序有数据。程序读取一部分数据(没有读完)后。
再次调用epoll_wait(). 则它不会返回并报告有数据。
因为它是按照边沿触发的。所以一次没读完全。 后续的数据不会再报告。
在noblock模式下:
1. 某个TCP连接。 socket的对端发送了1024个字节。
LT模式下,epoll_wait()返回并通知有数据。recv(2)或read(2)选择读取10个数据。
此时再调用epoll_wait(), 会立刻通知有数据。直到1024个数据读完。则recv(2) 或read(2)返回-1.
errno=EAGAIN或者EWOULDBLOCK. 表明暂时没数据了。
ET模式下,epoll_wait()返回并通知有数据。recv(2)或read(2)选择读取10个数据。
此时再调用epoll_wait(), 不会返回。
所以,在ET模式下,需要程序确保把数据读完。
2. 某个TCP连接。
socket的对端发送了1024个字节。应用程序正在读取阶段,又来了1024个数据。
LT模式下,epoll_wait()返回并通知有数据。每次读取10字节。
此时再调用epoll_wait(), 会立刻通知有数据。直到第二个1024字节数据依次读取完成。则recv(2)
或read(2)返回-1. errno=EAGAIN或者EWOULDBLOCK. 表明暂时没数据了。
ET模式下,应用程序正在读取时,第二个1024字节到达,epoll_wait()也会返回并通知有数据到达。
LT,ET模式转换:
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;
event.events |= EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
4. EPOLLONESHOT:
考虑这种情况,Nobloack模式+ET模式。
每次epoll_wait()返回时,就创建一个Thread()去接收数据并处理数据。
但如果正在处理数据时,这个socket()又有一个数据到了。则创建另一个Thread去接收和处理数据。
这就导致,同一个socket的数据,同时被两个socket 处理。 在某些有数据相关性的情况下,这会造成问题。
怎样才能保证同一个socket的数据,在同一时刻,只被一个thread处理呢?这就有了EPOLLONESHOT。
当一个文件描述符(Nobloack模式+ET模式)设置了EPOLLONESHOT后,操作系统最多只触发一次。直到我们用epoll_ctl()重置该文件描述符。才会再次触发。
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;
event.events |= EPOLLET;
event.events |=
EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);