本文参考《Unix高级环境编程》,仅用于个人学习,备忘。
1. sample 学习代码
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/select.h>
#define MAXLINE 500000
void set_fl(int, int);
void clr_fl(int, int);
int
main(void)
{
char buf[MAXLINE];
int ntowrite, nread, nwrite;
int val;
char *ptr;
fd_set rset, wset;
set_fl(STDIN_FILENO, O_NONBLOCK); /* 将标准输入设置为非阻塞模式 */
ntowrite = 0;
ptr = buf;
FD_ZERO(&rset); /* 清空读fd */
FD_SET(STDIN_FILENO, &rset); /* 设置标准输入 */
while (ntowrite < MAXLINE) {
if (select(4, &rset, NULL, NULL, NULL) < 0) {
fprintf(stderr, "select error\n");
exit(1);
}
if (!FD_ISSET(STDIN_FILENO, &rset))continue; /* 直到标准输入准备好 */
nread = read(STDIN_FILENO, ptr, MAXLINE-ntowrite);
fprintf(stderr, "read: %d, errno: %d, %s\n",
nread, errno, strerror(errno));
if (nread == 0) /* 遇到结束符号 */
break;
else if (nread > 0) {
ntowrite += nread;
ptr += nread;
}
}
clr_fl(STDIN_FILENO, O_NONBLOCK); /* 将标准输入恢复为阻塞模式 */
set_fl(STDOUT_FILENO, O_NONBLOCK); /* 将标准输出设置为非阻塞模式 */
ptr = buf;
FD_ZERO(&wset);
FD_SET(STDOUT_FILENO, &wset);
while (ntowrite > 0) {
errno = 0;
if (select(4, NULL, &wset, NULL, NULL) < 0) {
fprintf(stderr, "select error, %s\n", strerror(errno));
exit(3);
}
if (!FD_ISSET(STDOUT_FILENO, &wset)) continue; /* 直到标准输出可以写入 */
nwrite = write(STDOUT_FILENO, buf, ntowrite);
fprintf(stderr, "nwrite: %d, errno: %d, %s\n",
nwrite, errno, strerror(errno));
if (nwrite > 0) {
ptr += nwrite;
ntowrite -= nwrite;
}
}
clr_fl(STDOUT_FILENO, O_NONBLOCK); /* 将标注输出恢复为阻塞模式 */
exit(0);
}
void set_fl(int fd, int flag)
{
int val;
if ((val = fcntl(fd, F_GETFL, 0)) < 0) {
fprintf(stderr, "GETFL error\n");
exit(1);
}
val |= flag;
if (fcntl(fd, F_SETFL, val) < 0) {
fprintf(stderr, "SETFL error\n");
exit(1);
}
}
void clr_fl(int fd, int flag)
{
int val;
if ((val = fcntl(fd, F_GETFL, 0)) < 0) {
fprintf(stderr, "GETFL error\n");
exit(1);
}
val &= ~flag;
if (fcntl(fd, F_SETFL, val) < 0) {
fprintf(stderr, "SETFL error\n");
exit(1);
}
}
当将标准输入(或标准输出)设置为非阻塞模式, 并且未使用select等IO多路转接时,对它们进行读(或写)会立即返回。如果无法读取(或写入),那么read(或write)会返回-1,同时errno设置为错误代码。如下
read: -1, errno: 11, Resource temporarily unavailable
nwrite: -1, errno: 11, Resource temporarily unavailable
但使用select时,则程序会阻塞在select上,直到标准输入或标准输出准备就绪才返回。
2. select的用法说明
头文件 <sys/select.h>
第一个参数:int maxfdp1,说明需要检查的描述符数量(文件描述符号从0开始),所以这个值通常是(读,写,异常)三个中最大fd的值再加1。也可以设置为FD_SETSIZE,这个常量通常是1024,对大多数应用程序而言太大了,多数应用程序只是用3~10个。
第二,第三,第四个参数readfds, writefds, exceptfds,指向描述符号集的指针。通常用以下方法设置:
fd_set rset;
FD_ZERO(&rset);
FD_SET(target_fd, &rset);
FD_SET(STDIN_FILENO, &rset);
从select返回后,使用FD_ISSET检查
if (FD_ISSET(target_fd, &rset)) {
...
}
最后一个参数, struct timeval *restrict tvptr,当为NULL时, 永远等待。如果捕捉到一个信号则中断,select返回-1,errno设置为EINTR。当不为零时,则等待指定的时间。如果在超时时还没有一个描述符准备好,则返回值为0。其他和情况一相同。当为零时,则完全不等待。立即返回。这是得到多个描述符的状态而不阻塞select函数的轮询方法。
一个描述符阻塞与否并不影响select是否阻塞。
如果在一个描述符上碰到文件结尾处,则select认为该描述符时可读的。然后调用read,它返回0。
注:如果select同时监听多个fd,当返回时,可能只有部分fd是准备好了的,部分fd是没准备好的。如果这是在一个循环之内,则下一次循环,那些没准备好的fd就相当于是被clear了,不会再被检测。