在TCP服务器中Select实现多路IO转接

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一.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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值