Quantcast
Channel: Sam的技术Blog
Viewing all articles
Browse latest Browse all 158

Linux下IO多路复用epoll的使用初探

$
0
0
作者: 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);






 

Viewing all articles
Browse latest Browse all 158

Trending Articles