linux下的I/O复用模型之select详解

  1. select函数详解
int select(int maxfdp, fd_set *readfds, fd_set *writefds, 
   			fd_set *exceptfds, struct timeval *timeout);
参数:
(1)maxfdp:	当前最大描述符数+1
(2)readfds:	指向一个套接字集合,用于检测其可读性	
(3)writefds:	指向一个套接字集合,用于检测其可写性	
(4)exceptfds:指向一个套接字集合,用于检测错误
(5)timeout:	select函数的超时时间
   结构体如下:
   struct timeval{
       time_t tv_sec;		
       time_t tv_usec;	
   };
   它可以使select处于三种状态:
   (a)若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,
   	一定等到监视文件描述符集合中某个文件描述符发生变化为止;
   (b)若将时间值设为0秒0微秒,就变成一个纯粹的非阻塞函数,
   	不管文件描述符是否有变化,都立刻返回继续执行,
   	文件无变化返回0,有变化返回一个正值;
   (c)timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,
   	超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。

fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,
每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,
建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,
由此来通知执行了select()的进程哪个句柄可读。
=============================
ps:
linux下的fd_set结构和windows下的fd_set结构还是有所不同的,
Windows系统下的fd_count和fd_array[]是不可以使用的

系统提供了以下宏可以在不同的套接字环境中提高移植性
(1)FD_CLR(s,*set):从集合set中删除套接字s
(2)FD_ISSET(s,*set):若套接字s是集合中的一员,返回值非0,否则是0
(3)FD_SET(s,*set) :将套接字s添加到集合中
(4)FD_ZERO(*set):将set集合初始化为空集NULL

  1. select模型流程(重点)
    select函数返回的是就绪socket的数量,用户需要自定义数组来保存就绪的socket并且处理。
    fd_set这个结构只是负责监听,但是要注意需要我们自己人为的将传入时的集合(inset)和传出时的集合(outset)分离开来。
    inset:代表需要监视的文件描述符,将他们置为1
    outset:代表将就绪的socket返回时依旧保留为1,其余的就是置为0
    如下图所示:
    在这里插入图片描述

  2. 客户端代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/time.h>

#define ServerIP "192.168.43.8"
#define ServerPort 27015


int main()
{
	int ret;
	char sendbuf[128];
	bzero(sendbuf,sizeof(sendbuf));
	

	struct sockaddr_in addrServer;
	bzero(&addrServer,sizeof(addrServer));
	addrServer.sin_family = AF_INET;
	addrServer.sin_port = htons(ServerPort);
	inet_pton(AF_INET,ServerIP,&addrServer.sin_addr.s_addr);
	
	//创建socket
	int clientfd = socket(AF_INET,SOCK_STREAM,0);
	if(clientfd ==-1){
		printf("socket error\n");
		return -1;
	}
	//发起连接请求
	ret = connect(clientfd,(struct sockaddr*)&addrServer,sizeof(addrServer));
	if(ret == -1){
		printf("connect error\n");
		close(clientfd);
		return -1;
	}
	
	printf("connect successful.....\n");
	
	printf("Please input sendbuf:");
	scanf("%s",sendbuf);
	
	//发送数据

	ret = send(clientfd,sendbuf,sizeof(sendbuf),0);
	if(ret > 0){
		printf("host send:%s\n",sendbuf);
	}
	close(clientfd);
	return 0;
}
  1. select模型的服务器代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/select.h>
#include<arpa/inet.h>

int main()
{
	//网络初始化
	char recvbuf[1024];
	char ip[16];
	int  ret;
	int	 maxfd;
	int  clientfdArr[1024];

	struct sockaddr_in addrserver,addrclient;
	bzero(&addrserver,sizeof(addrserver));
	bzero(&recvbuf,sizeof(recvbuf));
	bzero(&ip,sizeof(ip));

	addrserver.sin_family = AF_INET;
	addrserver.sin_port = htons(27015);
	addrserver.sin_addr.s_addr = htonl(INADDR_ANY);

	//创建socket
	int serverfd = socket(AF_INET,SOCK_STREAM,0);
	if(serverfd == -1){
		printf("create socket error\n");
		return 1;
	}
	//绑定
	ret = bind(serverfd,(struct sockaddr*)&addrserver,sizeof(addrserver));
	if(ret == -1)
	{
		printf("bind error\n");
		close(serverfd);
		return 1;
	}
	//监听
	ret = listen(serverfd,128);
	if(ret == -1)
	{
		printf("listen error\n");
		close(serverfd);
		return 1;
	}
	printf("Select Server Runing.....\n");
	
	
	//启用select模型

	fd_set inset,outset;			
	FD_ZERO(&inset);				
	FD_SET(serverfd,&inset);		//将serverfd放入readset集合中
	
	//初始化socket描述符数组
	for(int i = 0; i < 1024; ++i)
		clientfdArr[i] = -1;
	
	maxfd = serverfd;
	
	//select循环调用
	while(1)
	{
				
		outset = inset;	//将传入监听集合赋给传出监听集合
		ret = select(maxfd+1,&outset,NULL,NULL,NULL);
		if(ret > 0)
		{
			printf("select successful...\n");
			printf("就绪的数量为:%d\t事件:某客户端请求连接.....\n",ret);
			
			//serverfd就绪
			if(FD_ISSET(serverfd,&outset))
			{
				socklen_t size = sizeof(addrclient);
				int clientfd = accept(serverfd,(struct sockaddr*)&addrclient,&size);
				//将新的clientfd加入监听集合和socket描述符数组中
				FD_SET(clientfd,&inset);
				for(int i = 0; i < 1024; ++i)
					if(clientfdArr[i] == -1){
						clientfdArr[i] = clientfd;
						break;
					}
				maxfd = clientfd > maxfd ? clientfd : maxfd;
			}

			//clientfd就绪,数据传输
			else
			{
				for(int i = 0; i < 1024; ++i)
				{
					if(clientfdArr[i] != -1)
					{
						if(FD_ISSET(clientfdArr[i],&outset))
						{
							ret = recv(clientfdArr[i],recvbuf,sizeof(recvbuf),0);
							if(ret > 0)
							{
								printf("recv data is:%s\n",recvbuf);
								continue;
							}
							else if(ret == 0)
							{
								printf("client normal closed...\n");
								close(clientfdArr[i]);
								FD_CLR(clientfdArr[i],&inset);
								clientfdArr[i] = -1;
								break;
							}
							else{
								printf("recv error\n");
								close(clientfdArr[i]); 
								FD_CLR(clientfdArr[i],&inset);
								clientfdArr[i] = -1;
								break;
							}
						}
					}
				}
			}
		}
		else
		{
			printf("select error\n");
			break;
		}

	}

	close(serverfd);
	return 0;
}

  1. 测试结果
    在这里插入图片描述
    6.模型评价:
    (1)系统开销比较小,实现和维护比较方便,局域网首选
    (2)如果IO复用的时候对时间精度要求较高,select支持微妙级别,而poll,epoll只支持到毫秒级别
    (3)具有良好的兼容性,支持跨平台
    (4)在Linux内核中,select所用到的FD_SET是有限的,由参数FD_SETSIZE来决定,最多也就是1024个
    (5)在内核中采用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,即 select要检测的句柄数越多就会越费时,效率会变得很低。
    (6)select没有将传入传出分离,用户每次要自己准备传入参数,select返回的只是就绪的数量,用户需要自己取出来校验哪个socket就绪,有一定的系统开销
    (7)select函数调用时每次都会将整个监听集合拷贝给内核,内核将集合中所有的socket挂载到设备队列中(里面有大量的重复socket描述符),这种方案产生大量无意义的拷贝开销和设备挂载开销
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值