多路IO复用技术--epoll模型

本文详细介绍了Linux下的epoll多路IO复用技术,包括epoll_create、epoll_ctl和epoll_wait等关键函数的分析,探讨了epoll的ET和LT两种工作模式及其默认模式,并讨论了epoll的优化策略,最后提供了epoll的使用源码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.epoll功能描述

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

2.eopll相关函数分析

2.1 int epoll_create(int size)

	2.1.1 函数说明: 创建一个树根
	2.1.2 参数说明:
			size: 最大节点数, 此参数在linux 2.6.8已被忽略, 但必须传递一个大于0的数。
	2.1.3 返回值:
			成功: 返回一个大于0的文件描述符, 代表整个树的树根。
			失败: 返回-1, 并设置errno值。

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

	2.2.1 函数说明: 将要监听的节点在epoll树上添加, 删除和修改。
	2.2.2 参数说明:
		epfd: epoll树根
		op:  
			EPOLL_CTL_ADD: 添加事件节点到树上
			EPOLL_CTL_DEL: 从树上删除事件节点
			EPOLL_CTL_MOD: 修改树上对应的事件节点
		fd: 事件节点对应的文件描述符
		event: 要操作的事件节点
		struct epoll_event的结构体如下:
           typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* Epoll 监听的事件类型 */
               epoll_data_t data;        /* 存放用户的相关变量信息 */
           };
	     events常用的有:
				 EPOLLIN: 读事件
				 EPOLLOUT: 写事件
				 EPOLLERR: 错误事件
		         EPOLLET: 边缘触发模式
		fd: 要监控的事件对应的文件描述符
	2.2.3 返回值:
			成功: 返回0
			失败: 返回-1, 并设置errno值

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

	2.3.1 函数说明:等待内核返回事件发生
	2.3.2 参数说明:
			epfd: epoll树根
			events: 传出参数, 其实是一个事件结构体数组
			maxevents: 数组大小
			timeout: 单位:毫秒ms
			-1: 表示永久阻塞
			0: 立即返回
			>0: 表示超时等待事件
	2.3.3 返回值:
			成功: 返回发生事件的个数。
			失败: 若timeout=0, 没有事件发生则返回; 返回-1, 设置errno值。

3. epoll的两种工作模式

3.1 epoll的两种模式ET和LT模式

	3.1.1 水平触发(LT): 高电平代表1
		只要缓冲区中有数据, 就一直通知。
	3.1.2 边缘触发(ET): 电平有变化就代表1
		缓冲区中有数据只会通知一次, 之后再有数据才会通知(若是读数据的时候没有读完, 则剩余的数据不会再通知, 直到有新的数据到来)。

3.2 epoll默认工作模式

	epoll默认工作模式是LT模式。

4.epoll优化

	4.1 ET模式由于只通知一次, 所以在读的时候要循环读, 直到读完, 但是当读完之后read就会阻塞, 所以应该将该文件描述符设置为非阻塞模式。
	4.2 边缘非阻塞模式: 提高效率。

5.epoll使用源码

	5.1 源码功能介绍:epoll服务器等待客户端的连接到来,接收客户端发过来的信息并回复。
	5.2 源码展示
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <sys/epoll.h>
#include <fcntl.h>

int main()
{
	int n;
	int i;
	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];

	//创建socket
	lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd <=0)
	{
		return -1;
	}
	//设置文件描述符为端口复用
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));

	//绑定bind
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	svraddr.sin_port = htons(8888);
	if(bind(lfd, (struct sockaddr *)&svraddr, sizeof(struct sockaddr_in)) < 0)
	{
		close(lfd);
		return -1;
	}
	
	//监听listen
	if(listen(lfd, 128) < 0)
	{
		close(lfd);
		return -1;
	}
	
	//创建epoll根节点
	int epfd = epoll_create(1024);
	if(epfd<0)
	{
		perror("epoll_create error");
		close(lfd);
		return -1;
	}

	//将lfd上epoll树
	//int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
	ev.data.fd = lfd;
	ev.events = EPOLLIN;	
	epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

	//循环等待事件的发生 
	while(1)
	{
		//int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
		nready = epoll_wait(epfd, events, 1024, -1);	
		if(nready<0)
		{
			perror("epoll_wait error");
			if(errno==EINTR)
			{
				continue;
			}
			exit(1);
		}
		printf("nready==[%d]\n", nready);

		for(i=0; i<nready; i++)
		{
			sockfd = events[i].data.fd;
			//有客户端连接请求
			if(sockfd==lfd)
			{
				cfd = accept(sockfd, NULL, NULL);	

				//将新的通信文件描述符上树
				ev.data.fd = cfd;
				//ev.events = EPOLLIN; //模式是水平触发模式
			    ev.events = EPOLLIN | EPOLLET; //模式是边缘触发模式
				epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);

				//将cfd设置为非阻塞模式
				int flags = fcntl(cfd, F_GETFL, 0);
				flags |= O_NONBLOCK;
				fcntl(cfd, F_SETFL, flags);
				continue;
			}

			//下面是有数据发来的情况
			while(1)
			{
				memset(buf, 0x00, sizeof(buf));
				n = read(sockfd, buf, 3);//与使用recv函数(常用)效果相同
				if(n==-1)//缓冲区中的数据已经读完了
				{
					if(errno==EAGAIN)
					{
						printf("read over, n==[%d]\n", n);
						break;
					}
					else
					{
						printf("read error, n==[%d]\n", n);
						close(sockfd);
						//将sockfd对应的事件下树
						epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
						break;
					}
				}
				else if(n==0)
				{
					printf("client closed, n==[%d]\n", n);
					close(sockfd);
					//将sockfd对应的事件下树
					epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
					break;
				}
				else if(n>0)
				{
					printf("read over, n==[%d],buf==[%s]\n", n, buf);
					write(sockfd, buf, n);//与使用send函数(常用)效果相同
				}
			}
		}
	}

	//关闭文件根节点和监听文件描述符
	close(epfd);
	close(lfd);

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值