Linux Epoll编程

Epoll介绍

Epoll是当前大规模并发服务器编程的完美方案。据评测性能比select提高不只一个数量级。Linux从kernel 2.6之后开始支持。关于常用网络服务器编程模型的优缺点比较可以参考这里:http://blog.csdn.net/sparkliang/article/details/4770655

Epoll相比select主要的改进在于:epoll可以监听数量巨大的fd,至于多大主要受内存限制(而select监听的fd最大值一般为1024);另一个是效率方面的,select随着监听的fd数量的增加效率会降低,epoll则没有这个问题。

epoll_create()

       #include <sys/epoll.h>

       int epoll_create(int size);
epoll_create()创建一个epoll实例。从Linux 2.6.8开始,参数size被忽略,但必须大于0. epoll_create()返回一个指向新的epoll实例的文件描述符。该文件描述符被用于后续的一系列epoll接口的调用。当不再使用时,应使用close()把它关掉。

返回值:成功返回非负文件描述符,出错返回-1, 并设置errno指示出错类型。

epoll_ctl()

#include <sys/epoll.h>

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

事件注册函数,注册要监听的事件类型。

epoll_ctl()系统调用在epfd所指向的epoll实例上执行控制操作。它请求在目标文件描述符fd上执行操作op.

参数op的有效值如下:

EPOLL_CTL_ADD

在epfd指向的epoll实例上注册目标文件描述符fd并监听fd上的事件event。

EPOLL_CTL_MOD

改变目标文件描述符fd的监听事件

EPOLL_CTL_DEL

将目标文件描述符fd从epfd所指向的epoll实例中删除。参数event可以被忽略,可以为NULL。

参数event告诉内核要监听什么事件。

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 events */
               epoll_data_t data;      /* User data variable */
           };

events成员是一个位集合,可以由以下可用事件组成。

EPOLLIN

侦听关联文件的读操作

EPOLLOUT

侦听关联文件的写操作

EPOLLRDHUP

流套接字对端关闭了连接,或关闭写一半的连接。

EPOLLPRI

有紧急数据可读

EPOLLET

将epoll设备边缘触发模式

EPOLLERR

关联的文件描述符发生错误。poll_wait()总是等待这类事件,不需要被设置。

EPOLLHUP

关联的文件描述符被挂起。poll_wait()总是等待这类事件,不需要被设置。

EPOLLONESHOT

只监听一次事件,当监听完这次事件之后,如果还需要监听这个fd的话,需要再次把该fd注册到epoll中

返回值:成功,返回0,出错返回-1,并设置errno.


epoll_wait()

#include <sys/epoll.h>

       int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
epoll_wait()系统调用等待epfd指向的epoll实例上的事件发生。events指向的内存区域包含了调用者可用的事件。最多有maxevents事件被返回。参数maxevents的值必须大于0.

epoll_wait会阻塞系统调用,阻塞的最大时间是timeout毫秒。设置timeout为-1将导致epoll_wait()无限期被阻塞直到有事件发生。设置timeout为0,将导致epoll_wait()立即返回。

epoll_wait每次返回的events结构中的data是用户通过调用epoll_ctl(EPOLL_CTL_ADD, EPOLL_CTL_MOD)设置的结构中的data是一样的。

返回值:

若成功,epoll_wait()返回准备好请求的I/O事件的描述符的数量,或者返回0表示没在请求的timeout毫秒内没有一个描述做好准备。出错返回-1.


epoll echo server

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>

int open_listenfd(int port);
int echo(struct epoll_event *pev);
#define MAXLEN 512


int main(int argc, char **argv)
{
	int port, listenfd, connfd, addrLen = sizeof(struct sockaddr_in);
	struct sockaddr_in clientAddr;

	if (argc != 2)
	{
		printf("usage: %s <port>\n", argv[0]);
		return 0;
	}
	port = atoi(argv[1]);

	if ( (listenfd = open_listenfd(port)) < 0)
	{
		perror("open_listenfd: ");
		return 0;
	}

	int readepfd = epoll_create(1);  //2.6.8之后size值被忽略,但要大于0
	struct epoll_event tevent;
	tevent.events = EPOLLIN;
	tevent.data.fd = listenfd;
	epoll_ctl(readepfd, EPOLL_CTL_ADD, listenfd, &tevent);
	struct epoll_event ev;
	while (1)
	{
		int n = epoll_wait(readepfd, &ev, 1, -1);
		
		if (n>0)
		{
			if (ev.data.fd == listenfd)
			{
				connfd = accept(listenfd, (struct sockaddr *)&clientAddr, &addrLen);
				printf("connection from %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
				struct epoll_event tev;
				tev.data.fd = connfd;
				tev.events = EPOLLIN;
				epoll_ctl(readepfd, EPOLL_CTL_ADD, connfd, &tev);
			}
			else
			{
				if (ev.events & EPOLLIN)
				{
					if (echo(&ev) <= 0)
					{
						printf("close\n");
						epoll_ctl(readepfd, EPOLL_CTL_DEL, ev.data.fd, NULL);
						close(ev.data.fd);
					}
				}
			}
		}
	}
	close(listenfd);
	close(readepfd);
}

int echo(struct epoll_event *pev)
{
	char buff[MAXLEN];
	int n;
	n = recv(pev->data.fd, buff, MAXLEN, 0);
	if (n > 0)
	{
		buff[n] = 0;
		printf("%d recv: %s\n", pev->data.fd, buff);
	}
	return n;	
}

int open_listenfd(int port)
{
	struct sockaddr_in myAddr;
	int listenfd;

	/* Create a socket descriptor */
	if ( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		return -1;

	/*
		listenfd will be an end point for all requests to port on any IP address for this host
	*/
	bzero((char *)&myAddr, sizeof(myAddr));
	myAddr.sin_family = AF_INET;
	myAddr.sin_port = htons(port);
	myAddr.sin_addr.s_addr = htonl(INADDR_ANY);


	if (bind(listenfd, (struct sockaddr *)&myAddr, sizeof(myAddr)) < 0)
	{
		close(listenfd);
		return -1;
	}
	
	if (listen(listenfd, 1024) < 0)
	{
		close(listenfd);
		return -1;
	}
	return listenfd;
}

总结

▶ 尽量少的调用epoll_ctl接口,即想办法减少在EPOLLIN和EPOLLOUT之间的切换

▶ 使用非阻塞的fd和ET模式 

▶ 使用事件缓存,循环在就绪的fds之间分配合理的时间,避免造成饥饿状态



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值