I/O复用系统调用之select()和poll()
2016-08-09 00:13
302 查看
I/O复用是一种让进程预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(如可以读/写了),内核就通知进程。主要有select、poll和epoll三种函数支持。调用这几个函数时,不会阻塞在真正的I/O函数上(如read、write),而是阻塞在这几个系统调用上,直到指定的I/O条件就绪。
下边看看select系统调用的应用场景。
以下情况需要使用I/O复用技术:
客户端需要同时处理多个socket;
客户端需要同时处理用户输入和网络连接;
服务器需要同时处理监听socket和连接socket;
服务器需要同时处理TCP请求和UDP请求;
服务器需要同时监听多个端口;
select()函数原型:
select函数的第四个参数说的“异常”并不是说文件描述符出现了错误,在Linux上一个异常情况只会在下面两种情况下发生:
连接到处于信包模式下的伪终端主设备上的从设备状态发生了改变;
流式套接字上接收到了带外数据。
readfds、writefds和exceptions指向的结构体都是保存结果的值。由于这些结构体会在调用中被修改,如果要在循环中重复调用select(0,必须保证每次都要重新初始化它们。
socket内核接收缓冲区中字节大于等于低水位(低水位为套接字选项中的内容)
socket对方关闭连接
监听socket上有新的连接
socket上有待处理的错误
socket中发送缓冲区字节大于低水位
socket对方写操作关闭
socket上使用非阻塞connect连接成功或者失败之后
socket上有未处理的错误
单个进程可监视的fd数量被限制(FD_SETSIZE限定了select能容纳的最大描述符数,在Linux上通常为1024);
对socket采用轮询的方法;
需要维护一个用来存放大量fd的数据结构
select监听的描述符上有普通数据,标志着这个select上有可读事件;如果上边有带外数据标志着select上有异常发生。
先看一个简单的客户/服务器回射程序(未使用select),客户从标准输入stdin读入一行数据,发送给服务器,然后又从服务器读取该行数据,回显至客户的标准输出stdout。
服务器程序运行,客户端启动,输入test,回显test正常:
客户端进程阻塞于stdin,等待标准输入(example行),此时关闭服务器端程序,客户端仍然阻塞于stdin。
该客户端程序中存在一个问题:当客户程序阻塞于从标准输入fgets读数据时,服务器程序退出,
客户端无法立刻收到通知。为了解决这个问题可以通过使用select重写该客户端程序的str_cli函数,使得服务器进程一终止,客户就能立刻得到通知。
和select()函数功能类似,
poll()返回值:
-1:表示发生错误,可能是EINTR信号中断;
0:表示该调用在任意一个文件描述符称为就绪态之前超时;
正整数:表示数组fds中拥有的非零revents字段的pollfd结构体数量。
select和poll返回正整数有一些差别。若一个文件描述符在返回的描述符集合中出现了不止一次,系统调用select()会将同一个文件描述符计数多次;而poll()系统调用返回的是就绪态的文件描述符个数,且一个文件描述符只会统计一次,就算在相应的revents字段中设定了多个位掩码也是如此。
poll事件类型:
下边看看select系统调用的应用场景。
I/O复用系统调用之select()——I/O复用场景
以下情况需要使用I/O复用技术:客户端需要同时处理多个socket;
客户端需要同时处理用户输入和网络连接;
服务器需要同时处理监听socket和连接socket;
服务器需要同时处理TCP请求和UDP请求;
服务器需要同时监听多个端口;
select()函数原型:
#include <sys/select.h>
int select(int nfds, //被监听描述符总数,通常被设置为select监听的最大描述符号+1
fd_set* readfds, //接下来三个参数指向可读、可写、异常所对应描述符的集合
fd_set* writefds,
fd_set* exceptfds,
struct timeval* timeout); //设置为NULL,则阻塞直到指定描述符事件发生,或设置超时时间没有任何描述符事件就绪就返回0
timeval结构体如下:
struct timeval { //timeval的两个域都为0,则select不会阻塞,只是简单的轮询指定的描述符集合
long tv_sec; //秒数
long tv_usec; //微秒数
返回值: select成功返回就绪描述符的个数;
失败返回-1(信号中断返回-1,设置error为EINTR)
select函数的第四个参数说的“异常”并不是说文件描述符出现了错误,在Linux上一个异常情况只会在下面两种情况下发生:
连接到处于信包模式下的伪终端主设备上的从设备状态发生了改变;
流式套接字上接收到了带外数据。
FD_ZERO(fd_set *fds): 清空fds与所有文件句柄的联系。
FD_SET(int fd, fd_set *fds):建立文件句柄fd与fds的联系。
FD_CLR(int fd, fd_set *fds):清除文件句柄fd与fds的联系。
FD_ISSET(int fd, fd_set *fds):检查fds联系的文件句柄fd是否
readfds、writefds和exceptions指向的结构体都是保存结果的值。由于这些结构体会在调用中被修改,如果要在循环中重复调用select(0,必须保证每次都要重新初始化它们。
可读的条件:
socket内核接收缓冲区中字节大于等于低水位(低水位为套接字选项中的内容)socket对方关闭连接
监听socket上有新的连接
socket上有待处理的错误
可写的条件
socket中发送缓冲区字节大于低水位socket对方写操作关闭
socket上使用非阻塞connect连接成功或者失败之后
socket上有未处理的错误
select()调用的缺点:
单个进程可监视的fd数量被限制(FD_SETSIZE限定了select能容纳的最大描述符数,在Linux上通常为1024);对socket采用轮询的方法;
需要维护一个用来存放大量fd的数据结构
select实例:
select监听的描述符上有普通数据,标志着这个select上有可读事件;如果上边有带外数据标志着select上有异常发生。先看一个简单的客户/服务器回射程序(未使用select),客户从标准输入stdin读入一行数据,发送给服务器,然后又从服务器读取该行数据,回显至客户的标准输出stdout。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/mman.h>
void sig_chld(int signo)
{
pid_t pid;
int stat;
while((pid = waitpid(-1,&stat,WNOHANG)) > 0)
printf("child %d terminated\n",pid);
return;
}
void str_echo(int sockfd)
{
ssize_t n;
char buf[BUFSIZ];
again:
while((n = read(sockfd, buf, BUFSIZ)) > 0)
write(sockfd,buf,n);
if(n < 0 && errno == EINTR)
goto again;
else if(n < 0)
printf("str_echo:%m\n"),exit(0);
}
int main(int argc, char **argv)
{
int listenfd,connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
void sig_chld(int);
listenfd = socket(AF_INET,SOCK_STREAM, 0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("192.168.234.129");
servaddr.sin_port = htons(13000);
bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
listen(listenfd,3);
signal(SIGCHLD,sig_chld);
for(;;) {
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen)) < 0) {
if(errno == EINTR)
continue;
else
printf("accept: %m\n"),exit(1);
}
if((childpid = fork()) == 0) {
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
}
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
void str_cli(FILE *fp, int sockfd)
{
char sendline[BUFSIZ],recvline[BUFSIZ];
while(fgets(sendline,BUFSIZ,fp) != NULL) {
write(sockfd,sendline,strlen(sendline));
if(read(sockfd,recvline,BUFSIZ) == 0)
printf("readline:%m\n"),exit(1);
fputs(recvline,stdout);
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(13000);
inet_aton("192.168.234.129",&servaddr.sin_addr);
connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
str_cli(stdin,sockfd);
exit(0);
}
服务器程序运行,客户端启动,输入test,回显test正常:
客户端进程阻塞于stdin,等待标准输入(example行),此时关闭服务器端程序,客户端仍然阻塞于stdin。
该客户端程序中存在一个问题:当客户程序阻塞于从标准输入fgets读数据时,服务器程序退出,
客户端无法立刻收到通知。为了解决这个问题可以通过使用select重写该客户端程序的str_cli函数,使得服务器进程一终止,客户就能立刻得到通知。
void str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
fd_set rset;
char buf[BUFSIZ];
int n;
stdineof = 0;
FD_ZERO(&rset);
for(;;) {
if(stdineof == 0)
FD_SET(fileno(fp),&rset); //fileno函数把标准I/O文件指针转换为对应的
描述符
FD_SET(sockfd,&rset);
if(fileno(fp) > sockfd)
maxfdp1 = fileno(fp) + 1;
else
maxfdp1 = sockfd + 1;
select(maxfdp1, &rset, NULL, NULL, NULL);
if(FD_ISSET(sockfd,&rset)) {
if((n = read(sockfd, buf, BUFSIZ)) == 0) {
if(stdineof == 1){
printf("stdineof == 1\n");
return; }
else
printf("read:%m\n"),exit(1);
}
write(fileno(stdout), buf, n);
}
if(FD_ISSET(fileno(fp),&rset)) {
if((n = read(fileno(fp), buf, BUFSIZ)) == 0) {
stdineof = 1;
shutdown(sockfd, SHUT_WR);
FD_CLR(fileno(fp),&rset);
continue;
}
write(sockfd, buf, n);
}
}
}
poll()系统调用
和select()函数功能类似,#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
fds为pollfd结构体数组,定义如下:
struct pollfd {
int fd; //文件描述符
short events; //注册的事件,一系列事件按位或实现
short revents; //实际发生的事件,由内核填充,通知应用程序发生了哪些事件,若对某个描述符不感兴趣可将该字段设置为0
}
nfds指定了数组fds中元素的个数(nfds_t 为无符号整型);
timeout参数同select()函数;
poll()返回值:
-1:表示发生错误,可能是EINTR信号中断;
0:表示该调用在任意一个文件描述符称为就绪态之前超时;
正整数:表示数组fds中拥有的非零revents字段的pollfd结构体数量。
select和poll返回正整数有一些差别。若一个文件描述符在返回的描述符集合中出现了不止一次,系统调用select()会将同一个文件描述符计数多次;而poll()系统调用返回的是就绪态的文件描述符个数,且一个文件描述符只会统计一次,就算在相应的revents字段中设定了多个位掩码也是如此。
poll事件类型:
相关文章推荐
- Mysql IO 内存方面的优化
- Node.js 的异步 IO 性能探讨
- SQL Server误区30日谈 第22天 资源调控器可以调控IO
- mysql 数据同步 出现Slave_IO_Running:No问题的解决方法小结
- java中的Io(input与output)操作总结(一)
- GO语言的IO方法实例小结
- java中的Io(input与output)操作总结(四)
- C#路径,文件,目录及IO常见操作汇总
- SQL语句实现查询当前数据库IO等待状况
- Java进阶教程之IO基础
- java中的Io(input与output)操作总结(三)
- Java 1.0和Java 1.1 的IO类的比较
- Java中的BufferedInputStream与BufferedOutputStream使用示例
- JAVA IO API使用详解
- Java NIO和IO的区别
- Java编程中最基础的文件和目录操作方法详解
- Python中使用select模块实现非阻塞的IO
- Python通过poll实现异步IO的方法
- Python通过select实现异步IO的方法
- Java编程中字节流与字符流IO操作示例