曾经困扰我的epoll-et

本文探讨了北京大学同学的性能测试中,如何在epoll的边沿触发(ET)模式下,正确设置socket为非阻塞并避免漏报消息。通过实例代码展示了EPOLLET和O_NONBLOCK的关键使用,并解释了ET模式下如何处理未读完的数据。

0.阅读引用

北大同学的测试

进程阻塞是否占用CPU资源

epoll边沿触发漏报消息包问题

北大同学的github上的epoll-et性能测试

为什么ET模式下socket文件描述符要设置成非阻塞的?

epoll的et模式一次没读完,下次有新数据来了,还会触发么

1.代码与测试

1.1 本次使用的测试工具

nc -v 127.0.0.1 8000

1.2 测试epoll-et的源码

搜索关键字:EPOLLET,O_NONBLOCK,边沿触发,非阻塞

epoll边沿触发模式的编程关键点:

  1. 要将事件设置成EPOLLET;
  2. 将事件设置成EPOLLET之后一定要将对应的fd设置成非阻塞的;
  3. 要对一次触发得到的缓冲区中的内容进行循环读直到不能再读取出数据为止.

源码如下:

#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>

#define MAX_EVENT_NUM           1024
#define BUFFER_SIZE             10
#define true                    1
#define false                   0

void use_et(struct epoll_event *event, int num, int epollfd, int listenfd)
{
	char buf[BUFFER_SIZE];
	static int connNumBal = 0;
	static int connNum = 0;
	for(int i = 0; i < num; i++)
	{
		int sockfd = event[i].data.fd;
		if(sockfd == listenfd)
		{
			struct sockaddr_in clientaddr;
			int clilen = sizeof(clientaddr);

			int connfd;
			do{
				if((connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clilen)) < 0 )
				{
					if(errno == EAGAIN)
					{
						printf("accept fail");
						break;
					}
				}
				int flag = fcntl(connfd,F_GETFL);
				flag |= O_NONBLOCK;
				fcntl(connfd,F_SETFL,flag); 

				struct epoll_event event;
				event.data.fd = connfd;
				event.events = EPOLLIN | EPOLLET;
				epoll_ctl(epollfd, EPOLL_CTL_ADD,connfd, &event);
				connNum ++;
				connNumBal ++;
				printf("来了第[%d]个连接,现有[%d]个连接,我对这个连接套结字描述符设置了非阻塞属性,事件的触发模式选择了ET,向epoll树中添加了节点之后,到达这里准备离开循环\n",connNum,connNumBal);
				break;
			}while(connfd>0);
			//printf("来了第[%d]个链接,我设置了非阻塞属性,添加了ET标志,向epoll树中添加了节点之后,我成功离开循环\n",num);
		}
		else if(event[i].events & EPOLLIN){
			printf("event trigger once\n");
			char buf[10] ={0};
			int ret ;
			while((ret =recv(sockfd, buf, BUFFER_SIZE, 0) ) > 0)
			{
				// printf("从缓冲区读取了 %d bytes,内容是:%s===================\n", ret, buf);
				write(STDOUT_FILENO,buf,ret);
			}
			if(ret < 0)
			{
				if(errno == EAGAIN)
				{
					printf("此次的内核缓冲区的内容已经读完了\n");
				}else{
					printf("recv error\n");
				}
			}

			if(ret == 0){
				connNumBal--;
				printf("对端关闭了连接,我要把这个连接从epoll树上摘掉啦,现在的连接有[%d]\n",connNumBal);
				epoll_ctl(epollfd, EPOLL_CTL_DEL, event[i].data.fd, NULL);
				close(sockfd);
			}

		}

	}
}

int start_server(char *ipaddr, char *port)
{

	const char * s = NULL;
	int ret = 0;
	int sock = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in serveraddr;
	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(atoi(port));
	inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr);

	//设置重用ip地址和端口号
	int on = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
	setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));
	ret = bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
	if(ret < 0)
	{
		perror(s);
		exit(1);
	}

	ret = listen(sock, 128);
	if(ret < 0)
	{
		perror(s);
		exit(1);
	}

	return sock;
}

int main(int argc, char *argv[])
{
	if (argc < 3)
	{ 
		printf("++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
		printf("+           ./server_et 127.0.0.1 8000             +\n");
		printf("+           please use the suggested format.       +\n");
		printf("++++++++++++++++++++++++++++++++++++++++++++++++++++\n");
		return 0;
	}
	int listenfd = start_server(argv[1], argv[2]);
	//setnonblocking(listenfd);
	struct epoll_event events[MAX_EVENT_NUM];
	int epollfd = epoll_create(5);
	if(epollfd < 0)
	{
		printf("epoll_create err\n");
		exit(1);
	}

	struct epoll_event event;
	event.data.fd = listenfd;
	event.events = EPOLLIN | EPOLLOUT | EPOLLET ;
	epoll_ctl(epollfd, EPOLL_CTL_ADD,listenfd, &event);

	while(1)
	{
		int ret = epoll_wait(epollfd, events, MAX_EVENT_NUM, -1);
		if(ret < 0)
		{
			printf("epoll failure\n");
			break;
		}
		if(ret > 0 )
		{
			printf("\n产生了事件,现在触发的事件有[%d]个\n",ret);
			use_et(events, ret, epollfd, listenfd);//et模式
		}
	}
	close(listenfd);
	return 0;
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值