多路IO—POll函数,epoll服务器开发流程

引言

"在计算机网络编程中,多路IO技术是非常常见的一种技术。其中,Poll函数和Epoll函数是最为常用的两种多路IO技术。这两种技术可以帮助服务器端处理多个客户端的并发请求,提高了服务器的性能。本文将介绍Poll和Epoll函数的使用方法,并探讨了在服务器开发中使用这两种技术的流程和注意事项。"

 

poll函数介绍

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

(man poll 调用)

函数说明: 跟select类似, 委托内核监控可读, 可写, 异常事件

函数参数:

fds: 一个struct pollfd结构体数组的首地址

    struct pollfd {

      int fd;//要监控的文件描述符,如果fd为-1, 表示内核不再监控

     short events; //输入参数, 表示告诉内核要监控的事件, 读事件, 写事件, 异常事件 

     short revents;//输出参数, 表示内核告诉应用程序有哪些文件描述符有事件发生   

    };

events/revents:

 POLLIN:可读事件,让内核监控读事件就要写这个

 POLLOUT: 可写事件,缓冲区未满就可写

 POLLERR: 异常事件

nfds: 告诉内核监控的范围, 具体是: 数组下标的最大值+1

timeout:

=0: 不阻塞, 立刻返回

-1: 表示一直阻塞, 直到有事件发生

>0: 表示阻塞时长, 在时长范围内若有事件发生会立刻返回;

  如果超过了时长也会立刻返回

函数返回值:

>0: 发生变化的文件描述符的个数

=0: 没有文件描述符发生变化

-1: 表示异常

poll函数开发流程

1 创建socket ,得到监听文件描述符,lfd ----- socket();

2 设置端口复用----------setsockopt()

3 绑定 ------ bind()

4

struct pollfd client[1024]; client[0].fd = lfd;      // 放在哪都行,放在最俩头方便使用client[0].events = POLLIN;  //监控读事件,如果也让其监控可写事件,用或// 设置为fd 为-1 ,表示内核不在监控,这是一个初始化int maxi = 0;   //  定义最大数组下标for(int i = 0;i < 1024;i  ++){client[i].fd = -1;}//委托内核持续监控k= 0;while(1){nready = poll(client,maxi + 1,-1);//异常情况if(nready < 0 ){if(error == EINTR){continue;}break;}if(client[0].revents = POLLIN){//接受新的客户端连接k ++;cfd  = Accept(lfd,NULL,NULL);/*继续委托内核监听事件寻找在client 数组中可用位置*/for(i  = 0;i < 1024;i ++ ){if(client[i ].fd ==-1 ){client.fd[i] =  cfd;client.fd[i] = POLLIN;break;}}//客户端连接数达到最大值if(i == 1024){close(cfd);continue;   //退出,可能会有客户端连接退出,方便继续寻找}//修改client 数组下标最大值 if(maxi < i )maxi = i;if(--nready == 0 )continue;}//下面是有客户端发送数据的情况for(i = 1;i <=  maxi;i ++){//如果client数组中fd 为-1,表示已经不再让内核监控了if(client[i].fd == -1)continue;if(client[i].revents == POLLIN){sockfd =  client[i].fd;memset(buf,0x00,sizeof(buf));//read 数据n  =  Read(sockfd, buf,sizeof(buf));if(n <= 0){printf("read error or client closed,n =[%d]\n",n);close(sockfd);client[i].fd = -1;    //告诉内核不再监控}else {printf("read error,n == [%d],buf==[%s]\n,"n,buf);//发送数据给客户端Write(sockfd,buf,n);}if(--nready == 0 ){break;}}}close(lfd);}

多路IO-epoll     (重点)

将检测文件描述符的变化委托给内核去处理, 然后内核将发生变化的文件描述符对应的
事件返回给应用程序.

头文件

#include <sys/epoll.h>

函数

int epoll_create(int size) 

函数说明:创建一棵poll树,返回一个数根节点

函数参数:size:必须传一个大于0的数

返回值:返回个文件描述符,这个文件描述符就表示epoll树的树根节点

int  epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)

函数说明:将fd上的epoll树,从树上删除和修改

函数参数: 

               epfd:epoll树的树根节点

op:

               EPOLL_CTL_ADD: 添加事件节点到树 上
               EPOLL_CTL_DEL: 从树上删除事件节点
               EPOLL_CTL_MOD: 修改树上对应的事件节点

fd:要操做的文件描述符

event :
        event.events 常用的有:
              EPOLLIN: 读事件
              EPOLLOUT: 写事件   
              EPOLLERR: 错误事件
              EPOLLET: 边缘触发模式


event.fd: 要监控的事件对应的文件描述符

typedef union epoll_data{

         void  *ptr;

          int     fd;

          uint32_t  u32;

          uint64_t  u64;

 }epoll_data_t;

struct epoll_event{

       uint32  events;    / *  Epoll events */

        epoll_data data;      /* User data variable */

};

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

函数说明:等待内核返回事件发生

参数说明:

      epfd: epoll树根

      events: 传出参数, 其实是一个事件结构体数组

      maxevents: 数组大小

timeout:

      -1: 表示永久阻塞

      0: 立即返回

      >0: 表示超时等待事件

返回值:

成功: 返回发生事件的个数

失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值,

使用epoll 模型开发服务器流程

       1:创建socket,得到监听文件描述符lfd ---- socket()

       2:  设置端口复用 -----  setsockopt()

       3:  绑定 ------ bind()

       4:  监听 -------- listen() 

       5.  创建一棵epoll树      

开发完整的代码

//EPOLL 模型测试
#include "wrap.h"
#include <sys/epoll.h> 
#include <ctype.h>
int main()
{int ret;int n;int nready;int lfd;int cfd;int sockfd;char buf[1024];socklen_t  socklen;struct sockaddr_in svraddr;struct epoll_event ev;struct epoll_event events[1024];int k;int i;//创建socketlfd = Socket(AF_INET,SOCK_STREAM,0);//设置文件描述符为端口复用int opt = 1;setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));//绑定svraddr.sin_family = AF_INET;svraddr.sin_addr.s_addr = htonl(INADDR_ANY);svraddr.sin_port = htons(8888);Bind(lfd,(struct sockaddr *)&svraddr,sizeof(struct sockaddr_in));//ListenListen(lfd,128);//创建一棵epoll树int epfd = epoll_create(1024);if(epfd < 0 ){perror("create epoll error");return -1;} ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);   //lfd 对应的事件节点上树while(1){nready = epoll_wait(epfd,events,1024,-1);  //等待内核返回事件 if(nready < 0){perror("epoll_wait error");if(nready == EINTR)   //判断是否收到了中断信号 {continue;}break;}for(i = 0;i < nready;i ++)   //小于发生事件的个数 {//有客户端连接发来请求 sockfd = events[i].data.fd;if(sockfd == lfd)        {cfd = Accept(lfd,NULL,NULL);ev.data.fd = cfd;ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);}//有客户端发送数据过来else {memset(buf,0x00,sizeof(buf));n = Read(sockfd,buf,sizeof(buf));if(n <= 0){close(sockfd);epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); //把sockfd从epfd树上删除 } else {for(k = 0;k < n;k ++){buf[k] = toupper(buf[k]);  //返回大写 }Write(sockfd,buf,n);}}}}close(epfd);close(lfd);return 0;
}   

epoll 的两种模式 ET 和 LT 模式
 

epoll 的LT模式:

     epoll 默认情况是LT模式,在这种情况下,如果读数据一次性没有读完,

     缓冲区还有可读数据,则epoll_wait还会再次通知。

epoll 的ET模式:

    如果将epoll设置为ET模式,若读数据的时候一次性没有读完,则epoll_wait不再通知

    直到下次有新的数据

用ET模式下,为了防止第二个客户端可以正常连接,并且发送数据,需要将socket设置为非阻塞模式

ET设置了非阻塞模式是因为使用了边缘触发模式(EPOLLET)。在边缘触发模式下,当有数据可读时,只会触发一次EPOLLIN事件,如果该次读取没有将缓冲区中的数据全部读取完毕,下次还是会触发EPOLLIN事件。因此,为了保证每次读取完整的数据,需要将socket设置为非阻塞模式,避免在缓冲区没有全部读取完毕时进行阻塞。

代码:

//EPOLL 模型测试 ET
#include "wrap.h"
#include <sys/epoll.h> 
#include <ctype.h>
#include <fcntl.h> 
int main()
{int ret;int n;int nready;int lfd;int cfd;int sockfd;char buf[1024];socklen_t  socklen;struct sockaddr_in svraddr;struct epoll_event ev;struct epoll_event events[1024];int k;int i;//创建socketlfd = Socket(AF_INET,SOCK_STREAM,0);//设置文件描述符为端口复用int opt = 1;setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int));//绑定svraddr.sin_family = AF_INET;svraddr.sin_addr.s_addr = htonl(INADDR_ANY);svraddr.sin_port = htons(8888);Bind(lfd,(struct sockaddr *)&svraddr,sizeof(struct sockaddr_in));//ListenListen(lfd,128);//创建一棵epoll树int epfd = epoll_create(1024);if(epfd < 0 ){perror("create epoll error");return -1;} ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);   //lfd 对应的事件节点上树while(1){nready = epoll_wait(epfd,events,1024,-1);  //等待内核返回事件 if(nready < 0){perror("epoll_wait error");if(nready == EINTR)   //判断是否收到了中断信号 {continue;}break;}for(i = 0;i < nready;i ++)   //小于发生事件的个数 {//有客户端连接发来请求 sockfd = events[i].data.fd;if(sockfd == lfd)        {cfd = Accept(lfd,NULL,NULL);ev.data.fd = cfd;ev.events = EPOLLIN | EPOLLET;  //epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);//将cfd设置为非阻塞模式int flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;   //O_NONBLOCK(非阻塞)标志位置为1。fcntl(cfd, F_SETFL, flag);}//有客户端发送数据过来else {memset(buf,0x00,sizeof(buf));while(1){n = Read(sockfd,buf,sizeof(buf));printf("n == [%d]\n",n);if(n == -1){printf("read over,n == [%d]\n",n);break;}if(n < 0 || (n <0 && n!=-1))    //对方关闭连接,或者异常的情况 {printf("n == [%d],buf == [%s]\n",n,buf);close(sockfd);epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,NULL); //把sockfd从epfd树上删除 break;} else {printf("n == [%d],buf == [%s]\n",n,buf);for(k = 0;k < n;k ++){buf[k] = toupper(buf[k]);  //返回大写 }Write(sockfd,buf,n);}}}}}close(epfd);close(lfd);return 0;
}   

图解epoll反应堆流程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/155746.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript基础知识19——循环结构:while循环

哈喽&#xff0c;你好&#xff0c;我是雷工。 本节学习JavaScript基础语法的循环结构&#xff1a;while循环&#xff0c;以下为学习笔记。 while循环 循环概念&#xff1a;重复执行一些操作&#xff1b; 循环特征&#xff1a;不断地重复&#xff1b; while&#xff1a;在…期间…

NEFU数字图像处理(三)图像分割

一、图像分割的基本概念 1.1专有名词 前景和背景 在图像分割中&#xff0c;我们通常需要将图像分为前景和背景两个部分。前景是指图像中我们感兴趣、要分割出来的部分&#xff0c;背景是指和前景不相关的部分。例如&#xff0c;对于一张人物照片&#xff0c;人物就是前景&…

浅析Redis大Key | 京东云技术团队

一、背景 在京东到家购物车系统中&#xff0c;用户基于门店能够对商品进行加车操作。用户与门店商品使用Redis的Hash类型存储&#xff0c;如下代码块所示。不知细心的你有没有发现&#xff0c;如果单门店加车商品过多&#xff0c;或者门店过多时&#xff0c;此Key就会越来越大…

三、 链表

一、链表的定义 链表是一种动态数据结果&#xff0c;内存分配不是在创建链表时一次性完成的&#xff0c;每添加一个节点&#xff0c;分配一次内存&#xff0c;由于没有闲置的内存&#xff0c;链表的空间效率高于数组 二、定义单向链表 struct ListNode {int m_nValue;ListNo…

架构设计之大数据架构(Lambda架构、Kappa架构)

大数据架构 一. 大数据技术生态二. 大数据分层架构三. Lambda架构3.1 Lambda架构分解为三层3.2 优缺点3.3 实际案例 四. Kappa架构4.1 结构图4.2 优缺点4.3 实际案例 五. Lambda架构与Kappa架构对比 其它相关推荐&#xff1a; 系统架构之微服务架构 系统架构设计之微内核架构 鸿…

CentOS停更沉寂,RHEL巨变限制源代:Docker容器化技术的兴起助力操作系统新格局

一、概述 操作系统是计算机系统的核心软件&#xff0c;它管理和控制着计算机的硬件和软件资源&#xff0c;为用户和应用程序提供了一个统一、高效、安全的运行环境。操作系统的发展历史也是计算机技术的发展历史的重要组成部分&#xff0c;它见证了计算机从单机到网络&#xf…

宝塔面板安装Python和Flask(新版Python项目)

&#xff08;一&#xff09;宝塔面板的项目菜单&#xff0c;打开Python项目的“项目版本管理” 安装Python版本3.10.0。 会创建一个Python版本的文件夹www/server/pyproject_evn/versions/ 会创建一个Python虚拟环境的文件夹www/server/pyproject_evn/python_venv/ &#xf…

C#__委托delegate

委托存储的是函数的引用&#xff08;把某个函数赋值给一个委托类型的变量&#xff0c;这样的话这个变量就可以当成这个函数来进行使用了&#xff09; 委托类型跟整型类型、浮点型类型一样&#xff0c;也是一种类型&#xff0c;是一种存储函数引用的类型 using System.Reflec…

[论文阅读]Voxel R-CNN——迈向高性能基于体素的3D目标检测

Voxel R-CNN Voxel R-CNN: Towards High Performance Voxel-based 3D Object Detection 迈向高性能基于体素的3D目标检测 论文网址&#xff1a;Voxel R-CNN 论文代码&#xff1a;Voxel R-CNN 简读论文 该论文提出了 Voxel R-CNN&#xff0c;这是一种基于体素的高性能 3D 对象…

可视化 | 数据可视化降维算法梳理

文章目录 &#x1f4da;数据描述&#x1f407;iris&#x1f407;MNIST &#x1f4da;PCA&#x1f407;算法流程&#x1f407;图像描述 &#x1f4da;Kernel-PCA&#x1f407;算法流程&#x1f407;图像描述 &#x1f4da;MDS&#x1f407;算法流程&#x1f407;图像描述 &#…

我在Vscode学OpenCV 初步接触

OpenCV是一个开源的计算机视觉库&#xff0c;可以处理图像和视频数据。它包含了超过2500个优化过的算法&#xff0c;用于对图像和视频进行处理&#xff0c;包括目标识别、面部识别、运动跟踪、立体视觉等。OpenCV支持多种编程语言&#xff0c;包括C、Python、Java等&#xff0c…

20.3 OpenSSL 对称AES加解密算法

AES算法是一种对称加密算法&#xff0c;全称为高级加密标准&#xff08;Advanced Encryption Standard&#xff09;。它是一种分组密码&#xff0c;以128比特为一个分组进行加密&#xff0c;其密钥长度可以是128比特、192比特或256比特&#xff0c;因此可以提供不同等级的安全性…