I/O复用之select

函数功能:在一段时间内,监听用户感兴趣的文件描述符上的可读、可写与异常事件

函数原型:int select(int nfds,struct fd_set *readfds, struct fd_set *writefds,struct fd_set *execptfds,struct timeval * timeout)

nfds:要监听的最大文件描述符加1; readfds:监听用户感兴趣的文件描述符上的可读事件

writefds:监听用户感兴趣的文件描述符上的可写事件 execptfds:监听用户感兴趣的文件描述符上的异常事件

timeout:select函数的超时时间

fd_set结构体只包含一个整型数组,并且数组大小为32,用数组中每个元素的每个位对应一个文件描述符;

内核中关于此结构体的定义:

#define FD_SETSIZE 1024

#include <sys/select.h>

#define FD_SETSIZE _FD_SETSIZE 
typedef long int _fd_maksk;

#undef _NFDBITS
#define  _NFDBITS (8 *(int)sizeof(_fd_mask))  //32
typedef struct 
{
   #ifdef _USE_XOPEN       1024          32
     _fd_maksk _fds_bits[_FD_SETSIZE /_NFDBITS ]
   #define _FDBITS(set)  ((set)->fds_bits)
   #else 
     _fd_maksk _fds_bits[_FD_SETSIZE /_NFDBITS ]
     #define _FDBITS(set)  ((set)->fds_bits)
#endif
}fd_set;

由于fd_set表示的特性,表示最多可监听文件描述符的个数为1024个,最大文件描述符值为1023,因为从0开始

并且select提供了设置位的宏函数访问fd_set

FD_ZERO(fd_set* fdset)  清空fdset的所有位

FD_SET(int fd,fd_set* fdset)  设置fdset的fd位

FD_CLR(int fd,fd_set* fdset)  清除fdset的fd位

int FD_ISSET(int fd,fd_set* fdset)  测试fdset的位fd是否被设置

比如:

FD_SET(5,&redset);则readset的第五位被置为1

FD_SET(32,&redset);则readset的第32位被置为1

函数功能实现过程

(1)首先select函数的三个fd_set类型参数会将自己感兴趣的文件描述符注册到对应的结构中

(2)调用select系统调用,这个三个结构被传给内核程序,内核程序轮询查找三个结构体中的文件描述符是否有就绪事件,实践复杂度为O(n);并且直接修改这3个结构上的内容来通知应用程序哪些文件描述符已经就绪,最后内核程序将这三个结构返回给应用程序

(3)应用程序拿到返回的结构之后采用轮询法遍历这三个结构,找到就绪的文件描述符,此查找过程时间复杂度为O(n);

select函数的缺点

1.从函数原型看,只能监听可读、可写、异常三种事件;

2.内核程序和应用程序都需要才有轮询法查找就绪文件描述符,时间复杂度为O(n);

3.内核程序是直接修改传入的结构体中的内容,所以下一次调用时又必须重新设置结构体;

4.从内核定义来看,单个进程所打开的FD是有一定限制的,它由FD_SETSIZE设置,默认值是1024;

具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.  所以在不同位的系统下,能打开的文件描述符个数也不相同;

5.只支持LT模式

select函数简单应用代码:

void addfds(int* fds,int c)
{
	int i = 0;
	for(; i < 128;++i)
	{
		if(fds[i] == -1)
		{
			fds[i] = c;
			break;
		}
	}
}
void delfds(int*fds,int c)
{
	int i = 0;
	for(; i < 128 ;++i)
	{
		if(fds[i] == c)
		{
			fds[i] = -1;
			break;
		}
	}
}
void main()
{
	int listen_fd = socket(AF_INET,SOCK_STREAM,0);//创建监听套接字
	assert(listen_fd != -1);
struct sockaddr_in ser,cli;//在绑定函数中需要的结构体,用来记录客户端的iip地址和端口号
	ser.sin_family = PF_INET;//地址族:TCP/IP
	ser.sin_port = htons(6000);//将客户端端口号转化为网络字节序
	ser.sin_addr.s_addr = inet_addr("127.0.0.1");//将客户端的ip地址转化为网络字节序;注意这里输入的ip地址是链接本机;
	int ret = bind(listen_fd,(struct sockaddr*)&ser,sizeof(ser));//绑定监听套接字
	assert(ret != -1);
	listen(listen_fd,5);//监听
	printf("listen finish\n");

	//设置select中read的结构;
	int maxfd = 0;
    fd_set read;
   //用一个数组来记录所有关注的文件描述符,此连接分为监听套接字和已完成连接的套接字
	int fds[128];
  //初始化数组
	int i = 0;
	for(;i < 128;++i)
	{
		fds[i] = -1;
	}
	fds[0] = listen_fd;
	while(1)
	{
		FD_ZERO(&read);
		int i = 0;
		//找出select第一个参数的值
		for(; i < 128;++i)
		{
			if(fds[i] != -1)
			{
				if(fds[i] > maxfd)
				{
					maxfd = fds[i];
				}
				FD_SET(fds[i],&read);
			}
		}
			//通过select数组中那些链接有可读事件
			int n = select(maxfd+1,&read,NULL,NULL,NULL);
			if( n == 0)//连接超时
			{
				printf("time out \n");
			}
			if(n < 0)//连接出错
			{
				exit(0);
			}
			for(i = 0;i < 128;++i)
			{
		    	if(fds[i] != -1)
				{
					if(FD_ISSET(fds[i],&read))//判断链接是否就绪
					{
						if(fds[i] == listen_fd)//如果是监听连接,则进行accept获得客户链接,并将客户链接添加到数组中
						{
							int len = sizeof(cli);
							int c = accept(listen_fd,(struct sockaddr*)&cli,&len);
							assert(c != -1);
							addfds(fds,c);
						}
						else
						{
				        	 char buff[128] = {0};
				             int n = recv(fds[i],buff,128,0);
				             if(n <= 0)
					         {
								 close(fds[i]);
						         delfds(fds,fds[i]);
							 }
							 printf("buff::%s\n",buff);
					         send(fds[i],"0k",2,0);
					         break;
						}
					}
				}		
	}	
	}
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值