使用epoll编写服务器

epoll是公认的linux2.6下性能最好的多路I/O就绪通知方法。

epoll有epoll_create,epoll_ctl,epoll_wait三个系统调用。

1.int epoll_create(int size):创建一个epoll句柄。当创建好句柄后,他会占用一个fd的值,所以当使用完epoll后一定要调用close将其关闭。

2.int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event):epoll的事件注册函数,它不同于select是在监听事件时告诉内核要监听什么类型的事件,而是先注册要监听的事件的类型。

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

第二个参数表示动作,用三个宏表示:EPOLL_CTL_ADD注册新的fd到epfd中,EPOLL_CTL_MOD修改已经注册的fd的监听事件,EPOLL_CTL_DEL从epfd中删除一个fd。

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

第四个参数告诉内核需要监听什么事,struct 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_data_t data;
};

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):收集在epoll监控的事件中已经发送的事件,events是分配好的epoll_event结构体数组

,epoll会把发生的事件赋值到events数组中,maxevents告诉内核这个events有多大,不能大于创建epoll时的size,timeout是超时时间。

epoll的底层实现:

epoll_create创建一个epoll描述符,底层同时创建一个红黑树,和一个就绪链表,红黑树存储所监控的文件描述符的节点数据,就绪链表存储就绪的文件描述符的节点数据:

epoll_ctl将会添加新的描述符,首先判断是红黑树上是否有此文件描述符节点,如果有,则立即返回。如果没有, 则在树干上插入新的节点,并且告知内核注册回调函数。当

接收到某个文件描述符过来数据时,那么内核将该节点插入到就绪链表里面。epoll_wait将会接收到消息,并且将数据拷贝到用户空间,清空链表。对于LT模式epoll_wait清空

就绪链表之后会检查该文件描述符是哪一种模式,如果为LT模式,且必须该节点确实有事件未处理,那么就会把该节点重新放入到刚刚删除掉的且刚准备好的就绪链表,

epoll_wait马上返回。ET模式不会检查,只会调用一次。


epoll两种工作模式:水平触发(LT)和边沿触发(ET)

LT(level triggered)是epoll缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进

行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

ET (edge-triggered)是高速工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以epoll_wait调用中获取到

这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。

而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在

ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。

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

static void usage(const char* proc)
{
	assert(proc);
	printf("usage: %s [ip] [port]\n",proc);
}

static int set_nonblock(int fd)
{
	int fl = fcntl(fd,F_SETFL);
	fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}

int my_read(int fd,char* buf,int len)
{
	assert(buf);
	ssize_t total = 0;
	ssize_t s = 0;
	while((s = read(fd,&buf[total],len - 1 - total)) > 0&&errno != EAGAIN)
	{
		total += s;
	}

	return total;
}

int start_up(char* ip,int port)
{
	assert(ip);
	assert(port > 0);

	int sock = socket(AF_INET,SOCK_STREAM,0);
	if(sock < 0)
	{
		perror("socket");
		exit(1);
	}
	
	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(port);
	local.sin_addr.s_addr = inet_addr(ip);

	if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
	{
		perror("bind");
		exit(2);
	}

	if(listen(sock,5) < 0)
	{
		perror("listen");
		exit(3);
	}

	return sock;
}

int main(int argc,char* argv[])
{
	if(argc != 3)
	{
		usage(argv[0]);
		return 1;
	}
	int listen_sock = start_up(argv[1],atoi(argv[2]));
	int epfd = epoll_create(256);
	if(epfd < 0)
	{
		perror("epoll_create");
		return 2;
	}
	
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = listen_sock;
	epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);
	int nums = 0;

	struct epoll_event ready_events[64];
	int len = 64;
	int timeout = -1;

	while(1)
	{
		switch(nums = epoll_wait(epfd,ready_events,len,timeout))
		{
			case 0:
				printf("timeout..");
				break;
			case -1:
				perror("epoll_wait");
				break;
			default:
				{
					int i = 0;
					for(;i < nums; i++)
					{
						int fd = ready_events[i].data.fd;
						if(fd == listen_sock && ready_events[i].events & EPOLLIN)
						{
							struct sockaddr_in client;
							socklen_t len = sizeof(client);
							int new_fd = accept(listen_sock,(struct sockaddr*)&client,&len);
							if(new_fd < 0)
							{
								perror("accept");
								continue;
							}

							printf("get a new client:%s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));

							ev.events = EPOLLIN|EPOLLET;
							ev.data.fd = new_fd;
							set_nonblock(new_fd);
							epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&ev);
						}
						else
						{
							if(ready_events[i].events & EPOLLIN)
							{
								char buf[1024];
								ssize_t s = read(fd,buf,sizeof(buf) - 1);
								if(s > 0)
								{
									buf[s] = 0;
									printf("client#%s\n",buf);
									ev.events = EPOLLOUT|EPOLLET;
									ev.data.fd = fd;
									epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);
								}
								else if(s == 0)
								{
									epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
									close(fd);
									printf("client close...");
								}
								else
								{
									perror("read");
								}
							}
							else if(ready_events[i].events & EPOLLOUT)
							{
								char buf[1024];
								sprintf(buf,"HTTP/1.0 200 OK\r\n\r\n<html><h2>hello</h2></html>");
								write(fd,buf,strlen(buf));
								epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
								close(fd);
							}
							else
							{}
						}
					}
				}
				break;
		}
	}
	return 0;
}

使用浏览器作为客户端,结果如下:




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值