很多时候,我们调用read函数从文件描述符中读取数据时,如果此时恰好没有数据可读,read系统调用势必会阻塞住。同样,当调用write函数时,而没有空间供我们写入,write系统调用也会被阻塞住,直到有空间被写入时。那么在这种情况下我们为了不阻塞我们的程序,就需要检查文件描述符是否可读或可写。
1. select系统调用
首先我们来看一段代码:
参数nfds必须设定为比3个文件描述符集合中的所包含的最大文件描述符还要大1,什么意思呢,假如我们只关注read,而read集合中包含两个文件描述符,它们的值分别为2、3,那么这里nfds就必须设置为大于等于3+1。
参数timeout为超时时间。
使用select系统调用的示例如下:
2. poll系统调用
poll系统调用同select系统调用类似,原型如下:
上面的代码使用poll系统调用修改如下:
1. select系统调用
首先我们来看一段代码:
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int bytes_read;
char buffer[128];
bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
if (buffer[bytes_read - 1] == '\n')
buffer[bytes_read - 1] = '\0';
printf("%s\n", buffer);
return 0;
}
上面这段代码是从标准输入端读取字符并显示出来,如果我们不输入任何字符,那么read函数是会被阻塞的,直到我们输入了任意字符并按了回车键。那么如何避免被阻塞呢,来看select系统调用,原型如下:
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
参数readfds、writefds、exceptfds都是指向文件描述符的指针,数据类型为fd_set。而readfds是用来检测输入的,writefds是用来检测输出的,exceptfds使用检测是否异常的。有关fd_set通常有四个宏供我们操作:FD_ZERO、FD_SET、FD_CLR、FD_ISSET。
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
FD_ZERO是将fd_set所指向的集合清空,FD_SET是将文件描述符fd添加到fd_set所指向的集合中,FD_CLR是将文件描述符fd从fd_set所指向的集合中移除,而如果文件描述符fd是fd_set所指向的集合中的成员,则FD_ISSET返回true,否则返回false。文件描述符集合有一个最大容量限制,由常量FD_SETSIZE来决定,在Linux上,该常量值为1024。
参数nfds必须设定为比3个文件描述符集合中的所包含的最大文件描述符还要大1,什么意思呢,假如我们只关注read,而read集合中包含两个文件描述符,它们的值分别为2、3,那么这里nfds就必须设置为大于等于3+1。
参数timeout为超时时间。
使用select系统调用的示例如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
int main(void)
{
int bytes_read, ready;
char buffer[128];
fd_set readfds;
struct timeval timeout;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
timeout.tv_sec = 10;
timeout.tv_usec = 0;
ready = select(STDIN_FILENO+1, &readfds, NULL, NULL, &timeout);
if (ready) {
bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
if (buffer[bytes_read - 1] == '\n')
buffer[bytes_read - 1] = '\0';
printf("%s\n", buffer);
} else {
printf("No data to read\n");
}
return 0;
}
如果没有数据可读,select系统调用也是会被阻塞的,直到超时为止,这里设定是的10秒。如果没有指定timeout参数,则select系统调用会永远阻塞下去。
2. poll系统调用
poll系统调用同select系统调用类似,原型如下:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd定义如下:
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
参数nfds为fds元素个数。timeout为超时时间,单位为毫秒,如果timeout等于-1,poll系统调用会一直阻塞,如果timeout等于0,则poll系统调用之检测一次并立即退出,如果timeout大于0,则poll系统调用会最多阻塞timeout毫秒,直到数据可读或可写。
上面的代码使用poll系统调用修改如下:
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
int main(void)
{
int bytes_read, ready;
char buffer[128];
struct pollfd readfds;
readfds.fd = STDIN_FILENO;
readfds.events = POLLIN;
ready = poll(&readfds, 1, 10000);
if (ready) {
bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer));
if (buffer[bytes_read - 1] == '\n')
buffer[bytes_read - 1] = '\0';
printf("%s\n", buffer);
} else {
printf("No data to read\n");
}
return 0;
}
参考教程:The Linux Programming Interface - A Linux and UNIX System Programming Handbook.pdf