多路IO复用--epoll

epoll

epoll 优点:

  1. 接口使用方便,不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
  2. 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中
  3. 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1)
  4. 没有数量限制: 文件描述符数目无上限

select、poll、epoll对比:
在这里插入图片描述

api

  • epoll_create: 创建epoll
int epoll_create(int size);

  • epoll_ctl : 注册监听的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

第一个参数是epoll_create()的返回值(epoll的句柄).
第二个参数表示动作,用三个宏来表示.
第三个参数是需要监听的fd.
第四个参数是告诉内核需要监听什么事

op:
EPOLL_CTL_ADD :注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL :从epfd中删除一个fd

events:
EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
EPOLLOUT : 表示对应的文件描述符可以写;
EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.

  • epoll_wait : 将就绪队列从内核态到用户态
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

events: 收就绪IO的袋子
maxevents: events的大小
timeout: 超时事件,ms
返回值:函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败

流程
在内核中创建epoll,accept创建io,判断是否加入到内核中,每一次将就绪的io切换到用户态

底层实现
epoll_create: 创建红黑树,创建回调机制,创建就绪队列
epoll_ctrl: 添加、删除、修改红黑树节点,建立对应的文件描述符的回调函数
红黑树的每个节点包含:fd文件描述符,event:对应的事件
在os层,使用驱动层的功能,完成某些回调功能,那些文件描述符已经就绪,则放入就绪队列中

epoll_wait: 负责等待就绪从就绪队列中取出节点

触发方式

水平触发(LT) :满足IO复用条件即触发
边沿触发 (ET) : 新的IO就绪事件到达即触发

如客户端一次发送数据100个字节数据到服务端,服务端一次读50个字节,
服务器设置为水平触发,则recv执行2次,
服务器设置为边缘触发,则recv执行1次

使用场景: 对于发送的数据包较大,则设置为边缘触发触发,循环读取数据;数据包较小,则设置为水平触发

code(service)

service-epoll:

运行服务端程序,将阻塞在epoll_wait,当客户端连接时,服务端接收客户端buffer,并发送buffer给客户端

	int sockfd= socket(AF_iNET,SOCK_STREAM,0);
	if(sockfd == -1) {
		return -1;
	}
	struct socketaddr_in servaddr;
	memset(&servaddr,0,sizeof(struct sockaddr_in));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(9999);

	if(-1 = bind(sockfd,(struct sockaddr*)&servaddr),sizeof(servaddr)){
		return -2;
	}
	//nonblock
	int flag = fcntl(sockfd,F_GETFL,0);
	flag |= O_NONBLOCK;
	fcntl(sockfd,F_SETFL,flag);
	
	listen(sockfd,10);
	
	//epoll
	int epfd = epoll_create(1);
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = sockfd;  // 8个字节
	epoll_ctl(epfd,EPOLL_CEL_ADD,sockfd,&ev); 
	
	struct epoll_event events[1024] = {0}; // events快递员装快递的袋子
	
	struct sockaddr_in clientaddr;
	socklen_t len = sizeof(client);
	while(1) {  // loop
	 	int nready = epoll_wait(epfd,events,1024,-1);// 取快递  最后一个参数,-1 一直等待,0,不等待,1,等待一段时间
	 	if(nready < 0 ) continue;
	 	int i = 0;
	 	// set 链接额的客户端 ready 盒子
	 	for(i = 0; i< nready;i++) {
	 		int connfd = events[i].data.fd;
	 		if(sockfd == connfd) {
	 			int clientfd = accept(sockfd,(struct sockaddr *)clientaddr,&len);
	 			if(clientfd <= 0 ) {
	 				continue;
	 			}
	 			printf("clientfd = %d",clientfd);
	 			// EPOLLET 边沿触发
	 			ev.events = EPOLLIN | EPOLLET;
	 			ev.data.fd = clientfd;
	 			epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev); 
	 		}
	 		else if (events[i].events & EPOLLIN) {
	 			char buffer[BUFFER_LENGTH] = {0};
	 			int n = recv(connfd,buffer,BUFFER_LENGTH);
	 			if(n > 0) {
	 				// 服务器先接受客户端的buffer,再将buffer发送给客户端
	 				printf("recv : %s\n",buffer);
	 				send(connfd,buffer,n,0);
	 			} else if (n == 0) {
	 				printf("close\n");
	 				// 用户搬走
	 				epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
	 				// 如不移除,connfd的值将会一直存在
	 				close(connfd);
	 			}
	 		}
	 	}
	 }
}

参考文章:
https://blog.csdn.net/weixin_45599288/article/details/123893844

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

八月的雨季997

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值