网络编程 — 浅析I/O多路转接select技术

浅析I/O多路转接之select技术




说到select服务器首先提到I/O多路转接,我们就不得不提及I/O的5种工作模式,再然后我们就不得不再提及I/O是什 么了? I/O

是input/output的缩 写,即输入输出端口。每个设备都会有一个专用的I/O地址,用来处理自己的输入输出 信息.接下来我 们来

认识 一下I/O的5种工作模式. 再说这个之前我说一个小故事,从前有好几个人一起钓鱼,但是他们所使用的方法却大大的不同.

现在开始我们说明 每个人的 方法. A是一个老头,它 比较固执他就一直坐在湖边等鱼上钩,眼睛一动不动的盯着鱼钩,一旦有情

况他就立 马抬杆,然后鱼上 钩后,他就 复到以前的状态.B是一个年轻的 大学生周末没事干也来钓鱼,但是他是边玩手机变钓鱼

,也就是玩一会手机来 瞧一瞧这个鱼饵有 没有 变化,如果有变化就立马抬杆.C是一个老板, 他的鱼竿具有提示功能, 然后他

在干自己的事情,等有 情况他就立马过来把鱼 钓上来,D是一个土豪,然后他就一次性放了好多个具有提示功 能的鱼竿,

然后守 在鱼竿跟前 ,一旦有 那个鱼竿出现情况就收拾那个 鱼竿.这时候E出现,E是 一个领导,他让别人帮 他钓鱼,钓到了给它

打电话. 上这5个人,其实分别代表这I/O的5中工作模式,分别是

阻塞式I/O模型,非阻塞式I/O模型,信号驱动I/O模型,I/O复用模型,以及异步I/O模型.


1.阻塞I/O模型

应用程序调用一个IO函数,导致应用程序阻塞,等待数据准备好. 如果数据没有准备好,一直等待. 数据准备好了,从内核拷贝

到用户空间. IO函数返回成功指示

2.非阻塞I/O模型

我们把一个套接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一个错
误.这样我 们的I/O 操作函数将不断地测试数据是否已经准备好,如果没有准备好,进行测试,直到数据准备好为止.在
这个不断测试的过 程中,会大量的 占用CPU的时间

3.I/O复用模型

I/O复用模型会用到select或者poll函数,这两个函数也会使进程阻塞,但是和阻塞I/O所不同的,这两个函数可以同
时阻塞多 个I/O操 作,而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写是,才真
正调用I/O操作函 数.

4.信号驱动I/O模型

首先我们允许套接字进行信号驱动I/O,并安装一个信号处理函数,进程继续进行并不阻塞.当数据准备好时,进程会
受到一个SIGIO信 号,可以在信号处理函数中调用I/O

5.异步I/O模型

调用aio_read函数,告诉内核描述字,缓冲区指针,缓冲区大小,文件偏移以及通知的方式,然后立即返回。当内核
讲数据拷贝到缓冲区 后,再通知应用程序

今天我们的主角很明显就是I/O复用模型当中异步I/O中的select函数,以及如何使用它来构造select服务器,现在正是开始,

们来了解 select, 系统提供select函数来实现多路复用输入\输出模型.socket系统调用是用来让我们的程序监视多个文

句柄的状态变 化的. 程序会停在select这里等 待,知道被监视的文件句柄有一个或多个发生了状态改变.



select函数
                                                                             


函数原型:
                          


参数nfds是需要监视的最大文件描述符值+1;

rdset,wrset,exset分别是对应于需要检测的可读文件描述符集合,可写文件描述符的集合及异常文件描述符集合. 剩下的

timeout就是我们的用来描 述一段时间,如果需要监视的描述符没有事件发生在这段时间里面,则返回返回 值为0.

如果参数timeout设置为NULL,则select一直被阻塞.给定特定时间值:如果在该时间没没有事件发生那么就返回0.


FD_CLR(int fd, fd_set *set); 用来清除描述词组set中相关fd的位.

FD_ISSET(int fd,fd_set *set): 用来测试描述词组set中相关fd的位是否为真.

FD_SET(int fd,fd_set* set); 用来设置描述词组set中的相关fd位

FD_ZERO(fd_set *set); 用来清除描述词组set的全部位


函数返回值:

执行成功则返回文件描述词状态以改变的个数.如果返回0代表在描述词状态改变前已超过timeout时间,没有返回 .如果有

误时 返回-1. 注意这里有一个重点!  当select函数返回的时候,它会将读,写,异常fd_set当中有我们关心事件但是没有

发生事件的文件描述符置0 ,所以每一次select返回后,我们需要重新置我们需要关心事件的fd_set. 当然!我们最重要的是

看看服务器是如何使用的,以及它的优缺点.

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个⽂ 文件描

fd。则1字节长的fd_set最⼤大可以对应8个fd。实际上就是位图
 
(1)执⾏行fd_set set; FD_ZERO(&set);则set⽤用位表⽰示是0000,0000。 

(2)若fd=5,执⾏行FD_SET(fd,&set);后set变为0001,0000(第5位置为1) 

(3)若再加入fd=2,fd=1,则set变为0001,0011 

(4)执行select(6,&set,0,0,0)阻塞等待 

(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被
清空。


使用select编写网络服务器
                                                                                    


现在呢我们来尝试编写一个select服务器,今天我们来编写一个简易版的就是所有人讲的话都可以在select上面大家都看

,也就是 类似于弹幕墙 一样的东西. 首先我们肯定需要一个监听套接字这时候我使用一个stratup()函数来封装.接下来

创建 一个存放文件描述符 数组,首先肯定是把它第一位设为为监听套接字,剩下的都为-1. 调用select函数,如果它的返

回值大 于0,开始给对应的文件描述符 上面设定位,因为我这里只需要读所以全都是&rfds,首先进入进入监听套接字,把

监听套 字监听到的文件描述符,交给一个连接套 接字来处理,这里每个客户端都会有一个连接套接字,当客户端关闭时,

连接套接 字也会关闭. 然后把这个连接套接字也放入文件描述符 队列,这样下一次如果它的对应位就绪,那么就会读取到

客户端的内容.

这里肯定不会让监听套接字去维护一个客户端, 一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一

存在。所以数据 都是从每个客户端的连接套接字里面读到的,包括以后的写都是由newsock服务的.


服务器server.c

/*************************************************************************
	> File Name: server.c
	> Author: ma6174
	> Mail: ma6174@163.com 
	> Created Time: Wed 26 Jul 2017 09:26:09 PM PDT
 ************************************************************************/

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



static void usage(const char* proc)
{
	printf("Usage : %s [local_ip] [local_port]\n",proc);
}


int fds[sizeof(fd_set)*8];

int startup(char* ip,int port)
{
	int sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0){
		error("socket");
		exit(2);
	}

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(port);
	local.sin_addr.s_addr = inet_addr(ip);

	if(bind(sock, (struct sockaddr*)&local , sizeof(local))<0)
	{
		perror("BInd");
		exit(3);
	}
	if(listen(sock ,10)< 0)
	{
		perror("lisen");
		exit(4);
	}
	return sock;
}

int main(int argc,char* argv[])
{
	if(argc != 3)
	{
		usage(argv[0]);
		return 1;
	}

	int listen_sock = startup(argv[1],atoi(argv[2]));
	fd_set rfds;
	int nums = sizeof(fds)/sizeof(fds[0]);
	int i =1;
	for(; i<nums; i++){
		fds[i] = -1;
	}

	while(1)
	{

		FD_ZERO(&rfds);
		int max = -1;
		//struct timevlal timeout = {5.0};
		fds[0] = listen_sock;
		for(i = 0;i < nums;i++){
			if(fds[i] > -1){
				FD_SET(fds[i],&rfds);
				if(max < fds[i])
				{
					max = fds[i];
				}
			}
		}
		switch(select(max+1,&rfds,NULL,NULL,0))
		{
			case 0:
				printf( "chao  shi le");
				break;
			case -1:
				printf("select");
				break;

			default:
 				i = 0;
				for(; i < nums ; i++)
				{
 	 		  		if(fds[i] == -1){
						continue;
					}
					if(i == 0 && FD_ISSET(fds[i],&rfds))
					{
 			 			struct sockaddr_in client;
						socklen_t len = sizeof(client);
	
						int new_sock = accept(fds[i],(struct sockaddr*)&client,&len);
						if(new_sock < 0)
						{ 
 				  			perror("accept") ;
				 			continue;
						}
			
						printf("get a client[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
						int j = 1;
						for(;j< nums;j++){
 						 	if(fds[j] == -1){ 
								break;
				 			}
						}
						if(j == nums){
							close(new_sock);
						}else{
							fds[j] = new_sock;
				 		}
					}
					else if(i != 0&& FD_ISSET(fds[i],&rfds))
					{
 				 		char buf[1024];
						ssize_t s = read(fds[i],buf,sizeof(buf));
				
						if(s > 0)
						{
							printf("client# %s\n",buf);
						}else if(s == 0)
						{
 				  			printf("client is quit!!!!!\n");
					
							close(fds[i]);
							fds[i] = -1;
				
						}
						else{
 				 		perror("read");
						close(fds[i]);
						fds[i] = -1;
						
						}
				    }else{	
				 		}
				}
				break; 
		}
	}
	return 0;
}


客户端client代码:

/*************************************************************************
	> File Name: client.c
	> Author: ma6174
	> Mail: ma6174@163.com 
	> Created Time: Thu 27 Jul 2017 01:20:36 AM PDT
 ************************************************************************/

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

void use(char* a)
{
	printf("#%s [port_server]\n",a);
}

int main(int argc,char* argv[])
{
	printf("main start\n");
	if(argc < 3)
	{
		use(argv[0]);
		return 3;
	}
	printf("use is ok\n");
	int sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0){
		perror("socket");
		return 1;
	}
	printf("create socket is ok\n");
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(atoi(argv[2]));
	server.sin_addr.s_addr = inet_addr((argv[1]));
	int conn = connect(sock,(struct sockaddr*)&server , sizeof(server));
	if(conn < 0){
		perror("connect");
		close(sock);
		return 2;
	}
	while(1)
	{
		printf("please enter#");
		fflush(stdout);
		char buf[1024];
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0';
		write(sock,buf,sizeof(buf));
		char* str = "quit";
		if(strcmp(buf,str) == 0)
		{
			break;
		}
	}
	close(sock);
	printf("client goodbye!\n");
	return 0;
}


注意这里我这是想让客户端在服务器当中输出消息,所以一直写就够了.


最后我们看看运行结果:




我们看到三个客户端发送的内容都通过局域网出现在服务器当中,所以我们最初级的select服务器宣布成功.现在还是有好多个

bug,需要我们不断地改进,现在我们的任务是了解这个函数,会使用它就好了.


总结:
                                                                                                                                                                                                   
优点:
1.select资源占用比较少,
2 用户量较多的时候它的性能和效率比较好.

缺点:
1.它总共监视的文件描述符是有限制的. 最多1024
2.因为参数为输入输出性,在操作时 得一直进行遍历,查找使用.
3.select所监视的文件描述符,select的调用频繁,然后它会反复遍历,可能会达到性能瓶颈.
4.当select频繁调用的过程中,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时很大.
5.select的参数为输入输出类型.


所以我们其实可以看到select服务器并没有我们想像中那么厉害,其实呢他的缺点大于它的优点,所以呢~它肯定会有优化 版本

我们关注下一个博 客 认识和编写poll服务器.



  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值