您的位置:首页 > 运维架构 > Linux

epoll系列系统调用

2016-07-13 17:05 295 查看
一、文件描述符就绪

epoll用来同时监听多个文件描述符是否就绪,那么哪些情况下文件描述符可以被认为是可读、可写或者异常呢?

1、文件描述符可读的情况

socket内核接收缓冲区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞地读,并且读操作返回的字节数大于0.
socket通信的对方关闭连接。此时对该socket的读操作将返回0.
监听socket上有新的连接请求。
socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

2、文件描述符可写的情况

socket内核发送缓冲区中的可用字节数大于或等于其低水位标记SO_SNDLOWAT。此时我们可以无阻塞地写,并且写操作返回的字节数大于0.
socket的写操作被关闭。对写操作关闭的socket执行写操作将触发一个SIGPIPE信号。
socket使用非阻塞connect连接成功或者失败(超时)之后。
socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

二、系统调用简介

epoll将用户关心的文件描述符上的事件放在内核的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件表述符集或事件集。

int epoll_create(int size)这个函数用来返回一个标识内核事件表的文件描述符。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)这个函数可以向内核事件表中注册、修改和删除fd上的事件。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)这个函数用来等待一组文件描述符上的事件,如果其检测到事件,就将所有就绪的事件从内核事件表中复制到第二个参数events指向的数组。这个数组只包含检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。

三、LT和ET模式

LT(level-triger)和ET(edge-triger)是epoll对文件描述符的操作的两种模式,二者的差异在于level-trigger模式下只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket;而edge-trigger模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。换句话说,如果epoll_wait通知有事件发生后,应用程序没有立即处理该事件(readable或writable状态依然存在),应用程序下一次调用epoll_wait函数会有不同的表现:

LT模式下epoll_wait会再次向应用程序通告此事件(因为socket依然处于readable/writable状态); 
ET模式下epoll_wait将不再通知这一事件(因为socket的readable/writable状态没有变化)。epoll默认工作在LT模式。
所以,在epoll的ET模式下,正确的读写方式为:
读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN
写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN
四、文件描述符非阻塞模式

在一个非阻塞的socket上调用read/write函数返回-1且errno等于EAGAIN或者EWOULDBLOCK时,表示资源暂时不够,read时,读缓冲区没有数据,或者write时,写缓冲区满了。遇到这种情况,如果是阻塞socket,read/write就要阻塞掉。
所以,对于阻塞socket,read/write返回-1代表网络出错了。但对于非阻塞socket,read/write返回-1不一定网络真的出错了。
综上,对于non-blocking的socket,正确的读写操作为:

读:忽略掉errno = EAGAIN的错误,下次继续读
写:忽略掉errno = EAGAIN的错误,下次继续写
五、accept系统调用的特殊性
1、阻塞模式 accept 存在的问题
考虑这种情况:TCP连接被客户端夭折,即在服务器调用accept之前,客户端主动发送RST终止连接,导致刚刚建立的连接从就绪队列中移出,如果套接口被设置成阻塞模式,服务器就会一直阻塞在accept调用上,直到其他某个客户建立一个新的连接为止。但是在此期间,服务器单纯地阻塞在accept调用上,就绪队列中的其他描述符都得不到处理。

解决办法是把监听套接口设置为非阻塞,当客户在服务器调用accept之前中止某个连接时,accept调用可以立即返回-1,这时源自Berkeley的实现会在内核中处理该事件,并不会将该事件通知给epool,而其他实现把errno设置为ECONNABORTED或者EPROTO错误,我们应该忽略这两个错误。

2、ET模式下accept存在的问题
考虑这种情况:多个连接同时到达,服务器的TCP就绪队列瞬间积累多个就绪连接,由于是边缘触发模式,epoll只会通知一次,accept只处理一个连接,导致TCP就绪队列中剩下的连接都得不到处理。

解决办法是用while循环抱住accept调用,处理完TCP就绪队列中的所有连接后再退出循环。如何知道是否处理完就绪队列中的所有连接呢?accept返回-1并且errno设置为EAGAIN就表示所有连接都处理完。

六、使用Linux的epoll模型,水平触发模式;当socket可写时,会不停的触发socket可写的事件,如何处理?

答:开始不把socket加入epoll,需要向socket写数据的时候,直接调用write或者send发送数据。如果返回EAGAIN,把socket加入epoll,在epoll的驱动下写数据,全部数据发送完毕后,再移出epoll。

七、EPOLLONESHOT事件
注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个读、写或者异常事件,且只读一次,除非使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这种机制确保了注册了EPOLLONESHOT事件的socket不会同时被两个不同的线程服务,从而防止出现不稳定因素。注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,进而可以让该socket上的事件得到下次触发。

参考文献:
《Linux高性能服务器编程》
http://www.ccvita.com/515.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  内核 通信 epoll linux