I/O 复用: select/ poll
I/O 复用代码实践:https://github.com/codeflysafe/computer-networkI/O 模型数据的读取分为两个阶段:准备数据将数据从内核拷贝到进程(用户空间)对于套接字的输入操作,第一步通常是等待数据从网络中到达,当所有分组到达时,将它复制到内核的某个缓冲区(准备数据);第二步是把数据从内核缓冲区复制到应用进程缓冲区5种I/O 模型阻塞式I/O非阻塞式I/O采用
I/O 复用
代码实践:Github
I/O 模型
数据的读取分为两个阶段:
- 准备数据
- 将数据从内核拷贝到进程(用户空间)
对于套接字的输入操作,第一步通常是等待数据从网络中到达,当所有分组到达时,将它复制到内核的某个缓冲区(准备数据);第二步是把数据从内核缓冲区复制到应用进程缓冲区
5种I/O 模型
阻塞式I/O
非阻塞式I/O
采用轮训式,一旦内核中数据准备好,便等待数据复制后返回
I/O复用
分为两步,采用select(poll)
函数(阻塞),等待数据报套接字变成可读。当select
返回这一条件后,再调用 recvfrom
函数去读取数据
信号驱动式I/O
异步I/O
对比
前四种在第二阶段相似,都在等待数据从内核复制到用户进程那里阻塞
5种模型的对比
select
函数
int select(int maxfd1, fd_set * __restrict, fd_set * __restrict,
fd_set * __restrict, struct timeval * __restrict)
struct timeval
_STRUCT_TIMEVAL
{
__darwin_time_t tv_sec; /* seconds */
__darwin_suseconds_t tv_usec; /* and microseconds */
};
告知内核等待所制定描述符中的任何一个就绪可花费多长时间
这个参数有三种可能:
- 永远等待下去,有一个描述符准备好后I/O才返回,此时将该
__restrict
设置为 null - 等待一段固定时间, 有一个描述符准备好后I/O才返回, 等待固定时间(
__restrict
)未收到也直接返回 - 不等待, 检查描述符后立刻返回,类似于
轮询
的方式__restrict
设置为0
其实第1,3都是第2中的一种特例,区分在于时间的设置
中间三个fd_set
代表 可读、可写、发生异常三种描述符,都是整数数组。在select
函数中可以置为空
异常分为两种
- 某个套接字的外带数据到达
- 某个已经设置为分组模式的伪终端存在可从其主端读取的控制状态信息
fd_set
是一个整数数组,最大可以表示1024个描述符数量,它底层是一个整数数组(int型数组,大小为32个)
#define __DARWIN_FD_SETSIZE 1024
#define __DARWIN_NBBY 8 /* bits in a byte */
#define __DARWIN_NFDBITS (sizeof(__int32_t) * __DARWIN_NBBY) /* bits per mask */
typedef struct fd_set {
__int32_t fds_bits[__DARWIN_howmany(__DARWIN_FD_SETSIZE, __DARWIN_NFDBITS)];
} fd_set;
操作fd_set
主要使用以下宏
fd_set rset;
// 重置为0
FD_ZERO(&rset);
// 设置位置, 最大是1024
FD_SET(1000, &rset);
// 判断此位是否设置
FD_ISSET(1000, &rset);
// 将该位置位0
FD_CLR(1000, &rset);
判定条件
maxfd1
带测试描述符的个数,通常为最大描述符+1
因为,它扫描 0~maxfd1
区间的描述符
poll
函数
int poll(struct pollfd *, nfds_t, int)
struct pollfd {
int fd;
short events;
short revents;
};
typedef unsigned int nfds_t;
从函数可以看出,它于select
的最大区别在于,它将fd组织成了一个链表,这样突破了1024的限制,理论上是无限的。它将三种事件描述符(可读、可写、异常发生)绑定到每个fd上,
struct pollfd {
int fd;
short events;
short revents;
};
他将事件分为以下几种,通过判定revents字段来确认是否准备完毕
References
更多推荐
所有评论(0)