前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >多路IO—POll函数,epoll服务器开发流程

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

作者头像
莫浅子
发布2023-11-01 09:25:59
2910
发布2023-11-01 09:25:59
举报
文章被收录于专栏:莫浅子的学习笔记

引言

"在计算机网络编程中,多路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

代码语言:javascript
复制
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树      

开发完整的代码

代码语言:javascript
复制
//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;
	
	//创建socket
	lfd = 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));
	
	//Listen
	Listen(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设置为非阻塞模式,避免在缓冲区没有全部读取完毕时进行阻塞。

代码:

代码语言:javascript
复制
//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;
	
	//创建socket
	lfd = 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));
	
	//Listen
	Listen(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反应堆流程

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • poll函数介绍
  • poll函数开发流程
  • 多路IO-epoll     (重点)
  • 使用epoll 模型开发服务器流程
  • epoll 的两种模式 ET 和 LT 模式
  • 图解epoll反应堆流程
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档