epoll系统调用

内核事件表

selectpoll不同,epoll使用一组函数来完成任务,而不是单个函数。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表

但是,epoll需要使用一个额外的文件描述符来唯一标识内核中的这个事件表

文件描述符由以下函数创建:

#include <sys/epoll.h>

int epoll_create(int size);		//成功返回指向事件表的文件描述符

下面的函数用来操作epoll的内核事件表:

#include <sys/epoll.h>

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

struct epoll_event
{
   
	__uint32_t events;		//epoll事件
	epoll_data_t data;		//用户数据
};

typedef union epoll_data
{
   
	void* ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
}epoll_data_t;
  • fd参数是要操作得到文件描述符
  • op参数指定操作类型
  • event参数指定事件,是epoll_event结构指针类型:
    events成员描述事件类型,与poll基本相同,但是宏前要加‘E’。
    data成员用于存储用户数据,一般只使用fd。

epoll_wait函数

epoll_wait函数在一段超时时间内等待一组文件描述符上的事件。

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
//成功返回就绪的文件描述符个数,失败返回-1
  • maxevents参数指定最多监听多少个事件且必须大于0

epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指向 的数组中。这个数组只用于输出epoll_wait检测到的就绪事件。

LT和ET模式

epoll对文件描述符的操作有两种:LT和ET
LT相当于高效的poll,ET才是epoll的高效工作模式。

  • 对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait 时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。
  • 而对于采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应
    用程序后,应用程序必须立即处理该事件,因为后续的epoll_ wait 调用将不再向应用程序通知这一事件。

epoll编程实例

epoll编程流程

/* * 1.创建socket-->bind-->listen * 2.创建一个结构体数组来存储发生事件的fd并作为参数传入epoll_wait * 3.对发生事件的数组进行检测 * 4.循环: * ① 如果是新连接,放入内核时间表 * ③ 如果断开就从内核事件表删除 * ④ 都不是就收发数据 */

头文件tcp_socket.h


#include<iostream>
#include<assert.h>
#include<string.h>

#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

#include<unistd.h>

#define BUFF_SIZE 128

using namespace std;
class Socket
{
   
public:
	Socket()
	{
   
		sockfd_ = socket(PF_INET, SOCK_STREAM, 0);
		assert(sockfd_ >= 0);
	}

	int Get_socket()
	{
   
		return sockfd_;
	}

	~Socket()
	{
   
		close(sockfd_);
	}
protected:
	int sockfd_;
};

class Socket_Ser :public Socket
{
   
public:
	Socket_Ser(const char* ip, int port = 6000, int backlog = 5)
	{
   
		struct sockaddr_in address;
		memset(&address, 0, sizeof(address));

		address.sin_family = AF_INET;
		address.sin_port = htons(port);
		address.sin_addr.s_addr = inet_addr(ip);

		int ret = bind(sockfd_, (struct sockaddr*)&address, sizeof(address));
		assert(ret != -1);

		ret = listen(sockfd_, backlog);
		assert(ret != -1);
	}

	int Accept()
	{
   
		struct sockaddr_in client;
		socklen_t len = sizeof(client);
		int connfd = accept(sockfd_, (struct sockaddr*)&client, &len);
		return connfd;
	}

	int Recv(int fd, char* buffer, int size)
	{
   
		int ret = recv(fd, buffer, size - 1, 0);
		if (ret == -1 || (strncmp(buffer, "end", 3) == 0))
		{
   
			return -1;
		}
	}

	void Send(int fd, const char* buffer, int size)
	{
   
		send(fd, buffer, size, 0);
	}

	void Close_client(int fd)
	{
   
		close(fd);
	}
};

class Socket_Cli :public Socket
{
   
public:
	Socket_Cli(const char* ip, int port = 6000)
	{
   
		struct sockaddr_in address;
		memset(&address, 0, sizeof(address));

		address.sin_family = AF_INET;
		address.sin_port = htons(port);
		address.sin_addr.s_addr = inet_addr(ip);

		int ret = connect(sockfd_, (struct sockaddr*)&address, sizeof(address));
		assert(ret != -1);
	}

	int Send(char* buffer, int size)
	{
   
		send(sockfd_, buffer, size, 0);
	}
};

头文件tcp_epoll.h

#include"tcp_socket.h"
#include<sys/epoll.h>
#include <fcntl.h>

#define MAXEVENTS 100
class Epoll
{
   
public:
	Epoll(const char* ip, int epoll_size = 5, int port = 6000, int backlog = 5) :ser(ip, port, backlog)
	{
   
		epollfd = epoll_create(epoll_size); 
		assert(epollfd != -1);

		Insert(ser.Get_socket(), EPOLLIN);
	}

	int Epoll_Deal(int flag)		
	{
   
		struct epoll_event events[MAXEVENTS];
		int n = epoll_wait(epollfd, events, MAXEVENTS, -1);		//将发生的事件存储在events中
		if (n <= 0)
		{
   
			cout << "epoll errno!" << endl;
			return -1;
		}

		Deal_connect(n, events,flag);
		return n;
	}

private:
	void Deal_connect(int n,struct epoll_event* events,int flag)
	{
   
		for (int i = 0; i < n; i++)
		{
   
			int fd = events[i].data.fd;
			if (fd == ser.Get_socket())	//服务器端新连接
			{
   
				Accept(flag);
			}
			else			//客户端
			{
   
				if (events[i].events & EPOLLRDHUP)	//断开连接
				{
   
					Delete(events[i].data.fd);
				}
				else					//收发数据
				{
   
					char buffer[128] = {
    0 };
					ser.Recv(events[i].data.fd, buffer, 128);
					cout << "recv form " << events[i].data.fd << ":" << buffer << endl;
				}
			}
		}
	}

	void Accept(int flag)
	{
   
		int connfd = ser.Accept();
		Insert(connfd, EPOLLIN | EPOLLRDHUP);
		if (flag == O_NONBLOCK)
		{
   
			SetUnblock(connfd);
		}
	}

	void Delete(int fd)
	{
   
		ser.Close_client(fd);
		epoll_ctl(epollfd, EPOLL_CTL_DEL, fd,NULL);
	}

	void Insert(int fd, int events)
	{
   
		struct epoll_event event_;
		event_.events = events;
		event_.data.fd = fd;

		int res = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event_);
	}

	void SetUnblock(int fd)
	{
   
		int oldoption = fcntl(fd, F_GETFL);
		int newoption = oldoption | O_NONBLOCK;
		fcntl(fd, F_SETFL, newoption);
	}
private:
	int epollfd;
	Socket_Ser ser;
};

主文件 tcp_epoll.cpp

#include "tcp_epoll.h"

int main(int argc,char* argv[])
{
   
	if (argc <= 1)
	{
   
		cout << "errno! please input again" << endl;
	}
	Epoll mypoll(argv[1]);

	while (1)
	{
   
		mypoll.Epoll_Deal();
	}
}

参考文献

[1]游双.Linux高性能服务器编程.机械工业出版社,2043.5.