当前位置: 首页 > news >正文

I/O模型:用poll实现多路复用I/O(linux下C语言版)

1. 多路复用I/O

1. 在UNIX/Linux下主要有4种I/O 模型:阻塞I/O、非阻塞I/O、多路复用I/O、信号驱动I/O,在本文我们主要讲解多路复用I/O

2.  为什么要选择多路复用I/O

(1)应用程序中同时处理多路输入输出流时,若采用阻塞模式,将得不到预期的目的;

​(2)若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;

​(3)若设置多个进程,分别处理一条数据通路,将新产生进程间的同步与通信问题,则会使程序变得更加复杂;

(4)比较好的方法是使用I/O多路复用,其基本思想是:

​        先构造一张有关文件描述符的表,然后调用一个函数,当这些文件描述符中的一个或多个已准备好进行I/O时,函数才返回,函数返回时告诉进程那个描述符已就绪,可以进行I/O操作。

3. 大致实现步骤:

    1. 创建文件描述符的集合/表, 把想要监测的文件描述符加入到集合中

    2. 监测集合中是否有事件产生

    3. 判断是谁产生了什么事件

    4. 处理事件

        总之就是调用函数来操作文件描述符,那怎么调函数呢?调什么函数呢?本文我们用poll函数来实现I/O多路复用,让我们一起来看看详细操作吧。

 2. poll函数介绍

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

作用: 监测集合中是否有事件产生

参数:

        fds --- 想要监测的文件描述符集合

                struct pollfd {

                        int fd; /* file descriptor */

                        short events; /* requested events */

                                POLLIN -- 读事件         POLLOUT- 写事件

                        short revents; /* returned events */

                };

        nfds --- 要监测的文件描述符个数

        timeout --- 超时设置,单位毫秒, -1:阻塞模式

返回值:

        >0: 有事件产生

        =0: 有超时设置,且再设定的实践范围内没有事件产生返回

        -1: 失败,并设置errno

3. 举例实现select函数的I/O多路复用

这里我们举两个例子来用select实现I/O多路复用

    1. 同时检测键盘和鼠标,获取数据来自哪里

    2. 在客户端/服务器模型中,服务器同时监测键盘、监听套接字和通信套接字,接收键盘输入的数据、客户端的发送的数据,并打印在终端上

​例1:监测来源于键盘和鼠标的数据

#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <poll.h>int main(int argc, char *argv[])
{ //0.打开设备int fd = open("/dev/input/mouse0", O_RDONLY);if(0 > fd){perror("open");return -1;}//1.创建文件描述符集合,把想要监测的文件描述符加入集合中struct pollfd fds[2];memset(fds, 0, sizeof(fds));fds[0].fd = 0;    //把键盘加入集合fds[0].events = POLLIN;fds[1].fd = fd;   //把鼠标加入集合fds[1].events = POLLIN;//2.监测是否有事件产生int retval;char buf[1024];while(1){retval = poll(fds, 2, 1000); //以阻塞形式监测if(retval < 0){perror("poll");break;}else if(retval == 0){printf("timeout...\n");continue;}//3.判断是谁产生了事件if(fds[0].revents == POLLIN) //监测一个事件时可用=={//4.处理事件read(0, buf, sizeof(buf));buf[strlen(buf)-1] = '\0';if(strcmp(buf, "exit") == 0)break;printf("Data from keyboard\n");}if(fds[1].revents & POLLIN) //更推荐使用&{//4.处理事件read(fd, buf, sizeof(buf));printf("Data from mouse\n");}}return 0;
} 

例2:服务器接收客户端的发送的数据并打印在终端上

(1)server.c

服务器端代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <poll.h>int main(int argc, char *argv[])
{ //创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("socket");return -1;}printf("socket create success\n");//绑定本机地址和端口struct sockaddr_in srvaddr;memset(&srvaddr, 0, sizeof(srvaddr));srvaddr.sin_family = AF_INET;srvaddr.sin_port = htons(60621);srvaddr.sin_addr.s_addr = inet_addr("192.168.2.154");if(0 > bind(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr))){perror("bind");return -1;}printf("bind success\n");//设置监听套接字if(0 > listen(sockfd, 1)){perror("listen");return -1;}printf("listen success\n");//1.创建文件描述符集合,把想要监测的文件描述符加入集合中struct pollfd fds[5]; //创建可装5个文件描述符的集合int m; //将集合进行初始化for(m = 0; m < 5; m++){fds[m].fd = -1; //此时集合位置m空闲,可被使用fds[m].events = 0;fds[m].revents = 0;}fds[0].fd = 0;fds[0].events = POLLIN;fds[1].fd = sockfd;fds[1].events = POLLIN;//2.循环监测是否有事件产生int connfd;char buf[1024];int ret;int n;while(1){int retval = poll(fds, 5, -1);if(retval < 0){perror("poll");break;}//3.判断是谁产生了事件int i = 0;//从0到maxfd遍历所有文件描述符判断是否是i产生事件for(i; i < 5; i++){if(fds[i].revents & POLLIN){if(fds[i].fd == 0) //键盘产生了事件{4.处理事件:打印键盘输入的数据memset(buf, 0, sizeof(buf));read(0, buf, sizeof(buf));buf[strlen(buf)-1] = '\0';if(strcmp(buf, "exit") == 0)return 0;printf("keyboard: %s\n", buf);}else if(sockfd == fds[i].fd)//监听套接字产生了事件{//4.处理事件:接收客户端的连接, 并生成通信套接字connfd = accept(sockfd, NULL, NULL);if(0 > connfd){perror("accept");return -1;}printf("accept success\n");//寻找空位for(n = 0; n < 5; n++){if(fds[n].fd == -1) {fds[n].fd = connfd;fds[n].events = POLLIN;break;}}}else //通信套接字产生了事件{//4.处理事件:与客户端通信,接收客户端的数据并打印,再给客户端发送数据ret = read(fds[n].fd, buf, sizeof(buf));if(0 > ret){perror("recv");break;}else if(0 == ret){printf("client close\n");//从集合中清除通信套接字close(fds[n].fd);fds[n].fd = -1;fds[n].events = 0;fds[n].revents = 0;break;}printf("recv: %s\n", buf);//设置退出条件if(strcmp(buf, "exit") == 0){//从集合中清除通信套接字close(fds[n].fd);fds[n].fd = -1;fds[n].events = 0;fds[n].revents = 0;break;}}}}}//关闭套接字close(sockfd);return 0;
} 

(2)client.c

客户端代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>int main(int argc, char *argv[])
{ //创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(0 > sockfd){perror("socket");return -1;}printf("socket create success\n");//主动连接服务器struct sockaddr_in srvaddr;memset(&srvaddr, 0, sizeof(srvaddr));srvaddr.sin_family = AF_INET;srvaddr.sin_port = htons(60621);srvaddr.sin_addr.s_addr = inet_addr("192.168.2.154");if(0 > connect(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr))){perror("connect");return -1;}//与服务器通信:将键盘输入的数据发送给服务器int ret;char buf[1024];while(1){printf("send: ");fgets(buf, sizeof(buf), stdin);buf[strlen(buf)-1] = '\0';if(0 > send(sockfd, buf, sizeof(buf), 0)){perror("send");break;}//设置退出条件if(strcmp(buf, "exit") == 0)break;}//关闭套接字close(sockfd);return 0;
} 

(3)运行注意事项

1. 我们在第一个终端编译 gcc server.c -o s  //重命名执行文件名

2. 在第二个终端(另外开一个终端)编译 gcc client.c -o c

(为什么要重命名执行文件名?这是因为如果像平常一样进行编译,两个程序编译后生成同一个文件名(a.out),互相覆盖,无法同时执行,所以,通常我们会用 -o 指定不同的输出文件名)

3. 在第一个终端执行 ./s

4. 在第二个终端执行 ./c

4. 总结

1. 使用 poll 函数实现 I/O 多路复用无文件描述符数量限制​​,支持数万个文件描述符,适用于高并发场景

​​2. ​poll 函数通过 poll 结构体数组直接监听事件,​​无需每次调用都重置文件描述符集合​​,事件检测更高效

3. 但 poll 函数仍需遍历所有文件描述符​​和 select 一样,poll 返回后仍需遍历整个 pollfd 数组检查就绪事件,​​时间复杂度仍是 O(n)​​

4. 除了 poll 函数,还可用 select、epoll 来实现I/O多路复用,它们各有各的特点,想了解更多的同学欢迎浏览主页相关文章!

感谢观看!如有疑问欢迎提出!

  ----香菜小猫祝这位uu天天开心----

http://www.dtcms.com/a/442664.html

相关文章:

  • 石家庄做网站哪家好北京网站建设的价格天
  • 网站设计与制作湛江网站建设方案书
  • 个人怎样建网站设计师网页设计作品
  • 网站建设好处网站内容规划
  • LeetCode 刷题【98. 验证二叉搜索树】
  • 使用 python-docx 库操作 word 文档(1):文件操作
  • gRPC从0到1系列【18】
  • 汕头优化网站杭州品牌vi设计公司
  • 网站推广网站制作网站建设公司o2o网站做推广公司
  • 嘉兴网站排名公司网站建设三原则
  • 160. 相交链表 LeetCode 热题 HOT 100
  • 厦门论坛网站建设东莞东城邮编
  • 网站改版分析ip代理池
  • 旧房翻新装修公司排名自己的网站怎样做优化
  • 自适应h5网站建筑业企业资质标准建设部网站
  • pc端网站建设相关查阅资料网络营销的网站分类有哪些
  • 高端网站建设公司报价机票网站制作
  • 简单的个人网站模板h5制作工具免费版
  • Base64 原理与 C++ 实现
  • 网站推广优化教程手机端网页设计尺寸规范
  • Web3 RWA 品牌的价值跃迁:从竞争到共赢的网络共建
  • 如何进入网站管理员界面wordpress需要的系统
  • 建个企业网站还是开个淘宝店老域名重新做网站
  • thinkphp企业网站源码全国建设网站
  • 长春火车站什么时候通车徐州网站建设商城制作网站推广seo
  • C++ 11包装库,lambda的用法
  • 湖南省百川电力建设有限公司网站四川万景建设工程有限公司网站
  • 网页设计与网站建设案例教程wordpress 教育模版
  • 《P2679 [NOIP 2015 提高组] 子串》
  • 011 Rust数组