网络编程 — 浅析I/O多路转接select技术
2017-08-03 21:40
423 查看
浅析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.它总共监视的文件描述符是有限制的. 最多10242.因为参数为输入输出性,在操作时 得一直进行遍历,查找使用.3.select所监视的文件描述符,select的调用频繁,然后它会反复遍历,可能会达到性能瓶颈.4.当select频繁调用的过程中,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时很大.5.select的参数为输入输出类型.
所以我们其实可以看到select服务器并没有我们想像中那么厉害,其实呢他的缺点大于它的优点,所以呢~它肯定会有优化版本,我们关注下一个博
客 认识和编写poll服务器.
相关文章推荐
- 【Linux网络编程】基于TCP协议 I/O多路转接(select) 的高性能回显服务器客户端模型
- Linux【网络编程】——I/O多路转接之Select服务器
- Linux网络编程【六】:TCP协议高性能服务器(http)模型之I/O多路转接epoll
- 【计算机网络】I/O多路转接之select
- Linux c==网络编程、循环服务器、并发服务器、I/O多路转接 (23)
- Linux c==网络编程、循环服务器、并发服务器、I/O多路转接
- 深入研究socket编程(2)——I/O多路转接(select、pselect和poll)
- 网路基础 — 浅析I/O多路转接之poll技术
- Linux【网络编程】——I/O多路转接之Poll服务器
- 【Linux网络编程】基于TCP流 I/O多路转接(poll) 的高性能http服务器
- 【网络编程】IO 多路复用之 select 总结
- Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)
- 网路基础 — 浅析I/O多路转接之epll技术原理
- linux网络编程----->高并发--->epoll多路I/O转接服务器
- Linux下的socket编程实践(七) I/O多路复用技术之select模型
- Linux【网络编程】——I/O多路转接之epoll服务器
- linux 网络编程---->多路复用:select实例!
- Linux下的socket编程实践(七) I/O多路复用技术之select模型
- 【Linux网络编程】I/O多路转接之 epoll 高性能简洁http服务器模型
- Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)