多路复用
1、多路复用的优点
2、select()函数—用于探测多个文件句柄的状态变化
#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);
参数:
nfds:最大文件描述符+1
readfds:读事件的表
writefds:写事件的表
exceptfds:异常事件的表
timeout:超时检测 NULL: 一直阻塞,直到文件描述符准备好为止
返回值:
成功: 准备好的文件描述符的个数 ;失败: -1
----------------------------------------------------------
void FD_CLR(int fd, fd_set *set); // 把表中删除一个文件描述符
int FD_ISSET(int fd, fd_set *set); //检测文件描述符是否准备好了,准备好了返回1,否则返回0
void FD_SET(int fd, fd_set *set); // 加入到表中
void FD_ZERO(fd_set *set); // 清空表
基本原理:首先创建一张文件描述符表(fd_set),通过使用特有的函数(select),让内核帮助上层用户循环检测是否有可操作的文件描述符,如果有则告诉应用程序去操作。
代码实例:(有名管道的应用)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
int main(int argc, const char *argv[])
{
int fd1 = open("./f1", O_RDWR);
int fd2 = open("./f2", O_RDWR);
int fd3 = open("./f3", O_RDWR);
//1.创建一张文件描述符表
fd_set readfds, tmpfds;
FD_ZERO(&readfds); //2.清空表
FD_SET(fd1, &readfds);
FD_SET(fd2, &readfds);
FD_SET(fd3, &readfds);
int maxfd = fd3;
tmpfds = readfds;
char buf[64] = {0};
int ret, i;
while(1)
{
readfds = tmpfds;
ret = select(maxfd+1, &readfds, NULL, NULL,NULL);
if(ret == -1)
{
perror("select");
return -1;
}
for(i=fd1; i<maxfd+1; i++)
{
if( FD_ISSET(i, &readfds) )//检测是否准备好
{
read(i, buf, sizeof(buf));
printf("%s\n", buf);
memset(buf, 0, sizeof(buf));
}
}
}
close(fd1);
close(fd2);
close(fd3);
return 0;
}
代码运行:
在多路复用IO模型中,会有一个线程不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个线程就可以管理多个 socket,系统不需要建立新的进程或者线程,也不必维护这些线程和进程,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。
从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后 最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。
这种模型的特征在于每一个执行周期都会探测一次或一组事件,一个特定的事件会触发某个特定的响应。我们可以将这种模型归类为“事件驱动模型”。
相比其他模型,使用select的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多CPU,同时能够为多客户端提供服务。
如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。
但这个模型依旧有着很多问题:
当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄; 该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,那么就会导 致后续的事件迟迟得不到处理,并且会影响新的事件轮询,在很大程度上降低了事件探测的及时性。
所以select机制只适用于"短作业" 处理机制. (处理时间短)
select缺点:
- 内核使用轮询的方式来检查文件描述符集合中的描述符是否就绪,文件描述符越多,消耗的时间资源越多。
- 文件描述符集合使用的数组,有大小限制,1024
- 每次文件描述符集合更新时,重新拷贝到内核中
多复用tcp服务器:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/time.h>
int main(int argc, const char *argv[])
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接字
if(sockfd<0)
{
perror("socket");
return -1;
}
printf("sockfd=%d\n",sockfd);
//端口复用函数
int on=1;
int k=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
if(k<0)
{
perror("setsockopt");
return -1;
}
//初始化地址和端口
struct sockaddr_in seraddr;
memset(&seraddr,0,sizeof(seraddr));
int len=sizeof(seraddr);
seraddr.sin_family=AF_INET;
seraddr.sin_port=htons(9898);
//seraddr.sin_addr.s_addr=htonl(INADDR_ANY);//自动路由,寻找ip
seraddr.sin_addr.s_addr=inet_addr("0");
int ret=bind(sockfd,(struct sockaddr *)&seraddr,len);
if(ret<0)
{
perror("bind");
return -1;
}
ret=listen(sockfd,5);
if(ret<0)
{
perror("listen");
return -1;
}
fd_set readfds, tmpfds;
FD_ZERO(&readfds); //2.清空表
FD_SET(sockfd, &readfds);
int maxfd=sockfd;
tmpfds=readfds;
char buf[64] = {0};
int i;
while(1)
{
readfds = tmpfds;
ret = select(maxfd+1, &readfds, NULL, NULL,NULL);
if(ret == -1)
{
perror("select");
return -1;
}
for(i=sockfd; i<maxfd+1; i++)
{
if( FD_ISSET(i, &readfds) )//检测是否准备好
{
if(i==sockfd)
{
//建立连接,通过accept函数
int connfd=accept(i,NULL,NULL);
printf("%d is link\n",connfd);
FD_SET(connfd,&tmpfds);
if(maxfd<connfd)
{
maxfd=connfd;
}
}
else
{
ret=recv(i,buf,sizeof(buf),0);
if(ret<0)
{
close(i);
perror("recv");
return -1;
}
else if(ret==0)
{
printf("%d is unlink\n",i);
FD_CLR(i,&tmpfds);
close(i);
break;
}
else
{
printf("%d:message=%s\n",i,buf);
}
}
}
}
}
close(sockfd);
return 0;
}
运行结果: