一、IO多路复用处理数据报文
二、select
1. 简介
select系统调用是用来让我们的程序监视多个文件描述符的状态变化的。程序会阻塞到select这里,直到被监听的文件描述符一个或者多个发生了状态变化。
2. 函数原型
int select(int nfds, fd_set* read_fds, fd_set* write_fds,
fd_set* except_fds, struct timeval* timeout);
2.1 参数说明
- nfds:是需要监视的最大的文件描述符值+1
- read_sets:对应于需要检测的可读文件描述符的集合
- write_sets:对应于需要检测的可写文件描述符的集合
- except_sets:对应于需要检测的异常文件描述符的集合
- timeout:用来设置select的等待时间
- NULL:表示不设置select的等待时间,select在监听到描述符状态变化之前将一直阻塞
- 0:表示仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生
- 特定的时间值:如果在这个设置的时间值内没有事件发生,select将超时返回
2.2 fd_set结构说明
首先,我们可以看一下这个结构的定义:
/* The fd_set member is required to be an array of longs. */
typedef long int __fd_mask;
typedef struct{
/* something */
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
/* something */
};
注:上面代码节选自<sys/select.h>中,其中我只保留了便于理解的部分。
从定义中我们可以看出,其实fd_set
结构就是一个long型数组,或者说,它代表一种数据结构----“位图”。使用位图中对应的位来表示要监视的文件描述符。
select提供了一组操纵位图的接口:
void FD_CLR(int fd, fd_set *set); // 用来清除描述位图set中相关fd的位
int FD_ISSET(int fd, fd_set *set); // 用来测试描述位图set中相关fd的位是否为真
void FD_SET(int fd, fd_set *set); // 用来设置描述位图set中相关fd的位
void FD_ZERO(fd_set* set); // 用来清空位图set中的所有位
2.3 timeval结构说明
这个结构的定义:
/* A time value that is accurate to the nearest
microsecond but also has a range of years.
*/
struct timeval{
__time_t tv_sec; /* Second. */
__suseconds_t tv_usec; /* Microseconds. */
};
注:上面代码节选自<time.h>中,其中我只保留了便于理解的部分。
2.4 返回值说明
执行成功
- 返回文件描述符中状态已经改变的描述符个数
其他结果
- 返回0,表示在参数传入的timeout时间内没有文件描述符状态发生变化
- 返回-1,表示在执行中发生错误,错误原因存储于errno中,errno可能的结果有以下几种:
- EBADF:文件描述符无效或文件已关闭
- EINTR:此次select调用被信号打断
- EINVAL:参数n为负值
- ENOMEM:内存不足
3. 就绪条件
3.1 读就绪
- socket内核中, 接收缓冲区中的字节数, 大于等于低水位标记SO_RCVLOWAT;
- socket TCP通信中, 对端关闭连接, 此时对该socket读, 则返回0;
- 监听的socket上有新的连接请求;
- socket上有未处理的错误。
3.2 写就绪
-
socket内核中, 发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小), 大于等于低水位标记
SO_SNDLOWAT;
-
socket的写操作被关闭(close或者shutdown). 对一个写操作被关闭的socket进行写操作, 会触发SIGPIPE
信号;
-
socket使用非阻塞connect连接成功或失败之后;
-
socket上有未处理的错误。
4. 函数使用
使用select实现一个本地回显程序。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/select.h>
int main() {
fd_set read_fds;
FD_ZERO(&read_fds); //初始化fd_set结构
FD_SET(STDIN_FILENO, &read_fds); //监听标准输入
while(1){
printf("> ");
fflush(stdout);
int ret = select(STDIN_FILENO+1, &read_fds, NULL, NULL, NULL);
if(ret < 0){
perror("select");
continue;
}
if(FD_ISSET(STDIN_FILENO, &read_fds)){
char buf[1024] = {
0};
read(STDIN_FILENO, buf, sizeof(buf) - 1); //读取键盘输入
printf("Echo: %s\n", buf);
} else {
printf("error! invalid fd\n");
continue;
}
FD_ZERO(