多路IO复用技术--select模型

扶醉桌前 提交于 2019-12-18 01:56:47

1.功能介绍

1.1 网络通信中,对于套接字(文件描述符)在任意时刻是否有数据可读,我们不知道,只会用while 10毫秒循环收发,select能够解决这个问题,时时监听套接字的读写情况,有收到数据就读取。

2.相关函数说明

2.1 int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);
说明:select()用来等待文件描述符状态的改变。参数n代表最大的文件描述符加1,参数readfds、writefds和exceptfds称为文件描述符集,填了readfd代表监听可读事件(收数据),不填代表不监听该事件,其他两个同理,timeout代表超时时间设置,就是个时间,可不设置,填NULL,struct timeval结构体如下:
 struct timeval{      
         long tv_sec;   /*秒 */

         long tv_usec;  /*微秒 */   

     }
2.2 字符集相关函数操作

int FD_ZERO(fd_set *fdset);  

int FD_CLR(int fd, fd_set *fdset);   

int FD_SET(int fd, fd_set *fdset);  

int FD_ISSET(int fd, fd_set *fdset);
说明:FD_ZERO是初始化字符集变量,使用FD_SET将套接字fd(文件描述符)加入字符集fdset,使用FD_CLR将套接字fd(文件描述符)从字符集fdset中删除,使用FD_ISSET来判断套接字fd(文件描述符)是否加入字符集fdset进行监听。

3.使用场景

3.1 有多个套接字(高并发)需要收发数据。

4.使用代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>

int main()
{
	//创建socket
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd < = 0)
    {
           return -1;
    }

	//设置端口复用
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

	//绑定
	struct sockaddr_in serv;
	serv.sin_family = AF_INET;
	serv.sin_port = htons(8888);
	serv.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(lfd, (struct sockaddr *)&serv, sizeof(serv));

	//监听
	listen(lfd, 128);

	//定义文件描述符集变量
	fd_set readfds, tmpfds;
	
	//初始化文件描述符集变量
	FD_ZERO(&readfds);
	FD_ZERO(&tmpfds);
	
	//将lfd加入到readfds中
	FD_SET(lfd, &readfds);
	
	int maxfd = lfd;
	int cfd;
	int i;
	int sockfd;
	int nready;
	int n;
	char buf[1024];
	
	sleep(10);
	
	while(1)
	{
		FD_ZERO(&tmpfds);
		tmpfds = readfds;
		
		//将文件描述符集委托给内核监控
		//tmpfds是传入传出参数, 
			//传入:告诉内核要监测哪些文件描述符
			//传出:内核告诉应用程序那些文件发生了变化, 但是并没有具体告诉到底是哪些.
		nready = select(maxfd+1, &tmpfds, NULL, NULL, NULL);
		printf("nready==[%d]\n", nready);
		if(nready<0)
		{
			if(errno==EINTR)//若被信号中断,则errno为EINTR
			{
				perror("select error");
				continue;
			}
			close(lfd);
			exit(-1);
		}
		
		//若有客户端连接请求, 则接受新的连接,同时将新的连接加入到文件描述符集中
		if(FD_ISSET(lfd, &tmpfds))
		{
			cfd = accept(lfd, NULL, NULL);
		    if(lfd < = 0)
            {
                continue;
            }
			//将cfd加入到文件描述符集中
			FD_SET(cfd, &readfds);
			
			if(maxfd<cfd)
			{
				maxfd = cfd;
			}
			
			if(--nready==0)
			{
				continue;
			}
		}
		
		//下面是有客户端发送数据到来的情况
		for(i=lfd+1; i<=maxfd; i++)
		{
			//判断某个文件描述符是否有变化
			sockfd = i;
			if(FD_ISSET(sockfd, &tmpfds))
			{
				//读数据
				memset(buf, 0x00, sizeof(buf));
				n = Read(sockfd, buf, sizeof(buf));
				if(n<=0)
				{
					//若读数据失败或者对方关闭连接, 则将sockfd从文件描述符集中删除
					close(sockfd);
					FD_CLR(sockfd, &readfds);					
				}
				else 
				{
					for(int k=0; k<n; k++)
					{
						buf[k] = toupper(buf[k]);
					}
					Write(sockfd, buf, n);
				}
				
				if(--nready==0)
				{
					break;
				}
			}
		}
	}
	
	close(lfd);
	
	return 0;
}

5.缺点

5.1 当客户端多个连接, 但少数活跃的情况, select效率较低。
例如: 作为极端的一种情况, 3-1023文件描述符全部打开, 但是只有1023有发送数据, select就显得效率低下。
5.2 fd_set能容纳的文件描述符 数量由FD_SETSIZE指定,一般情况下,FD_SETSIZE等 于1024,这就限制了select能同时处理的(套接字)文件描述符的总量(FD_SETSIZE=1024 fd_set使用了该宏, 当然可以修改内核, 然后再重新编译内核, 一般不建议这么做.)。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!