栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

IO复用函数

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

IO复用函数

I/O复用技术使得程序能够同时监听多个文件描述符,这对于提高程序的性能至关重要。

TCP 服务器同时要处理监听套接字和连接套接字。

服务器要同时处理 TCP 请求和 UDP 请求。

程序要同时处理多个套接字。

客户端程序要同时处理用户输入和网络连接。

服务器要同时监听多个端口。

需要指出的是,I/O 复用虽然能同时监听多个文件描述符,但它本身是阻塞的。并且当 多个文件描述符同时就绪时,如果不采取额外的措施,程序就只能按顺序依处理其中的每一 个文件描述符,这使得服务器看起来好像是串行工作的。如果要提高并发处理的能力,可以 配合使用多线程或多进程等编程方法。

select

select 系统调用的用途是:在一段指定时间内,监听用户感兴趣的文件描述符的可读、 可写和异常等事件。

select接口

1. #include 
2.
3. int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
4. 

应用:

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 #include
  8 #include
  9 #include
 10 #define MAX 10
 11 int socket_init();
 12 
 13 void poll_fds_init(struct pollfd fds//封装 对poll结构数组的初始化
 14 {
 15     for(int i=0;i 

select/poll性能总结

在用户空间创建集合或数组 来存放描述符及事件 ,每一次调用select 或 poll 时 ,需要将数组和集合当作参数传进去,每循环调用一次意味着从用户空间往内核空间拷贝一次数据结构;

内核实现 :在内核中 以 轮询 的方式实现,因此在内核中检测是否有事件就绪的时间复杂度为O(n);

函数调用完成返回后,无法确定具体哪个描述符有事件就绪,仅知道有个描述符上有事件就绪,此时便需要用户再次进行遍历查找具体是哪个描述符有事件,此时时间复杂度也为O(n);

结论:无法应对大量描述符。

epoll

epoll 的接口

epoll 是 Linux 特有的 I/O 复用函数。它在实现和使用上与 select、poll 有很大差异。为了解决大量文件描述符的问题。

首 先,epoll 使用一组函数来完成任务,而不是单个函数。

其次,epoll 把用户关心的文件描述 符上的事件放在内核里的一个事件表中。从而无需像 select 和 poll 那样每次调用都要重复传 入文件描述符或事件集。

但 epoll 需要使用一个额外的文件描述符,来唯一标识内核中的这 个事件表。epoll 相关的函数如下:

内核实现:注册回调函数的方式实现。事件复杂度O(1)

返回方式:将就绪个数及具体的就绪的文件描述符信息全部返回 。时间复杂度O(1)

1. #include 
2.
3. int epoll_create(int size);  //创建内核事件表,存放描述符及事件,数据结构:红黑树
​
4. 
8.
9. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//用于操作内核事件表,每个描述符只添加一次
10. 
34.
35. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);//用于在一段超时时间内等待一组文件描述符上的事件,返回就绪的描述符的个数及具体的描述符信息
36.
37. 

epoll应用

实现服务器端:

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 #include
  8 #include
  9 #include
 10 #define MAX 10
 11 int socket_init();               //设置套接字
 12 void epoll_add(int epfd,int fd)  //封装一个往内核事件表添加描述符的函数
 13 {
 14     struct epoll_event ev;  //定义事件结构体
 15     ev.data.fd=fd;          //写入文件描述符
 16     ev.events=EPOLLIN;      //写入关注事件 :读
 17     if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1) //往内核时间表中加入该文件描述符信息
 18     {
 19         printf("epoll ctl erron");  
 20     }
 21 }
 22 void epoll_del(int epfd,int fd)  //封装一个从内核事件表删除描述符的函数
 23 {
 24     if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1) //从内核事件表中删除该文件描述符
 25     {
 26         perror("epoll ctl del errn");
 27     }
 28 }
 29 int main()
 30 {
 31     int sockfd=socket_init();  //创建监听套接字
 32     assert(sockfd!=-1);
 33 
 34     int epfd=epoll_create(MAX); //创建内核事件表 大小为MAX
 35     assert(epfd!=-1);
 36 
 37     epoll_add(epfd,sockfd); //加入监听套接字
 38 
 39     struct epoll_event evs[MAX]; //创建事件结构存储所有就绪的事件
 40 
 41     while(1)
 42     {
 43         int n=epoll_wait(epfd,evs,MAX,5000); //等待有事件就绪,可能会阻塞
 44         if(n==-1)
 45         {
 46             printf("epoll wait errn"); 
 47         }
 48         else if(n==0)
 49         {
 50             printf("time outn");
 51         }
 52         else
 53         {
 54             for(int i=0;i 

客户端

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 #include
  8 
  9 int main()
 10 {
 11     int sockfd=socket(AF_INET,SOCK_STREAM,0);
 12     assert(sockfd!=-1);
 13 
 14     struct sockaddr_in saddr;
 15     saddr.sin_family=AF_INET;
 16     saddr.sin_port=htons(6000);
 17     saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
 18 
 19 
 20     int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
 21 
 22     assert(res!=-1);
 23     while(1)
 24     {
 25     char buff[128]={0};
 26 
 27     printf("intput:n");
 28     fgets(buff,128,stdin);
 29     if(strncmp(buff,"end",3)==0)
 30     {
 31         break;
 32     }
 33     send(sockfd,buff,strlen(buff),0);
 34     memset(buff,0,128);
 35     recv(sockfd,buff,127,0);
 36     printf("buff=%sn",buff);
 37     }
 38     close(sockfd);
 39 
 40 }
~       

LT 和 ET 模式

对文件描述符有两种操作模式:LT(Level Trigger,电平触发)模式和 ET(Edge Trigger,边沿触发)模式。

LT模式 :

概念:对于 LT 模式操作的文件描述符,当 检测到其上有事件发生并将此事件通知 应用程序后,应用程序可以不处理完该事件。这样,当应用程序下一次调用检测时, 还会再次向应用程序通告此事件,直到该事件被处理完。即对于数据的处理不需要一次性必须处理完。

注:select / poll / epoll 都具有这种模式。

ET 模式:

注:只有epoll 具有这种模式。

当往 epoll 内核事件表中注册一个文 件描述符上的 EPOLLET 事件时,epoll 将以高效的 ET 模式来操作该文件描述符。

概念:对于 ET 模式操作的文件描述符,当 epoll_wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的 epoll_wait 调用将不再向应用程序 通知这一事件。所以 ET 模式在很大程度上降低了同一个 epoll 事件被重复触发的次数,因 此效率比 LT 模式高。

应用:

将用epoll实现的服务器端从LT模式改成ET模式

  1 #include
  2 #include
  3 #include
  4 #include
  5 #include
  6 #include
  7 #include
  8 #include
  9 #include
 10 #define MAX 10
 11 int socket_init();
 12 void epoll_add(int epfd,int fd)
 13 {
 14     struct epoll_event ev;
 15     ev.data.fd=fd;
 16     ev.events=EPOLLIN|EPOLLET;  // 设置 ET 模式 :按位或 ET模式
 17     if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
 18     {
 19         printf("epoll ctl erron");
 20     }
 21 }
 22 void epoll_del(int epfd,int fd)
 23 {
 24     if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
 25     {
 26         perror("epoll ctl del errn");
 27     }
 28 }
 29 int main()
 30 {
 31     int sockfd=socket_init();
 32     assert(sockfd!=-1);
 33 
 34     int epfd=epoll_create(MAX);
 35     assert(epfd!=-1);
 36 
 37     epoll_add(epfd,sockfd);
 38 
 39     struct epoll_event evs[MAX];
 40 
 41     while(1)
 42     {
 43         int n=epoll_wait(epfd,evs,MAX,5000);
 44         if(n==-1)
 45         {
 46             printf("epoll wait errn");
 47         }
 48         else if(n==0)
 49         {
 50             printf("time outn");
 51         }
 52         else
 53         {
 54             for(int i=0;i 

优化:

由于ET模式下,数据存在丢失的可能,为了在只提式一次的情况下,将所有的数据全部都出来,因此需要进行以下优化。

    描述符设置成非阻塞

    循环处理

 01 #include  //描述符设置成非阻塞
 02 #include  //循环处理
​
 11 int socket_init();
 12 void setnonblock(int fd)//1. 封装函数 将文件描述符设置成非阻塞模式
 13 {
 14    int oldfl=fcntl(fd,F_GRTFL); //获取之前文件描述符属性
 15    int newfl=oldfl|O_NONBLOCK;  //增添非阻塞模式属性 ,当文件没有数据读阻塞时会返回-1
 16    if(fcntl(fd,F_SETFL,newfl)==-1)//给文件描述符设置增添后的新属性
 15    {
 14         printf("fcntl errorn");
 15    }
 15 }
​
 12 void epoll_add(int epfd,int fd)
 13 {
 14     struct epoll_event ev;
 15     ev.data.fd=fd;
 16     ev.events=EPOLLIN|EPOLLET;  // 设置 ET 模式 :按位或 ET模式
 17     if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
 18     {
 19         printf("epoll ctl erron");
 20     }
 22     setnonblock(fd); //调用函数 给文件描述符 设置非阻塞形式
 21 }
 22 void epoll_del(int epfd,int fd)
 23 {
 24     if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
 25     {
 26         perror("epoll ctl del errn");
 27     }
 28 }
 29 int main()
 30 {
 31     int sockfd=socket_init();
 32     assert(sockfd!=-1);
 33 
 34     int epfd=epoll_create(MAX);
 35     assert(epfd!=-1);
 36 
 37     epoll_add(epfd,sockfd);
 38 
 39     struct epoll_event evs[MAX];
 40 
 41     while(1)
 42     {
 43         int n=epoll_wait(epfd,evs,MAX,5000);
 44         if(n==-1)
 45         {
 46             printf("epoll wait errn");
 47         }
 48         else if(n==0)
 49         {
 50             printf("time outn");
 51         }
 52         else
 53         {
 54             for(int i=0;i
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/748534.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号