I/O复用——select函数

 1、I/O复用:解决进程或线程阻塞到某个 I/O 系统调用而出现的技术,使进程不阻塞于某个特定的 I/O 系统调用。

2、I/O复用适用于:
a.  客户端程序要同时处理多个socket。

b.  客户端程序要同时处理用户输入和网络连接。

c.  TCP服务器既要处理监听套接字,又要处理已连接套接字。

d.  服务器要同时处理TCP和UDP请求。

e.  服务器要监听多个窗口或者处理多种服务
I/O复用常用函数:select、poll、epoll

3、select()函数

系统调用原型:

#include
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *execptfds,struct timeval *timeout); 
功能:轮询扫描多个描述符中的任一描述符是否发生响应,或经过一段时间后唤醒。

参数:

a.  nfds: 指定要检测的描述符的范围,所检测描述符最大值+1
b.  readset:可读描述符集,监测该集合中的任意描述符是否有数据可读
c.  writeset:可写描述符集,监测该集合中的任意描述符是否有数据可写
d.  exceptset:异常描述符集,监测该集合中的任意描述符是否发生异常
e.  timeout:超时时间,超过规定时间后唤醒
返回值为负值表示select错误,正值表示某些文件可读写或出错 ,0表示等待超时,没有可读写或错误的文件

以下为服务器端select.c与多个客户端cli.c通过I/O复用单线程建立连接的程序。

select.c

#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#include   
#define MAXFD 10  
void fds_init(int fds[])//数组初始化
{  
    int i = 0;  
    for(; i < MAXFD; i++)  
    {  
        fds[i] = -1;  
    }  
}  
void fds_add(int fds[],int fd)//添加元素到数组
{  
    int i = 0;  
    for(; i < MAXFD;i++)  
    {  
        if(fds[i] == -1)  
        {  
            fds[i] = fd;  
            break;  
        }  
    }  
}  
void fds_del(int fds[],int fd)//逻辑上移除一个元素 
{  
    int i = 0;  
    for(; i < MAXFD ; i++)  
    {  
        if(fds[i] == fd)  
        {  
            fds[i] = -1;  
            break;  
        }  
    }  
}  
int main()  
{  
    int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建套接字,
    assert(sockfd != -1);  
          
    struct sockaddr_in saddr,caddr;  
    memset(&saddr,0,sizeof(saddr));  
    saddr.sin_family = AF_INET;  
    saddr.sin_port = htons(6000);  
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
               
    int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));  
    assert(res != -1);  
      
    listen(sockfd,5);  
  
    int fds[MAXFD];//定义一个要添加元素的数组
    fds_init(fds);  
    fds_add(fds,sockfd);//添加唯一一个有效的元素sockfd  
      
    while(1)  
    {  
        fd_set fdset;//定义集合  
        FD_ZERO(&fdset;//将集合清空,每个位置置为0 
        //循环添加元素到集合 
        int maxfd = -1;//定义描述符最大值  
        int i = 0;  
        for(;i < MAXFD;i++)//找描述符最大值  
        {  
            if(fds[i] == -1)//无效描述符  
            {  
                continue;  
            }  
            //有效就添加  
            FD_SET(fds[i],&fdset);  
            if(maxfd < fds[i])  
            {  
                maxfd = fds[i];  
            }  
        }  
          
        struct timeval tv = {5,0};//设置超时时间  
        //把集合交给select处理  
        int n = select(maxfd+1,&fdset,NULL,NULL,&tv);  
        //maxfd+1:描述符最大值+1,表示告诉select只需要关注前maxfd+1个就好了,
        //后面的不用关注,因为未用到;
        //fdset:读事件集合(对方发数据);写;异常;tv置为空,则表示永久阻塞
        if(n == -1)//失败  
        {  
            perror("select error\n");  
            continue;  
        }  
        else if(n == 0)//超时,5s时间到,没有生成就绪队列或者无客户发数据  
        {  
            printf("time out\n");  
            continue;  
        }
        //到此只知道有几个文件描述符,但是不清楚是哪个描述符上的数据已就绪  
        else  
        {  
            int i = 0;  
            for(;i < MAXFD;i++)//遍历整个数组  
            {  
                if(fds[i] == -1)//无效描述符  
                {  
                    continue;  
                }  
                //判断描述符上有没有数据就绪,如果为真,有数据就绪,就处理  
                if(FD_ISSET(fds[i],&fdset)) 
                {  
                    if(fds[i] == sockfd)  
                    {  
                        int len = sizeof(caddr);  
                        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);  
                        if(c < 0)  
                        {  
                            continue;  
                        }  
                        printf("accept c = %d\n",c);
                        //每建立一个新连接就会增加一个新的描述符,则将新得到的添加到数组中,
                        //在下次select循环时集合中就会多个,方便处理 
                        fds_add(fds,c);  
                    }  
                    else  
                    {  
                        char buff[128] = {0};  
                                                int num = recv(fds[i],buff,127,0);  
                        if( num<= 0)//对方关闭了  
                        {  
                            close(fds[i]);  
                            fds_del(fds,fds[i]);//移除  
                            printf("one client over\n");  
                              
                        }  
                        else  //说明读到了num个数据
                        {  
                            printf("recv(%d)=%s\n",fds[i],buff);  
                            send(fds[i],"OK",2,0);  
                        }  
                    }  
                }  
            }  
        }  
    }  
}  

cli.c 

运行结果:

I/O复用——select函数_第1张图片

例:程序main.c关注键盘,有数据输入就读取打印,没有数据输入就timeout。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define STDIN 0
 
int main()
{
	int fd = STDIN;
	char buff[128] = {0};
	while(1)
	{

        fd_set fdset;
		FD_ZERO(&fdset);
		DS_SET(fd, &fdset);
 
		struct timeval tv = {5, 0};
		int n = select(STDIN+1, &fdset, NULL, NULL, &tv);
		if(n < 0)
		{
            
			continue;
		}
		else if(n == 0)
		{
			printf("time out\n");
			continue;
		}
		else
		{
			if(FD_ISSET(fd,&fdset))
            {
                 read(fd, buff, 127);
                 if(strncmp(buff,"end",3)==0)
                 {
                     break;
                 }
                 printf("read = %s\n", buff);
            }
            
		}
	}
}
 

运行结果:

I/O复用——select函数_第2张图片

你可能感兴趣的:(Linux)