从来好事天生俭,自古瓜儿苦后甜。这篇文章主要讲述#导入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); // 检查集合中指定的文件描述符是否可以读写 |
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 = https://www.songbingjia.com/android/%d//n",ev.code,ev.value); ??? ? ? ? ? ? ??return 0; ? |
【#导入Word文档图片# Linux下IO多路复用: Selectpollepoll】查看系统的标准文件描述符: [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 |
#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); |
2.2 poll函数 函数格式如下所示:
# include <
poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout); |
struct pollfd int fd; /* 文件描述符 */ short events; /* 等待的事件 */ short revents; /* 实际发生了的事件 */ ; |
POLLIN | 有数据可读。 |
POLLRDNORM | 有普通数据可读。 |
POLLRDBAND | 有优先数据可读。 |
POLLPRI | 有紧迫数据可读。 |
POLLOUT | 写数据不会导致阻塞。 |
POLLWRNORM | 写普通数据不会导致阻塞。 |
POLLWRBAND | 写优先数据不会导致阻塞 |
POLLMSGSIGPOLL | 消息可用。 |
POLLER | 指定的文件描述符发生错误。 |
POLLHUP | 指定的文件描述符挂起事件。 |
POLLNVAL | 指定的文件描述符非法。 |
使用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()就像它的名字那样,一旦选举出来,立即返回。
- 返回值和错误代码
EBADF | 一个或多个结构体中指定的文件描述符无效。 |
EFAULTfds | 指针指向的地址超出进程的地址空间。 |
EINTR | 请求的事件之前产生一个信号,调用可以重新发起。 |
EINVALnfds | 参数超出PLIMIT_NOFILE值。 |
ENOMEM | 可用内存不足,无法完成请求。 |
#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) 推荐阅读
|