#导入Word文档图片# Linux下IO多路复用: Selectpollepoll
Posted DS小龙哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#导入Word文档图片# Linux下IO多路复用: Selectpollepoll相关的知识,希望对你有一定的参考价值。
IO多路复用之Select机制
1.1 基本概念
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:
(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
1.2 select函数
该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下:
#include <sys/select.h> #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); |
- 返回值就绪描述符的数目,超时返回0,出错返回-1
- 函数参数介绍(1)第一个参数nfds指定待测试的描述字个数,它的值是待测试的最大描述字加1 (如果文件描述符是5,那么nfds就填5+1)。
(2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个的条件不感兴趣,就可以把它设为空指针。struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:
void FD_ZERO(fd_set *fdset); //清空集合 void FD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中 void FD_CLR(int fd, fd_set *fdset); //将一个给定的文件描述符从集合中删除 int FD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写 |
(3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。
struct timeval long tv_sec; //seconds long tv_usec; //microseconds ; |
这个参数有三种可能:
(1)永远等待下去:仅在有一个描述字准备好I/O时才返回。为此,把该参数设置为空指针NULL。
(2)等待一段固定时间:在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
(3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。
1.3 总结
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
1.4 示例1
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <linux/input.h> #include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #define DEVICE "/dev/input/event3" //鼠标设备 fd_set read_set; struct input_event ev; int main() int fd; fd=open(DEVICE,2); if(fd<0)
printf("驱动打开失败!\\n");
while(1) FD_ZERO(&read_set); FD_SET(fd,&read_set); if(select(fd+1,&read_set,NULL,NULL,NULL)) if(FD_ISSET(fd,&read_set)) if(read(fd,&ev,sizeof(struct input_event))==sizeof(struct input_event)) //读取发生的事件 printf("key=%d value = %d\\n",ev.code,ev.value); return 0; |
1.5 示例2
查看系统的标准文件描述符: [root@wbyq test_20180702]# ls /dev/std* -l lrwxrwxrwx. 1 root root 15 4月 17 11:34 /dev/stderr -> /proc/self/fd/2 lrwxrwxrwx. 1 root root 15 4月 17 11:34 /dev/stdin -> /proc/self/fd/0 lrwxrwxrwx. 1 root root 15 4月 17 11:34 /dev/stdout -> /proc/self/fd/1 |
网络通信里使用select机制:
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdlib.h> #include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <string.h> unsigned char rx_buff[1024]; unsigned int rx_cnt; unsigned char log_info[1024]; unsigned int log_cnt=0; /* TCP服务器创建 */ int main(int argc,char **argv) int tcp_server_fd; //服务器套接字描述符 int tcp_client_fd; //客户端套接字描述符 struct sockaddr_in tcp_server; struct sockaddr_in tcp_client; socklen_t tcp_client_addrlen=0; int tcp_server_port; //服务器的端口号 //判断传入的参数是否合理 if(argc!=2) printf("参数格式:./tcp_server <端口号>\\n"); return -1; tcp_server_port=atoi(argv[1]); //将字符串转为整数 /*1. 创建网络套接字*/ tcp_server_fd=socket(AF_INET,SOCK_STREAM,0); if(tcp_server_fd<0) printf("TCP服务器端套接字创建失败!\\n"); return -1; /*2. 绑定端口号,创建服务器*/ tcp_server.sin_family=AF_INET; //IPV4协议类型 tcp_server.sin_port=htons(tcp_server_port);//端口号赋值,将本地字节序转为网络字节序 tcp_server.sin_addr.s_addr=INADDR_ANY; //将本地IP地址赋值给结构体成员 if(bind(tcp_server_fd,(const struct sockaddr*)&tcp_server,sizeof(struct sockaddr))<0) printf("TCP服务器端口绑定失败!\\n"); return -1; /*3. 设置监听的客户端数量*/ listen(tcp_server_fd,10); /*4. 等待客户端连接*/ tcp_client_addrlen=sizeof(struct sockaddr); tcp_client_fd=accept(tcp_server_fd,(struct sockaddr *)&tcp_client,&tcp_client_addrlen); if(tcp_client_fd<0) printf("TCP服务器:等待客户端连接失败!\\n"); return -1; //打印连接的客户端地址信息 printf("已经连接的客户端信息: %s:%d\\n",inet_ntoa(tcp_client.sin_addr),ntohs(tcp_client.sin_port)); /*5. 数据通信*/ fd_set readfds; //读事件的文件操作集合 fd_set writefds;//写事件的文件操作集合 int select_state; //接收返回值 while(1) /*5.1 清空文件操作集合*/ FD_ZERO(&readfds); FD_ZERO(&writefds); /*5.2 添加要监控的文件描述符*/ FD_SET(tcp_client_fd,&readfds); FD_SET(tcp_client_fd,&writefds); /*5.3 监控文件描述符*/ select_state=select(tcp_client_fd+1,&readfds,&writefds,NULL,NULL); if(select_state>0)//表示有事件产生 /*5.4 测试指定的文件描述符是否产生了读事件*/ if(FD_ISSET(tcp_client_fd,&readfds)) /*5.5 读取数据*/ rx_cnt=read(tcp_client_fd,rx_buff,1024); if(rx_cnt==0) printf("对方已经断开连接!\\n"); break; sprintf(log_info,"server rx data[%d]",log_cnt++); write(tcp_client_fd,log_info,strlen(log_info)); //回发数据 write(tcp_client_fd,rx_buff,rx_cnt); //将收到的数据返回 //判断是否产生了写事件 if(FD_ISSET(tcp_client_fd,&writefds)) printf("写事件!\\n"); //只要有写权限,写事件将会一直产生 //比如: //连接建立成功后可写 //缓冲区可写 else if(select_state<0) //表示产生了错误 printf("select函数产生异常!\\n"); break; /*6. 关闭连接*/ close(tcp_client_fd); |
第二章 IO多路复用之poll机制
2.1 基本概念
poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
2.2 poll函数
函数格式如下所示:
# include <poll.h> int poll ( struct pollfd * fds, unsigned int nfds, int timeout); |
pollfd结构体定义如下:
struct pollfd int fd; /* 文件描述符 */ short events; /* 等待的事件 */ short revents; /* 实际发生了的事件 */ ; |
每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:
POLLIN | 有数据可读。 |
POLLRDNORM | 有普通数据可读。 |
POLLRDBAND | 有优先数据可读。 |
POLLPRI | 有紧迫数据可读。 |
POLLOUT | 写数据不会导致阻塞。 |
POLLWRNORM | 写普通数据不会导致阻塞。 |
POLLWRBAND | 写优先数据不会导致阻塞 |
POLLMSGSIGPOLL | 消息可用。 |
此外,revents域中还可能返回下列事件:
POLLER | 指定的文件描述符发生错误。 |
POLLHUP | 指定的文件描述符挂起事件。 |
POLLNVAL | 指定的文件描述符非法。 |
这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。
使用poll()和select()不一样,你不需要显式地请求异常情况报告。
POLLIN | POLLPRI等价于select()的读事件,POLLOUT |POLLWRBAND等价于select()的写事件。
POLLIN等价于POLLRDNORM |POLLRDBAND,而POLLOUT则等价于POLLWRNORM。
例如,要同时监视一个文件描述符是否可读和可写,我们可以设置 events为POLLIN |POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
- 返回值和错误代码
成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:
EBADF | 一个或多个结构体中指定的文件描述符无效。 |
EFAULTfds | 指针指向的地址超出进程的地址空间。 |
EINTR | 请求的事件之前产生一个信号,调用可以重新发起。 |
EINVALnfds | 参数超出PLIMIT_NOFILE值。 |
ENOMEM | 可用内存不足,无法完成请求。 |
2.3 示例
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <poll.h> /*使用poll轮询机制*/ /*定义一个poll结构体数组,用来存放poll相关的信息*/ struct pollfd tiny4412_poll[1]; /* argv :字符串的个数 argc :存放字符串的数组 ./app /dev/led */ int main(int argv,char*argc[]) int fb_count; /*存放poll函数返回值*/ int count=0; int fb1,tmp; if(argv!=2) printf("传参方式: ./app /dev/<device filce>\\n"); exit(-1); /*打开设备文件,打开成功返回文件描述符*/ fb1=open(argc[1],O_RDWR); /*打开第一个驱动-KEY*/ if(fb1<0) printf("open device_file error!\\n"); exit(-1); char key; printf("open device_file ok!\\n"); tiny4412_poll[0].fd=fb1; /*检测的文件描述符*/ tiny4412_poll[0].events=POLLIN;/*检测的事件*/ while(1) #导入Word文档图片# 阻塞与非阻塞IO操作 |