I/O复用
linux下实现I/O复用的系统调用主要有select,poll和epoll.
一、select系统调用
用途:在一段指定时间内,监听用户感兴趣的文件描述符上的可读可写和异常等事件。
1.1 select API
#include<sys/select.h>
int select( int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval * timeout );
- nfds参数指定被监听的文件描述符的总数,select监听所有文件描述符的最大值+1(文件描述符是从0开始计数的)。
- readfds,writefds,exceptfds分别指向可读、可写、异常等事件对应的文件描述符的集合。应用程序调用select函数时,通过这3个参数传入自己感兴趣的文件描述符。select调用返回时,内核将修改他们来通知应用程序哪些文件描述符已经就绪。三个参数的是通过fd_set结构体仅包含了一个整型数组,该数组的每个元素的每一位标记一个文件描述符。fd_set能容纳的文件描述符数量由FD_SETSIZE指定,这就限定了select能同时处理文件描述符的总量。
- timeout参数用来设置select函数的超时时间。timeval结构类型的指针:采用指针参数是因为内核将修改它来告诉应用程序select等待了多久。但是timeout在调用失败是值是不确定的。
select提供微妙级的定时方式。给timeout变量tv_sec成员和tv_usecc成员都传递0,则select将立即返回。如果传递null,select一直阻塞,直到某个文件描述符就绪。
select成功时返回就绪文件描述符的总数。如果在超时时间内没有任何文件描述符就绪,select将返回0,select失败时返回-1并设置errno.如果在select等待期间,程序接收到信号,则select立即返回-1.并设置errno为EINTR。
1.2文件描述符就绪的条件
1.2.1以下情况socket是可读的:
- 无堵塞的读该socket:socket内核接收缓存区中字节数>=最低水位标记(SO_RCVLOWAT),此时读操作返回的字节数大于0.
- socket通信的对方关闭了连接,此时读操作返回0;
- 监听socket上有新的连接请求;
- socket上有未处理的错误(使用getsockopt来读取和清除该错误)
1.2.2以下情况socket是可写的:
- socket内核发送缓存区中的可用字节数>=最低水位标记,此时无阻塞的写该socket,写的操作返回的字节数大于0;
- socket的写操作被关闭。(对于写操作被关闭的socket,执行写操作将触发SIGPIPE信号)
- socket使用非阻塞connect连接成功或者失败之后。
- socket上有未处理的错误
1.2.3异常情况:- socket上接收到带外数据
二、poll系统调用poll系统调用和select类似,也是在指定时间内轮询一定数量的文件描述符,用来测试其中是否有绪者。#include<poll.h>int poll( struct pollfd *fds,nfds_t nfds,int timeout );参数解析:1、fds参数,pollfd结构体类型的数组,指定所有文件描述符上发生的可读、可写和异常等事件struct pollfd{int fd;//文件描述符short events;//注册事件,告诉poll监听fd上哪些事件,一系列事件的按位或short revents;//实际发生的事件,有内核填充};通常,应用程序需要根据recv调用的返回值来区分socket上接收到的是有效数据还是对方关闭连接的请求,并作出相应的处理。2、nfds参数指定被监听事件集合fds的大小。3、timeout参数指定poll的超时值,单位是毫秒。当timeout=-1时,poll调用将永远阻塞,直到某个事件发生;当timeout=0时,poll调用将立即返回。poll系统调用的返回值的含义与select相同。三、epoll的系统调用epoll需要一个额外的文件描述符来唯一识别内核中的事件表(存放用户关心的文件描述符上的事件)。1、创建额外的文件描述符int epoll_create( int size );- 参数size不起作用,只是给内核一个提示,告诉事件表的大小。
- 返回值是文件描述符,用作其他epoll系统调用的第一个参数,用来指定要访问的内核事件表。
2、用来操作epoll的内核事件表int epoll_ctl( int epfd,int op,int fd,struct epoll_event *event);参数说明:1、fd:要操作的文件描述符2、op:指定操作类型。操作类型的分类:- EPOLL_CTL_ADD,往事件表中注册fd上的事件。
- EPOLL_CTL_MOD,修改fd上的注册事件
- EPOLL_CTL_DEL,删除fd上的注册事件
3、event:指定事件。结构:struct epoll_event{_uint32_t events;//描述事件类型epoll_data_t data;};//存储用户数据data的定义:typedef union epoll_data{void *ptr;//用来指定和fd相关的用户数据int fd;//指定事件所从属的目标文件描述符uint32_t u32;uint64_t u64;}epoll_data_t;注:由于epoll_data_t是一个联合体,故不能同时使用ptr和fd.如果要实现将文件描述符和用户数据关联起来,以实现数据的快速访问。解决方法:放弃使用epoll_data_t中的fd成员,而在ptr指向的用户数据中包含fd.3、epoll_wait函数:一段超时时间内等待一组文件描述符的事件。epoll系列调用的主要接口是epoll_wait函数。int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);参数解释:- maxevents:指定最多监听多少事件,必须大于0;
- events:只用于输出epoll_wait检测到的就绪事件。
注:epoll_wait如果检测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指定的数组中。