1. 多路复用I/O
1)应用程序处理多路输入输出流,若采用阻塞模式,将得不到预期的目的;
2)若采用阻塞模式,对多个输入进行轮询,会非常浪费CPU时间;
3)若设置多个进程,分别处理一条数据通路,将产生进程间的同步与通信问题,使程序变得非常复杂;
4)比较好的方法是使用I/O多路复用,其基本思想:
先构造一张有关描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作。
2. select()/poll()实现多路复用
头文件:#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
函数原型:int select(int nfds, fd_set *readfds,fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
头文件:#include <poll.h>
函数原型:int poll(struct pollfd *fds, nfds_tnfds, int timeout);
注:本博文只学习select函数,poll函数用法select函数类似。
参数:
maxfd
所有监控的文件描述符中最大的那个加1
read_fds
所有要读的文件描述符的集合
write_fds
所有要写的文件描述符的集合
except_fds
其他要向我们通知的文件描述符
Timeout
超时设置
NULL: 一直阻塞就,直到有文件描述符就绪或出错
时间值为0:仅仅检测文件描述符的状态,然后立即返回,非阻塞模式
时间值不为0:在指定的时间内,如果没有事件发生,则超时返回。
3. select函数特性
在调用select函数时进程会一直阻塞直到以下某种情况发生:
1)有文件可以读
2)有文件可以写
3)超时所设置的时间到了
为了设置文件描述符,用到以下几个函数:
void FD_CLR(int fd, fd_set *set);将fd从fdset里面清除
int FD_ISSET(int fd, fd_set *set);判断fd是否在fdset集合中
void FD_SET(int fd, fd_set *set);将fd加入到fdset
void FD_ZERO(fd_set *set);从fdset中清除所有的文件描述符
4. 从具体例程中学习select函数应用
/*************************************************************************
@Author: wanghao
@Created Time : Fri 29 May 2018 00:00:00 PM PDT
@File Name: server.c
@Description:
************************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/select.h>
#define N 36
#define MAXSIZE 24
#define NUM 24
int main(int argc, const char *argv[])
{
intsocketfd;
intclient_fd;
intlistenfd;
intmaxfd;
intdatalen;
intaddrlen;
inti;
fd_setrfds;
fd_settempfd;
u_shortport;
pid_tpid;
charbufread[MAXSIZE] = {0};
charbufwrite[MAXSIZE] = {0};
structsockaddr_in my_addr;
structsockaddr_in client_addr;
if(argc< 3)
{
printf("usage:%s <IPadd> <Port>\n",argv[0]);
return-1;
}
listenfd= socket(PF_INET, SOCK_STREAM, 0);
if(socket< 0)
{
perror("");
return-1;
}
printf("socket= %d\n",listenfd);
port= (u_short)atol(argv[2]);
printf("port= %d\n", port);
my_addr.sin_family= PF_INET;
my_addr.sin_port= htons(port);
my_addr.sin_addr.s_addr= inet_addr(argv[1]);
addrlen= sizeof(struct sockaddr_in);
if(bind(listenfd,(struct sockaddr *)(&my_addr),addrlen) < 0)
{
perror("");
return-2;
}
listen(listenfd,N);
/*初始化rfds文件描述符集合*/
FD_ZERO(&rfds);
/*初始化tempfd文件描述符集合*/
FD_ZERO(&tempfd);
/*将listenfd加入rfds文件描述符集合中*/
FD_SET(listenfd,&rfds);
/*初始化最大文件描述符*/
maxfd= listenfd;
while(1)
{
/*因为在文件描述符集合中,select每执行一次,文件描述符就会减少一个,所以必须定义一个临时变量,用于select。比如文件描述符集合中有文件描述符3、4、5,当3代表的进程发生时,文件描述符集合会将3去掉此时剩下4、5。在下一次循环中,如何保证文件描述符集合还是包括3,4,5是完整的,最有效的方法就是定义一个临时文件描述符集合*/
tempfd= rfds;
if(select(maxfd+1,&tempfd, NULL, NULL, NULL) <= 0)
{
perror("failto select");
return-3;
}
/*查找是哪个文件描述符促发select执行*/
for(i= 0; i <= maxfd; i++)
{
if(FD_ISSET(i,&tempfd))
{
/*是否是listenfd,即又有客户端要连接进来*/
if(i== listenfd)
{
if((client_fd= accept(listenfd,(struct sockaddr *)(&client_addr),&addrlen)) < 0)
{
perror("");
return-4;
}
/*如果有客户端连接进来,首先将它加入文件描述符集合中*/
FD_SET(client_fd,&rfds);
/*如果新的客户端fd大于maxfd,则用它替代maxfd*/
if(client_fd> maxfd)
{
maxfd= client_fd;
}
}
/*肯定是客户端发送消息过来*/
else
{
datalen= read(i, bufread, sizeof(bufread));
if(datalen<= 0)
{
close(i);
continue;
}
bufread[datalen]= '\0';
printf("%s\n",bufread);
}
}
}
}
close(listenfd);
return0;
}