http://www.blogbus.com/eastsun-logs/7762285.html
摘自《UNIX NETWORK PROGRAMMING》chapter 6
p144
对于常见的input操作,一般分为两个步骤:
1. wait to be ready
2. copy data from kernel buffer to user buffer
常见的I/O模型:(参见以上步骤)
1. 阻塞I/O 用户进程执行1、2
2. 非阻塞I/O 用户轮巡1,然后执行2
3. 多路复用select、poll 用户调用select等待kernel返回,然后执行2
4. 信号驱动I/O(SIGIO) 用户设置信号处理函数(sigio)后,正常继续其他函数,当kernel返回SIGIO后,执行2
5. 异步信号I/O kernel执行1、2后通知用户进程(不常用)
# include <sys/select.h>
# include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
作用:
1. 内核扫描maxfdp1个描述符(常规情况下,系统最大值FD_SETSIZE=1024,可修改)
2. 内核查看readset, writeset, exceptset集中的描述符是否准备好
3. 等待超过timeout时间而没有描述符准备好,select返回
struct timeval {
long tv_sec;
long tv_usec;
}
注:timeval == 0 马上返回
timeval == NULL 永久阻塞
void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_ISSET(int fd, fd_set *fdset);
注意,如循环调用select,多次检查同一描述符,必须在调用select之前重新设定初始值。select函数会在每次返回时,将没有ready的描述符所在的位清0
任何信号将使select()出错返回。而且BSD系统的select将不可能自动再启动
在标准select()中,返回后系统不会改变timeval的值,而linux系统例外。
exception condition:
当前只支持 out-of-band和the presence of control status information to be read from the master side of a pseudo terminal that has been put into packet mode.(???)
一般的系统实现中,将fd_set设置为整数队列,每个整数元素中的一位表示为一个描述符
FD_SETSIZE定义在<sys/select.h>中,如果要更改的话,必须重新编译内核
select()的常见错误:
1. maxfdp1必须指定为最大描述符值+1
2. 每次select返回后,都会将fd_set中的初值清0,除非该描述符已经准备好。因此,如果要重新检查描述符,必须再次赋初值
在select返回的描述符个数中,如果同一描述符同时为读、写准备好,则记数2次
早期的SVR4版本只记录1次。(bug)
select中准备好的意义:
为读准备好:
1. 在socket接受缓存中的数据 >= SO_RCVLOWAT,默认情况下,SO_RCVLOWAT=1
2. 对端写关闭,read返回0
3. 在listenfd中的complete queue 中,有entry
4. socket出错。read返回-1
为写准备好:
1. 在socket发送缓存中的数据 >= SO_SNDLOWAT,默认情况下,SO_SNDLOWAT=2048
2. 对端读关闭,kernel返回SIGPIPE
3. socket出错,write返回-1
注意,当socket出错时,将在readset/writeset分别赋值
使用select应该注意:
由于同时处理单个描述符的读写,可能出现此描述符的写(或读)操作已全部完成,而相对的另一个读(或写)操作还没有完成。为了获得这些数据,进程必须调用shutdown()以进行半关闭
# include <sys/socket.h>
int shutdown(int sockfd, int howto);
其中,howto可以置为SHUT_RD/SHUT_WR/SHUTRDWR
区别于close():
1. shutdown不查看描述符计数器。直接进行半关闭
2. close只能进行全关闭,shutdown可以选择一端或两端
服务器处理客户机请求的原则:永远不要将服务器阻塞在一个客户连接中。(可能被dos攻击)
# include <sys/select.h>
# include <signal.h>
# include <time.h>
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *except_set, const struct timespec *timeout, const sigset_t sigmask);