接下来我们讨论3篇关于I/O多路复用的问题,首先我们来看下select,select是多路复用当中最早的一种
I/O复用的最主要的功能就是让程序能够同时去监听多个文件描述符,这样程序的性能就能提高。
select介绍
我们先来看一下select的接口。
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
从上面的这些接口我们应该能有写认识,首先我么来看select系统调用的参数的含义。
参数 | 功能 |
---|---|
nfds | 被监听的文件描述符的总数。通常是文件描述符最大值加1 |
readfds | 可读事件的文件描述符集合 |
writefds | 可写事件的文件描述符集合 |
exceptfds | 异常事件的文件描述符集合 |
timeout | 设定超时时间 |
值得注意的是,后面的三个参数即是输入型参数,又是输出型参数,输入表示关心那些文件描述符对应的特定事件发生。输出表示的是那些文件描述符对应的事件就绪。当就绪后,内核将会去修改这些文件描述符的集合。这三个参数都是fd_set结构体类型
typedef struct
{
/*XPG4.2requiresthismembername.Otherwiseavoidthename
fromtheglobalnamespace.*/
#ifdef__USE_XOPEN
__fd_maskfds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->fds_bits)
#else
__fd_mask__fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->__fds_bits)
#endif
}fd_set;
可以看出fd_set就是一个结构体数组,这个结构体数组的每一位就是一个文件描述符的标记,配套提供了一些对于fd_set操作的宏。
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
宏 | 功能 |
---|---|
FD_CLR | 进行对应位fd |
FD_ISSET | 进行判断对应位fd |
FD_SET | 设置fd的对应位置 |
FD_ZERO | 进行清空fd_set |
最后要说一下的就是timeout参数,这个参数用来设置超时时间,它也是一个结构体,它用来告诉应用程序select等待多久,这里的单位是微秒级别的。
timeout参数 | 说明 |
---|---|
0 | 立即返回,即轮询 |
NULL | 阻塞监视文件描述符,当有时间就绪才返回 |
大于0的时间 | 超时时间设置 |
select调用时内核级别的,select的轮询方式是和非阻塞轮询方式是不同的,select的轮询方式是同时可以对多个I/O端口进行监听,任何一个端口数据好了,这个时候就可以读了。然后通过系统调用,就可以把数据从内核拷贝到用户进程。
select缺点
1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。
一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max查看,有宏FD_SETSIZE进行限制fd的数量。32位机默认是1024个。64位机默认是2048.
2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:
当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
select示例
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
//设定一个select数组
#define __SIZE__ 64
int gfds[__SIZE__];
int startup(int port,char *ip)
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0)
{
perror("socket");
exit(2);
}
//int opt = 1;
//setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(sock,(struct sockaddr *)&local,sizeof(local)) < 0)
{
perror("bind");
exit(3);
}
if(listen(sock,5) < 0)
{
perror("listen");
exit(4);
}
return sock;
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("Usage : %s [local_ip] [local_port]\n",argv[0]);
return 5;
}
//创建监听套接字
int listen_sock = startup(atoi(argv[2]), argv[1]);
int i=0;
//让记录套接字的数组初始化为-1.
for(;i < __SIZE__;i++)
{
gfds[i] = -1;
}
while(1)
{
//监听套接字插入gfds
gfds[0] = listen_sock;
//定义max最终在nfds使用
int max_fd = -1;
//创建文件描述符集
fd_set rfds;
//对文件描述符集进行初始化
FD_ZERO(&rfds);
//将监听套接字设置在文件描述符集合当中
FD_SET(listen_sock,&rfds);
//进行查找最大的max_fd, 为了后续操作nfds
int k = 0;
for(;k < __SIZE__;k++)
{
if(gfds[k] != -1)
{
//记得最大的文件描述符的内容和gfds内容进行比较
if(gfds[k] > max_fd)
{
max_fd = gfds[k];
}
FD_SET(gfds[k],&rfds);
}
}
//设置timeout值
struct timeval timeout={5,0};
//进行select I/O多路复用
switch(select(max_fd+1,&rfds,NULL,NULL,&timeout))
{
case -1:
{
perror("select");
break;
}
break;
case 0:
{
printf("time out\n");
continue;
}
break;
default:
{
int j = 0;
for(;j < __SIZE__;j++)
{
if(gfds[j] == -1)
{
//continue;
break;
}
else if(FD_ISSET(gfds[j],&rfds) && gfds[j] == listen_sock)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock =accept(listen_sock,\
(struct sockaddr *)&peer,&len);
printf("client :ip:%s,port:%d\n",\
inet_ntoa(peer.sin_addr),\
ntohs(peer.sin_port));
printf("sock: %d\n",sock);
if(sock < 0)
{
perror("accept");
continue;
}
else
{
int m = 0;
for(;m < __SIZE__;m++)
{
if(gfds[m] == -1)
{
gfds[m] = sock;
FD_SET(gfds[m],&rfds);
break;
}
}
if(m == __SIZE__)
{
close(sock);
printf("too many client\n");
}
}
}
else if(FD_ISSET(gfds[j],&rfds))
{
char buf[1024];
ssize_t _r = read(gfds[j],buf,sizeof(buf)-1);
if(_r > 0)
{
buf[_r] = 0;
printf("client echo # %s\n",buf);
}
else if(_r == 0)
{
printf("client is closed\n");
close(gfds[i]);
gfds[j] = -1;
continue;
}
else
{
perror("read");
return 5;
}
}
}
break;
}
break;
}
}
close(listen_sock);
return 0;
}