epoll例子

77 篇文章 0 订阅

令人高兴的是,2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多,所以,大部分情况下,强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。

epoll的接口非常简单,一共就三个函数:

1. int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

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

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值,

第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fdepfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

EPOLL_CTL_DEL:从epfd中删除一个fd

第三个参数是需要监听的fd

第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event {

  __uint32_t events;  /* Epoll events */

  epoll_data_t data;  /* User data variable */

};

events可以是以下几个宏的集合:

EPOLLIN :     表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:    表示对应的文件描述符可以写;

EPOLLPRI:      表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:     表示对应的文件描述符发生错误;

EPOLLHUP:     表示对应的文件描述符被挂断;

EPOLLET:      将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

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

等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。



#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <errno.h>

#define MAXLINE 10

#define OPEN_MAX 100

#define LISTENQ 20

#define SERV_PORT 5555

#define INFTIM 1000

//线程池任务队列结构体

struct task
{
	int fd; //需要读写的文件描述符
	struct task *next; //下一个任务
};

//用于读写两个的两个方面传递参数
struct user_data
{
	int fd;
	unsigned int n_size;
	char line[MAXLINE];
};

//线程的任务函数
void * readtask(void *args);
void * writetask(void *args);
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev, events[20];
int epfd;
pthread_mutex_t mutex;
pthread_cond_t cond1;
struct task *readhead = NULL, *readtail = NULL, *writehead = NULL;
void setnonblocking(int sock)
{
	int opts;
	opts = fcntl(sock, F_GETFL);
	if (opts < 0)
	{
		perror("fcntl(sock,GETFL)");
		exit(1);
	}
	opts = opts | O_NONBLOCK;
	if (fcntl(sock, F_SETFL, opts) < 0)
	{
		perror("fcntl(sock,SETFL,opts)");
		exit(1);
	}
}
int main()
{
	int i, maxi, listenfd, connfd, sockfd, nfds;
	pthread_t tid1, tid2;

	struct task *new_task = NULL;
	struct user_data *rdata = NULL;
	socklen_t clilen;

	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&cond1, NULL);
	//初始化用于读线程池的线程

	pthread_create(&tid1, NULL, readtask, NULL);
	pthread_create(&tid2, NULL, readtask, NULL);

	//生成用于处理accept的epoll专用的文件描述符
	epfd = epoll_create(256);

	struct sockaddr_in clientaddr;
	struct sockaddr_in serveraddr;
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	//把socket设置为非阻塞方式
	setnonblocking(listenfd);
	//设置与要处理的事件相关的文件描述符
	ev.data.fd = listenfd;
	//设置要处理的事件类型
	ev.events = EPOLLIN | EPOLLET;
	//注册epoll事件
	epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
	bzero(&serveraddr, sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	char *local_addr = "200.200.200.222";
	inet_aton(local_addr, &(serveraddr.sin_addr));//htons(SERV_PORT);
	serveraddr.sin_port = htons(SERV_PORT);
	bind(listenfd, (sockaddr *) &serveraddr, sizeof(serveraddr));
	listen(listenfd, LISTENQ);
	maxi = 0;
	for (;;)
	{
		//等待epoll事件的发生
		nfds = epoll_wait(epfd, events, 20, 500);
		//处理所发生的所有事件
		for (i = 0; i < nfds; ++i)
		{
			if (events[i].data.fd == listenfd)// 并且将新连接也加入EPOLL的监听队列
			{
				connfd = accept(listenfd, (sockaddr *) &clientaddr, &clilen);
				if (connfd < 0)
				{
					perror("connfd<0");
					exit(1);
				}
				setnonblocking(connfd);
				char *str = inet_ntoa(clientaddr.sin_addr);
				std::cout << "connec_ from >>" << str << std::endl;
				//设置用于读操作的文件描述符
				ev.data.fd = connfd;
				//设置用于注测的读操作事件
				ev.events = EPOLLIN | EPOLLET;
				//注册ev
				epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
			} else
				if (events[i].events & EPOLLIN)
				{
					printf("reading!\n");
					if ((sockfd = events[i].data.fd) < 0) continue;
					new_task = new task();
					new_task->fd = sockfd;
					new_task->next = NULL;
					//添加新的读任务
					pthread_mutex_lock(&mutex);
					if (readhead == NULL)
					{
						readhead = new_task;
						readtail = new_task;
					} else
					{
						readtail->next = new_task;
						readtail = new_task;
					}
					//唤醒所有等待cond1条件的线程
					pthread_cond_broadcast(&cond1);
					pthread_mutex_unlock(&mutex);
				} else
					if (events[i].events & EPOLLOUT)
					{
						rdata = (struct user_data *) events[i].data.ptr;
						sockfd = rdata->fd;
						write(sockfd, rdata->line, rdata->n_size);
						delete rdata;
						//设置用于读操作的文件描述符
						ev.data.fd = sockfd;
						//设置用于注测的读操作事件
						ev.events = EPOLLIN | EPOLLET;//EPOLLIN | EPOLLET并没有设置对写socket的监听,如果有写操作的话,这个时候epoll是不会返回事件的,如果要对写操作也监听的话,应该是EPOLLIN | EPOLLOUT | EPOLLET
						//修改sockfd上要处理的事件为EPOLIN
						epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
					}
		}
	}
}
void * readtask(void *args)
{
	int fd = -1;
	unsigned int n;
	//用于把读出来的数据传递出去
	struct user_data *data = NULL;
	while (1)
	{
		pthread_mutex_lock(&mutex);
		//等待到任务队列不为空
		while (readhead == NULL)
			pthread_cond_wait(&cond1, &mutex);
		fd = readhead->fd;
		//从任务队列取出一个读任务
		struct task *tmp = readhead;
		readhead = readhead->next;
		delete tmp;
		pthread_mutex_unlock(&mutex);
		data = new user_data();
		data->fd = fd;
		if ((n = read(fd, data->line, MAXLINE)) < 0)
		{
			if (errno == ECONNRESET)//Connection Reset:你连接的那一端已经断开了,而你却还试着在对方已断开的socketfd上读写数据!
			{
				close(fd);
			} else
			std::cout << "readline error" << std::endl;
			if (data != NULL) delete data;
		} else
			if (n == 0)
			{
				close(fd);
				printf("Client close connect!\n");
				if (data != NULL) delete data;
			} else
			{
				data->n_size = n;
				//设置需要传递出去的数据
				ev.data.ptr = data;
				//设置用于注测的写操作事件
				ev.events = EPOLLOUT | EPOLLET;
				//修改sockfd上要处理的事件为EPOLLOUT
				epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
			}
	}
}


----------------一些相关的数据结构-------------------------------------------

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;
 
struct epoll_event
{
  uint32_t events; /* Epoll events */
  epoll_data_t data; /* User data variable */
};



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值