I/O多路复用之select
来源:互联网 发布:cst软件介绍 编辑:程序博客网 时间:2024/06/06 00:52
I/O模型
- 阻塞I/O
- 非阻塞I/O
- I/O复用
- 信号驱动I/O
- 异步I/O
阻塞I/O模型
应用程序调用一个I/O函数,应用程序会一直等待数据准备好。如果数据没有准备好,就会一直等待。只有当数据准备好,从内核拷贝到用户空间IO函数才成功返回。
非阻塞I/O模型
把一个套接口设置成非阻塞告诉内核,当所有的I/O操作无法完成时,不要将进程睡眠,而返回一个错误信息。此时I/O操作函数将不断的测试数据是否准备好,如果没有准备好,继续测试,知道数据准备好为止。不断测试的过程中会占用cpu时间。
I/O复用模型
I/O复用模型会用到select或者poll函数,这两个函数会使进程阻塞,并同时阻塞多个I/O操作,而且同时对多个读写操作的I/O函数进行检测,知道有数据刻毒或可写,才正真I/O操作函数。
信号驱动I/O模型
允许套接字进行信号驱动I/O,并安装信号驱动函数,进程继续运行并不停止,当数据=准备好时,进程会收到SIGIO信号,可以在信号处理函数中调用I/O操作函数进行处理数。
异步I/O模型
调用aio_read函数,告诉内核描述字,缓冲区指针,缓冲区大小,文件偏移以及通知方式,然后立即返回。当内核数据拷贝到缓冲区后,再通知应用程序
大部分场景,一个程序或网络服务出现性能问题时,可以优先考虑I/O所带来的影响。
站在用户的角度,I/O分两个阶段完成,第一阶段是等事件就绪,第二阶段才是真正的进行I/O操作。
对于这5种I/O模型,前四种第一二阶段基本相同,都是等数据就绪后将其从内核拷贝到调用者的缓冲区。而异步I/O模型不同。
影响I/O性能主要是第一阶段等待数据就绪的时候。
提高I/O性能,首先要考虑I/O等的比重。
主要讲述下I/O复用模型及select
I/O复用模型
I/O多路复用指内核一旦发现进程指定的一个或多个IO条件准备读取,它就通知进程。
I/O复用一般用于一下场合:
- 客户端程序要同时处理多个socket。
- 客户端程序要同时处理用户输入和网路连接
- TCP服务器要同时处理监听socket和连接socket。这时I/O复用使用最多的场合
- 服务器要同时处理TCP和UDP请求。
- 服务器要同时监听多个端口或者处理多种服务。
I/O复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当多个文件描述符同时就绪的同时,如果不采取额外的措施,程序就只能按照顺序的依次处理其中的每一个文件描述符,这就使得服务器程序看起来是串行工作的。当然如果要实现并发,只能使用多线程或多进程编程。
select
select系统调用的用途是:提供轮询等待的方式从多个文件描述符中获取状态变化后的消息
/* According to POSIX.1-2001 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ };//第一个参数nfds指定要坚挺的文件描述符的总数,测试的范围是在0到nfds-1的文件描述(nfds为这些文件描述符的最大值+1,因为文件描述符是从0开始的)。//后面4个参数是输入输出型参数,第2、3、4参数类型都是fd_set*(文件描述符集)。readfds、writefds、exceptfds分别指向可读、可写、可写的文件描述符集。当select调用返回时,内核将修改它们通知应用程序那个文件描述符就绪。//最后一个参数timeout用来设置select调用的超时时间。采用timeval结构类型的指针,内核将修改它来告诉应用程序select等待了多久。//select成功返回就绪的文件描述符的总数;返回0时,说明还没有文件描述符就绪。失败返回-1并设置errno
select操作函数
void FD_CLR(int fd, fd_set *set);//用来清除set中fd相关的位int FD_ISSET(int fd, fd_set *set);//用来检测set中fd相关的位是否为真void FD_SET(int fd, fd_set *set);//用来设置set中fd相关的位void FD_ZERO(fd_set *set);//用来清除set中的所有`
//select.cinclude<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#include<fcntl.h>#include<string.h>#include<sys/select.h>#include<unistd.h>int fds[1024];static void usage(const char* proc){ printf("usuage:%s [local_ip] [local_port]\n",proc);}int startup(const char* ip,int port){ int sock=socket(AF_INET,SOCK_STREAM,0);//创建套接字 if(sock<0) { perror("socket"); exit(1); } 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(2); } if(listen(sock,5)<0)//服务器监听网络 { perror("listen"); exit(3); } 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]));//监听套接字 int nums=sizeof(fds)/sizeof(fds[0]); int maxfd=-1; int i=1; for(i=1;i<nums;i++) { fds[i]=-1;//将放文件描述符的数组初始化位-1 } fds[0]=listen_sock;//用数组的第一个放listen_sock while(1) { struct timeval timeout={5,0}; fd_set rfds;//定义文件描述符集 FD_ZERO(&rfds);//每次进来将清除rfds的全部位 maxfd=-1; for(i=0;i<nums;i++)//遍历fds数组,看哪个文件描述符有效 { if(fds[i]>0) { FD_SET(fds[i],&rfds);//fds[i]有效,将设置在rfds中fds[i]的相关位 if(maxfd<fds[i]) { maxfd=fds[i];//找到文件描述符的最大值 } } } switch(select(maxfd+1,&rfds,NULL,NULL,&timeout)) { case 0: printf("timeout..\n");//没有文件描述符事件就绪 break; case -1: perror("select");//调用失败 break; default: { for(i=0;i<nums;i++) { if(fds[i]<0) { continue;//找到第一个有效文件描述符 } if(i==0 && FD_ISSET//listen_sock(listen_sock,&rfds)) { struct sockaddr_in client; socklen_t len=sizeof(client); int new_fd=accept(listen_sock,(struct sockaddr*)&client,&len);//接受客户请求 if(new_fd<0) { perror("accept");//失败继续 continue; } printf("get a new client[%s,%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port)); int j=1; for(j=1;j<nums;j++)//将新的标识符文件描述符放入fds中 { if(fds[j]==-1) { break; } } if(j==nums) { printf("server full..\n"); close(new_fd); } else { fds[j]=new_fd; } }//if else if((i>0)&&FD_ISSET(fds[i],&rfds))//第二次轮询的时候fds[i]=new_fd ready,进行读事件 { char buf[1024]; while(1) { ssize_t s=read(fds[i],buf,sizeof(buf)-1); if(s>0) { buf[s]=0; printf("client say:%s",buf); fflush(stdout); // fds[i]=-1; } else if(s==0) { printf("client quit!\n"); close(fds[i]); fds[i]=-1;//断开连接将fds[i]置为1。 break; } else { perror("read"); break; } } }//else if else {} }//for }//default break; }//switch }//whilereturn 0;}
//client.c#include<stdio.h>#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<stdlib.h>#include<string.h>void usage(const char* proc){ printf("%s [server_ip] [server_port]\n",proc);}int main(int argc,char* argv[]){ if(argc!=3) { usage(argv[0]); exit(1); } int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { perror("sock"); exit(2); } struct sockaddr_in remote; remote.sin_family=AF_INET; remote.sin_port=htons(atoi(argv[2])); remote.sin_addr.s_addr=inet_addr(argv[1]); if(connect(sock,(struct sockaddr*)&remote,sizeof(remote))<0) { perror("connect"); exit(3); } char buf[1024]; while(1) { printf("please enter:"); fflush(stdout); ssize_t s=read(0,buf,sizeof(buf)-1); if(s>0) { buf[s]=0; write(sock,buf,strlen(buf)); } } return 0;}
select缺点
- 每次调用select,都需要把fd集合从用户态拷贝到内核态
- 每次调用select都需要在内核中遍历所有的fd
- select支持的文件描述符个数太小,为sizeof(fd_set)*8=1024
- I/O多路复用之select
- I/O 多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O多路复用之select
- I/O 多路复用之select
- I/O多路复用select
- 多路复用I/O--select
- I/O多路复用之select总结
- I/O多路复用技术之 - select
- I/O 多路复用之 Select & Epoll
- I/O多路复用之select/poll/epoll
- I/O多路复用之select模型
- 大数据工程师技能图谱
- Eclipse运行main类抛出异常:Unsupported major.minor version 52.0
- Android开机广播
- iOS开发~制作同时支持armv7,armv7s,arm64,i386,x86_64的静态库.a
- Spring Boot基础
- I/O多路复用之select
- 几维安全:为客户提供全方位的移动应用安全服务
- HEVC中编码每个CTU的码流跟踪
- 叹,时光飞逝
- [重要] -- swag.ger生成 PHP restful API 接口文档
- 利用xshell及rz,sz实现拖曳上传文件到Linux服务器
- 一分钟秒懂公有云、私有云、混合云......
- 微信小程序 textarea 不显示文本解决办法
- iOS 什么是MVC?