unix网络编程卷一:第六章——I/O复用 select poll

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_setwrite_setexcept_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_setwrite_setexcept_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_setwrite_setexcept_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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值