网络编程----libevent改造Echo服务器

前文已经介绍了epoll这IO模型,能够处理数百万的并发量,但是epoll只适用于Linux平台,如果想要编写高并发可移植的网络应用程序我们该怎么办呢?答案是用开源的跨平台高性能的网络库Libevent..

libevent 是一个高性能轻量级的跨平台网络库,事件驱动,适用于多个平台,Linux,Windows,Mac os等,支持多种网络IO复用,如常用的select,epoll,pll,dev/poll,支持IO,信号和定时器事件;支持注册事件优先级等,github地址:https://github.com/libevent/libevent,想了解的可以clone下了解,这篇文章不对libevent作详细介绍,仅介绍如何用libevent改善我们的ECHO服务器。

在上文中我们用epoll实现的ECHO服务器,其主干部分如下:

		int n = epoll_wait(efd, events, MAX_EVENT_NUM, -1);
		for(int i=0; i < n; i++)
		{
			if(events[i].data.fd == listenfd)
			{
				if((connfd = doAccept(listenfd)) < 0)
				{
					perror("accept failed.\n");
					continue;
				}
				
				if(make_socket_nonblock(connfd) < 0)
				{
					perror("make non block failed");
					return -1;
				}
				
				event.data.fd = connfd;
				event.events = EPOLLIN | EPOLLET | EPOLLOUT ;
				if(epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event) < 0)
				{
					perror("epoll add");
					return -1;
				}
			}
			else if(events[i].events & EPOLLIN)
			{
				doRead(listenfd);
			}
			else if(events[i].events & EPOLLOUT)
			{

			}
			else
			{
				perror("epoll error");
				close(events[i].data.fd);
			}
		}
	}
可以看到epoll计数需要对当前发生的事件进行判断,判断的当前发生的是何种事件,然后分别进行处理,此外epoll实现的ECHO服务器仅能够在Linux平台下运行,若想移植到Winsow平台则上述代码无法执行,现在用Libevent改造我们的代码,使得ECHO服务器程度可以跨平台执行。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int make_socket_nonblock(int fd)
{
#ifdef _WIN32
	{
		unsigned long nonblocking = 1;
		if (ioctlsocket(fd, FIONBIO, &nonblocking) == SOCKET_ERROR) {
			return -1;
		}
	}
#else
	int flag;
	if((flag = fcntl(fd, F_GETFL,0)) < 0)
	{
		perror("FCNTL FGET");
		return -1;
	}
	flag |= O_NONBLOCK;
	if(fcntl(fd,F_SETFL, flag)< 0)
	{
		perror("FCNTL SET");
		return -1;
	}
#endif
	return 0;
}


int make_socket_reuseable(int fd)
{
#ifndef _WIN32
	int reuse = 1;
	if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
	{
		perror("set reuse addr");
		return -1;
	}
#endif
	return 0;
}

void doRead(evutil_socket_t fd, short event, void * arg)
{
	char recvBuf[1024] = {0};
	while(recv(fd,recvBuf, 1024, 0)>0)
	{
		send(fd,recvBuf, 1024, 0);
		char* temp = recvBuf;
		while(*temp != '\r' && *temp != '\n')
			temp++;
		*temp='\0';
		if(strcmp(recvBuf, "quit") == 0)
			exit(0);
	}
}


void doAccept(evutil_socket_t fd, short event, void * arg)
{
	int connfd;
	char ipAddr[32] = {0};
	struct event* readEvent = NULL;
	struct sockaddr_in cliAddr;
	bzero(&cliAddr, sizeof(cliAddr));
	socklen_t len = sizeof(cliAddr);

	struct event_base* base = (struct event_base*)arg;
	connfd = accept(fd, (struct sockaddr*)&cliAddr, &len);
	inet_ntop(AF_INET,&(cliAddr.sin_addr),ipAddr,32);
	printf("accept client connect %s:%d.\n", ipAddr,ntohs(cliAddr.sin_port));
	if(connfd < 0)
		return;
	make_socket_nonblock(connfd);
	//接受一个客户端连接后创建一个新的事件,并将事件添加到事件队列里,设置该事件的回调函数。
	readEvent = event_new(base, connfd, EV_READ|EV_PERSIST, doRead, NULL);
	if(!readEvent)
		return;
	event_add(readEvent, NULL);
}

int main()
{
	int listenfd, connfd;
	struct event_base* base;
	struct event *listenEvent;
	struct sockaddr_in serverAddr;
	bzero(&serverAddr, sizeof(serverAddr));
	serverAddr.sin_family =  AF_INET;
	serverAddr.sin_port = htons(12345);
	serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);

	//创建一个event_base结构
	base = event_base_new();
	if(!base)
	{
		perror("base new");
		return -1;
	}
		
	if((listenfd = socket(AF_INET, SOCK_STREAM, 0))< 0)
	{
		perror("create socket");
		return -1;
	}

	//将套接字设置为非阻塞
	if(make_socket_nonblock(listenfd) < 0)
	{
		perror("make socket nonblock");
		return -1;
	}

	//将套接字地址设为可重用
	make_socket_reuseable(listenfd);
	
	if(bind(listenfd,(struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0)
	{
		perror("bind error");
		return -1;
	}

	if(listen(listenfd, 64) < 0)
	{
		perror("listen fail");
		return -1;
	}
	//创建一个event,设置该事件为读事件,并设置EV_PERSIST属性,只有事件发生后该事件不从事件队列里删除
	//设置事件发生后的回调函数doAccept
	listenEvent = event_new(base, listenfd, EV_READ|EV_PERSIST, doAccept, (void*)base);
	if(!listenEvent)
	{
		perror("event new");
		return -1;
	}

	//将创建的事件添加到事件队列里去,类似于epoll的epoll_ctl add
	event_add(listenEvent, NULL);

	//开始事件循环,类似于epoll的epoll_wait
	event_base_dispatch(base);
}

测试:

运行该程序,然后telnet 127.0.0.1 12345 这个ECHO服务器仍然支持并发访问。主要注意的是编译该程序需要连接libevent库,要加上编译选项-levent

可以看到的是我们使用libevent改写我们的ECHO服务器代码后,代码的可读性有了很大的提升,我们不再去遍历所有注册的事件,并判断该事件时读是写还是异常,我们仅需要创建一个事件,并标明事件时读是写还是异常,然后设置该事件发生后的回调函数,当事件发生时,libevent会调用我们注册的回调函数,在回调中我们自己处理相应的事件。因此,代码的可读性有了很大的提升,程序员也可以从繁琐的epoll,select编码中解放出来,仅关注注册事件,以及事件发生后的处理问题,libevent的这种机制成为Reactor机制。

网络编程----libevent改造Echo服务器_第1张图片


Reactor模式UML图如上,主要分为几个部分,Reactor:负责注册事件,以后事件的处理,删除, Event Demultiplexer:事件分发器,接受Reactor事件的注册,并且启动事件循环,当事件发生时调用具体的事件处理器 Concrete Handler处理事件。

网络编程----libevent改造Echo服务器_第2张图片

Reactor模式的流程图,利用该模式网络编程时,程序员仅关注流程图左边的两部分,即事件的注册和事件的处理。目前libevent已经被广泛应用,作为底层的网络库,如著名的memcached, Vomit.有志于深入网络编程的朋友们可以先从学习libevent入手。

你可能感兴趣的:(网络编程)