6.2 I/O模型
6.3 select函数
函数作用:允许进程指示内核等待多个事件中的任何一个发生,并仅在有一个或多个事件发生或经历一段指定事件后返回。
之前一般阻塞在真正的I/O系统调用上,现在阻塞在select
上,由select
管理并通知哪个描述字就绪。
我们调用select
通知内核我们感兴趣的描述字。
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *read_set, fd_set *write_set, fd_set *except_set, const struct timeval *timeout);
返回值:就绪描述字的个数,0:超时,-1:出错。
参数解释:
1) const struct timeval *timeout
时间参数,timeval
结构体用于指定事件参数的秒与微秒数;
struct timeval {
long tv_sec; //秒
long tv_usec;//微秒
};
参数 | 含义 |
---|---|
null | 永远等待,直到有描述字就绪 |
固定时间 | 有描述字就绪或者等待时间到 |
不等待 | 一直轮询(poll)检测,立即返回 |
由于传入的参数是一个const
的指针,所以内核不能通过该指针修改对应的时间,那么也就没有办法得知从调用select
开始到多久有描述字就绪,但是可以通过别的手段获取经历的时间(在调用select
前获取系统时间,在返回后再次获取系统时间)。
2) read_set
、write_set
、except_set
这三个参数都是fd_set
类型的指针,fd_set
是一个描述字的集合,实际上是一个结构体。
#define __NFDBITS (8 * sizeof(unsigned long)) //每个ulong型可以表示多少个bit,
#define __FD_SETSIZE 1024 //socket最大取值为1024
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) //bitmap一共有1024个bit,共需要多少个ulong
typedef struct {
unsigned long fds_bits [__FDSET_LONGS]; //用ulong数组来表示bitmap
} __kernel_fd_set;
typedef __kernel_fd_set fd_set;
关于fd_set,详见该博客。
于是read_set
、write_set
、except_set
分别是指向读描述符集合、写描述符集合、异常集合的fd_set
型指针。
由于上面的参数是一个描述符的集合,那么怎么得知是集合中的那一个描述符就绪了呢?
实际上,每个描述符在fd_set
中有一比特与其对应,采用的是bitmap
的思路。
对fd_set
的操作由以下四个宏实现:
void FD_ZERO(fd_set * fdset); //将fd_set中的每一位清零
void FD_SET(int fd, fd_set* fdset);//将fdset中与fd相应的比特置一
void FD_CLR(int fd, fd_set* fdset);//将fdset中与fd相应的比特清零
int FD_ISSET(int fd, fd_set* fdset);//检测fdset中与fd相应的位是否被置位
通过这几个宏操作也能猜测出fd_set
的实现形式。
//每个ulong为32位,可以表示32个bit。
//fd >> 5 即 fd / 32,找到对应的ulong下标i;fd & 31 即fd % 32,找到在ulong[i]内部的位置
#define __FD_SET(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] |= (1<<((fd) & 31))) //设置对应的bit
#define __FD_CLR(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] &= ~(1<<((fd) & 31))) //清除对应的bit
#define __FD_ISSET(fd, fdsetp) ((((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] & (1<<((fd) & 31))) != 0) //判断对应的bit是否为1
#define __FD_ZERO(fdsetp) (memset (fdsetp, 0, sizeof (*(fd_set *)(fdsetp)))) //memset bitmap
举例:
fd_set rdset;//读描述符集合
FD_ZERO(&rdset);//将所有比特清零
FD_SET(1, &rdset);//将1对应的位 置为1
FD_SET(4, &rdset);//将4对应的位 置为1
FD_SET(5, &rdset);//将5对应的位 置为1
如果对 read_set
、write_set
、except_set
中的哪个不感兴趣,就将其设置为空指针。
3)maxfdp1
这个参数的含义是待检测的描述字的个数,他的值为最大的描述符加一,因为描述符以0
开始,所以要想表示个数需要将最大的描述符的值加一。
参数介绍完毕。
由于传入select
函数的是指向描述符集合的指针,而且不是const
的,那么select
函数可以修改指针指向的内容,select
也确实会修改,我们在调用select
之前将描述符对应的位置位,在select
返回之后,未就绪的描述符所对应的比特被清零,就绪的描述符对应的位仍然是1,使用FD_ISSET
即可检测。
当再次调用select
时,需要重新将描述符对应的比特置位。
select缺点:
1.每次调用 select(),都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大,同时每次调用 select() 都需要在内核遍历传递进来的所有 fd(复杂度O(n)),这个开销在 fd 很多时也很大。
2.单个进程能够监视的文件描述符的数量存在最大限制,在 Linux 上一般为 1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。
6.6 shutdown函数
关闭某个方向的连接。
使用close
函数也能关闭连接,调用close
函数之后,只有在文件描述符的引用为0
的时候才会关闭套接口,而shutdown
不管引用计数就发送断开连接的请求。
close
直接关闭两个方向的连接,而shutdown
可以只关闭一个方向的连接。
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
函数的行为依赖于howto
这个参数。
参数 | 含义 |
---|---|
SHUT_RD | 关闭读 |
SHUT_WR | 关闭写 |
SHUT_RDWR | 关闭读写 |
6.10 poll
select() 和 poll() 系统调用的本质一样,poll() 的机制与 select() 类似,与 select() 在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是 poll() 没有最大文件描述符数量的限制(但是数量过大后性能也是会下降)。poll() 和 select() 同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
#include <poll.h>
int poll(struct pollfd* fdarray, unsigned long nfds, int timeout);
返回值:
- 就绪描述符的个数
- 0:超时
- -1:出错,并设置 errno 为下列值之一:
EBADF:一个或多个结构体中指定的文件描述符无效。
EFAULT:fds 指针指向的地址超出进程的地址空间。
EINTR:请求的事件之前产生一个信号,调用可以重新发起。
EINVAL:nfds 参数超出 PLIMIT_NOFILE 值。
ENOMEM:可用内存不足,无法完成请求。
参数说明:
1)struct pollfd* fdarray
:指向结构数据第一个元素的指针,即fdarray[N]
。
结构体pollfd
形式:
struct pollfd {
int fd; //描述字
short events;//等待发生的事件
short revents;//实际发生的事件
};
revents
:revents 域是文件描述符的操作结果事件,内核在调用返回时设置这个域。events 域中请求的任何事件都可能在 revents 域中返回.
events
是几个事件的组合,组合选项如下:
最后三个不能作为events
但是可以作为revents
.
2)nfds结构数组中元素的个数;
3)timeout参数指定poll函数返回之前等待多少时间,单位为毫秒。
epoll
参考Linux epoll模型详解及源码分析
epoll函数:
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);