提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一.Select原理及优缺点
系统调用 select()可用于执行 I/O 多路复用操作,调用 select()会一直阻塞,直到某一个或多个文件描述
符成为就绪态(可以读或写)。在TCP服务器中它的作用就相当于“秘书”,当监听的客户端中有一个有事件发生,这个秘书再转交这个客户端给“老板”处理,而不用“老板“都对每一个监听的客户端进行处理,从而提高效率
缺点:监听上限受文件描述符。最大1024.
优点:跨平台
二、先看代码
//**************多路io转接——select************
#include <stdio.h>
#include "warp.h"
#include <sys/time.h>
#include <sys/select.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
int lfd = tcp4bind(8888,NULL);
int max_fd = lfd;
fd_set r_set;
fd_set old_set;
int nready = 0;
struct sockaddr_in cliaddr;
char ip[16] = "";
socklen_t len = sizeof(cliaddr);
int cfd;
char buf[1024] = "";
int n;
listen(lfd,128);
FD_ZERO(&old_set);
FD_ZERO(&r_set);
FD_SET(lfd,&old_set);
while(1)
{
r_set = old_set;
nready = select(max_fd+1,&r_set,NULL,NULL,NULL);
if(nready < 0){
perror("");
break;
}
else if(nready >= 0)
{
//lfd already change?
if(FD_ISSET(lfd,&r_set))
{
cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);
printf("client ip=%s port=%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),
ntohs(cliaddr.sin_port));
FD_SET(cfd,&old_set);
if(max_fd < cfd)
max_fd = cfd;
if(--nready == 0)
continue;
}
for(int i=lfd+1 ;i<=max_fd ;i++)
{
if(FD_ISSET(i,&r_set))
{
n = Read(i,buf,sizeof(buf));
if(n == 0)
{
printf("client close\n");
close(i);
FD_CLR(i,&old_set);
}
else if(n < 0)
{
perror("**********");
close(i);
FD_CLR(i,&old_set);
continue;
}
else
{
printf("%s\n",buf);
Write(i,buf,n);
}
}
}
}
}
return 0;
}
这里有部分函数经过了封装处理,如果有需要可以看我之前的博客,但是并不会影响select实现多路IO转接。
三、简要流程
//创建套接字
lfd=socket();
//绑定地址结构
bind();
//设置监听上限
listen();
//创建读集合
fd_set rset,allset;
//读集合清零
FD_ZERO(&allset);
//将lfd添加至读集合中
FD_SET(lfd,&allset);
while(1)
{
//使用select进行监听
rset=allset;
ret=select(lfd+1,&allset,NULL,NULL,NULL); //最后一个参数是监听的时间,当然可以选择不阻塞监听
//判断ret返回值
if(ret>0)
{
if(FD_ISSET(lfd,&rset)) //判断是否在集合中,说明有新的客户端链接请求
{
cfd=accept();
FD_SET(cfd,&allset);
}
for(i=lfd+1;i<最大文件描述符;i++) //轮询
{
FD_ISSET(i,&rset)
//接下来就是read(),write()这些操作了
}
}
}
//
四.使用数组提高效率
究竟为什么使用数组能够提高效率呢,其实看上面的代码就可以知道遍历最大文件描述符+1的范围其中有效的只有几个,此时效率就显得很低了,假设把有效的文件描述符放到一个数组里面,首先把数组的值全部置为-1,如果有一个客户端接入就把从0开始的第一个-1替换成这个客户端的描述符,如果再有客户端接入就把第二个-1变为第二个客户端的描述符,以此类推,此时我们只需要遍历这个数组中不为-1的部分就可以了,这样效率就得到了提高,当让这个数组最大数也不会超过1024,不然就会出错。
直接看代码
//**************多路io转接——select************
#include <stdio.h>
#include "warp.h"
#include <sys/time.h>
#include <sys/select.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
//添加数组提高效率
int i,j,n,maxi,sockfd;
int client[FD_SETSIZE]; //自定义数组防止遍历1024,提高效率
int lfd = tcp4bind(8888,NULL);
int max_fd = lfd;
maxi=-1; //数组初始化 -1为元素下表
for (i = 0; i < 1024; i++)
{
client[i]=-1;
}
fd_set r_set;
fd_set old_set;
int nready = 0;
struct sockaddr_in cliaddr;
char ip[16] = "";
socklen_t len = sizeof(cliaddr);
int cfd;
char buf[1024] = "";
listen(lfd,128);
FD_ZERO(&old_set);
FD_ZERO(&r_set);
FD_SET(lfd,&old_set);
while(1)
{
r_set = old_set; //r_set是满足监听条件的集合,old_set是所有的集合
nready = select(max_fd+1,&r_set,NULL,NULL,NULL);
if(nready < 0){
perror("");
break;
}
else if(nready >= 0)
{
//lfd already change?
if(FD_ISSET(lfd,&r_set))
{
cfd = Accept(lfd,(struct sockaddr*)&cliaddr,&len);
printf("client ip=%s port=%d\n",inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,ip,16),
ntohs(cliaddr.sin_port));
/添加数组部分
for(i = 0;i<1024;i++)
{
if(client[i]<0) //找位置
{
client[i]=cfd;
break;
}
}
if(i==1024) //数组爆表
{
fputs("too many clients\n",stderr);
exit(-1);
}
FD_SET(cfd,&old_set); //添加cfd到监听合集
if(max_fd < cfd)
max_fd = cfd;
if(i>maxi) //保证maxi存的总是client[]最后一个元素下标
maxi=i;
if(--nready == 0) //只有lfd有事件则后续的for就不需要执行了
continue;
}
for(i=0 ;i<=maxi ;i++) //遍历至文件描述符为-1开始处
//for(int i=lfd+1 ;i<=max_fd ;i++)
{
if((sockfd=client[i])<0)
continue;
if(FD_ISSET(sockfd,&r_set))
{
n = Read(sockfd,buf,sizeof(buf));
if(n == 0) //判断关闭
{
printf("client close\n");
close(sockfd);
FD_CLR(sockfd,&old_set);
client[i]=-1; //释放文件描述符号
}
else if(n < 0)
{
perror("**********");
close(i);
FD_CLR(i,&old_set);
continue;
}
else if(n > 0)
{
printf("nnnnnnnnnnnnnnnnnnn");
Write(sockfd,buf,n);
Write(STDOUT_FILENO,buf,n);
}
if(--nready == 0)
break;
}
}
}
}
close(lfd);
return 0;
}