sockct编程中的select函数和poll函数使用详解

select函数

函数原型

#include <sys/select.h>

#include <sys/time.h>

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *excetset,const_struct timeval *timeout);

参数说明:

    maxfdp1:读、写、异常集合中的文件描述符的最大值加1

    readset:读的集合,表示如果有数据可读的套接口,就放到这个集合中

    writeset:写的集合,表示如果有数据可写的套接口,就放到这个集合中

    excetset:异常的集合,表示如果有数据异常的套接口,就放到这个集合中

    timeout:指定一个超时时间,如果是空指针,则不会超时,意味着一定要检测到一个事件才返回

说明:

select函数是利用单进程实现了并发,用于管理多个文件描述符或管理多个I/O,一旦一个或多个I/O检测到感兴趣的事件,select就立刻返回,将返回到的事件填充到对应的集合中,并返回事件个数。此时,就可以轮询这些事件进行挨个处理。这是不会阻塞了,因为select提前阻塞了,select的返回意味着事件的到来。

void FD_ZERO(fd_set *fdset);//清空集合
void FD_SET(int fd,fd_set *fdset);//添加文件描述符到集合中
void FD_CLR(int fd,fd_set *fdset);//将指定文件描述符从集合中移除
void FD_ISSET(int fd,fd_set *fdset);//判断一个文件描述符是否在集合中

使用(认真看注释)

int i;
	//FD_SETSIZE是select的一个限制,最多能处理的文件描述符的个数
	int client[FD_SETSIZE];
	int maxi=0;//最大的不空闲的位置
	for(int i=0;i<FD_SETSIZE;i++)
		client[i]=-1;//值为-1表示空闲

	int nready;//检测到的事件个数
	int maxfd=listenfd;
	fd_set rset;
	fd_set allset;
	FD_ZERO(&rset);
	FD_ZERO(&allset);
	//感兴趣的是监听套接口
	FD_SET(listenfd,&allset);
	while(1)
	{
		/*
		rset里面是已经改变过的集合,它只保留当前产生的事件的集合,并没有
		去监听所有的套接口,所以需要一个变量来临时保存一下
		rset=allset;
		/*
		本质思想是:用select统一管理多个套接口是否产生事件(本例中是可读事件,
		参数还可以选择还有可写,异常事件)
		,在这里,要管理的套接口
		有两类,一类是监听套接口,还有一类是已连接套接口	
		*/
		nready=select(maxfd+1,&rset,NULL,NULL,NULL);
		if(nready==-1)
		{
			//如果是被信号中断的,继续循环进行监听
			if(errno==EINTR)
				continue;

			ERR_EXIT("select");
		}
		if(nready==0)
			continue;
		
		//监听套接口发生了可读事件,意味着三次握手已经完成
		if(FD_ISSET(listenfd,&rset))
		{
			peerlen=sizeof(peeraddr);
			//accept不再阻塞了
			conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
			if(conn==-1)
				ERR_EXIT("accept");

		//以前是每个进程中都有一个conn,现在是用单进程的方式实现,单进程只有一个conn,那么就无法
                //保存多个客户端的连接信息,所以这时候需要一个数组来保存客户端的信息
			for(i=0;i<FD_SETSIZE;i++)
			{
				if(client[i]<0)
				{
					client[i]=conn;
					if(i>maxi)//说明最大的不空闲位置发生了改变
						maxi=i;
					break;//说明找到了空闲的位置
				}
			}
			//如果没有找到空闲的位置
			if(i==FD_SETSIZE)
			{
				fprintf(stderr,"too many clients\n");
				exit(EXIT_FAILURE);
			}

			printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
		
			//将conn这个套接口加入到集合当中,以便下一次去关心它的可读事件
			//此时,这个已连接套接口也是我们所感兴趣的套接口了
			FD_SET(conn,&allset);
			
			if(conn>maxfd)
				maxfd=conn;
			
			//说明检测到的事件已经处理完了,可以开始下一波的监听(套接口是否产生可读事件)了
			if(--nready<=0)
				continue;
		}

		
		//已连接套接口产生了可读事件
		for(int i=0;i<=maxi;i++)
		{
			conn=client[i];
			if(conn==-1)
				continue;

			//说明已连接套接口产生了可读事件,现在就可以读取数据了
			if(FD_ISSET(conn,&rset))
			{
				char recvbuf[1024]={0};
			        int ret=readline(conn,&recvbuf,1024);
                		if(ret==-1)
                        		ERR_EXIT("readline");
                 		if(ret==0)
                 		{      
                        		printf("client close\n");
					//对方关闭,从集合当中清除
					FD_CLR(conn,&allset);
					//一旦一个套接口关闭,这个位置就空闲了,置为-1,用以容纳更多的客户端
					client[i]=-1;
                 		}
          	
				//将收到的这一行输出至标准输出
                 		fputs(recvbuf,stdout);
				//回射给客户端
                 		writen(conn,recvbuf,strlen(recvbuf));
				
				//所有的事件已被处理,就需要重新监听哪些套接口产生了可读事件
				if(--nready<=0)
					break;
			}
		}
		
	}
	

poll函数

函数原型

#include <poll.h>

int poll(struct pollfd* fdarray,usigned long nfds,int timeout);

参数说明

    fdarray:第一个参数是指向一个结构数组第一个元素的指针,将我们所关心的一些事件或I/O加入到数组中即可。

    nfds:加入数组中的个数。

    timeout:超时事件

struct pollfd{
    int fd;//文件描述符
    short events;//这个文件描述符所感兴趣的事件
    short revents;//当事件到来时,到底是哪一个事件发生了返回到revents中
}

使用(认真看注释)

	int i;
	//FD_SETSIZE是select的一个限制,最多能处理的文件描述符的个数
	//保存客户端的信息
	struct pollfd client[2048];
	int maxi=0;//最大的不空闲的位置
	for(int i=0;i<2048;i++)
		client[i].fd=-1;//值为-1表示空闲

	int nready;//检测到的事件个数
	
	client[0].fd=listenfd;//第一个添加进去的是监听套接口
	client[0].events=POLLIN;//对监听套接口的可读事件感兴趣,而对可写事件并不感兴趣

	while(1)
	{
		//-1表示永远等待,直到有事件到来的时候才返回
		nready=poll(client,maxi+1,-1);
		
		if(nready==-1)
		{
			//如果是被信号中断的,继续循环进行监听
			if(errno==EINTR)
				continue;

			ERR_EXIT("select");
		}
		if(nready==0)
			continue;
		
		//如果返回的事件等于POLLIN,监听套接口发生了可读事件
		if(client[0].revents & POLLIN)
		{
			peerlen=sizeof(peeraddr);
			//accept不再阻塞了
			conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen);
			if(conn==-1)
				ERR_EXIT("accept");

		//以前是每个进程中都有一个conn,现在是用单进程的方式实现,单进程只有一个conn,那么就无法
                //保存多个客户端的连接信息,所以这时候需要一个数组来保存客户端的信息
			for(i=0;i<2048;i++)
			{
				if(client[i].fd<0)
				{
					client[i].fd=conn;
					if(i>maxi)//说明最大的不空闲位置发生了改变
						maxi=i;
					break;//说明找到了空闲的位置
				}
			}
			//如果没有找到空闲的位置
			if(i==2048)
			{
				fprintf(stderr,"too many clients\n");
				exit(EXIT_FAILURE);
			}

			printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
			printf("count=%d\n",++count);
			//添加事件
			client[i].events=POLLIN;

			//说明检测到的事件已经处理完了,可以开始下一波的监听(套接口是否产生可读事件)了
			if(--nready<=0)
				continue;
		}

		
		//已连接套接口产生了可读事件
		for(int i=1;i<=maxi;i++)
		{
			conn=client[i].fd;
			if(conn==-1)
				continue;

			//说明已连接套接口产生了可读事件,现在就可以读取数据了
			if(client[i].events & POLLIN)
			{
				char recvbuf[1024]={0};
			        int ret=readline(conn,&recvbuf,1024);
                		if(ret==-1)
                        		ERR_EXIT("readline");
                 		if(ret==0)
                 		{      
                        		printf("client close\n");
					//一旦一个套接口关闭,这个位置就空闲了,置为-1
					client[i].fd=-1;
					close(conn);
                 		}
          	
				//将收到的这一行输出至标准输出
                 		fputs(recvbuf,stdout);
				//sleep(4);
				//回射给客户端
                 		writen(conn,recvbuf,strlen(recvbuf));
				
				//所有的事件已被处理,就需要重新监听哪些套接口产生了可读事件
				if(--nready<=0)
					break;
			}
		}
		
	}

猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/80408461