LibEvent中文帮助文档--第1、2、3、4章



 

 LibEvent中文帮助文档--第1、2、3、4章

返回主目录

Libevent

快速可移植非阻塞式网络编程

 

 

修订历史

版本

日期

作者

备注

V1.0

2016-11-15

周勇

Libevent编程中文帮助文档

 

文档是2009-2012年由Nick-Mathewson基于Attribution-Noncommercial-Share Alike许可协议3.0创建,未来版本将会使用约束性更低的许可来创建.

此外,本文档的源代码示例也是基于BSD"3条款""修改"条款.详情请参考BSD文件全部条款.本文档最新下载地址:

英文:http://libevent.org/

中文:http://blog.csdn.net/zhouyongku/article/details/53431597

请下载并运行"gitclonegit://github.com/nmathewson/libevent- book.git"获取本文档描述的最新版本源码.

返回主目录

1.关于本文档

为了更好掌握Libevent(2.0)进行快速可移植的异步IO网络编程,你需要具备:

  • C语言基本知识

  • C语言网络开发函数调用(socket(),connect()).

2.示例代码注意事项

本文档描述的源码示例需要运行在LinuxFreeBSDOpenBSDNetBSDMacOSXSolarisAndroid这些操作系统中,Windows环境下编译可能会有一些不兼容的情况发生.

3.一个小的异步IO例子

许多初学者往往都是使用阻塞式IO调用进行编程.当你调用一个同步IO的时候,除非操作系统已经完成了操作或者时间长到你的网络堆栈放弃的时候,否则系统是不会返回完成的.举个例子,当你调用"connect"做一个TCP连接的时候,你的操作系统必须排队处理来自发送到服务器的SYN,除非等到SYN_ACK包从对面接收到,或者是超时,否则操作是不会返回给你的应用程序.

 

TCP三次握手

第一次握手:建立连接时,客户端发送syn(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers).第二次握手:服务器收到syn,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN(syn=k),SYN+ACK,此时服务器进入SYN_RECV状态;第三次握手:客户端收到服务器的SYN+ACK,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手.

这里有一个很简单的阻塞式网络调用的例子,它打开一个连接到www.google.com,发送它简单的HTTP请求,并打印输出到stdout.

示例:一个简单的阻塞式HTTP客户端

/* For sockaddr_in*/
#include <netinet/in.h>
/* For socket functions*/
#include <sys/socket.h>
/* For gethostbyname*/
#include <netdb.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
int main(int c, char** v)
{
const char query[] =
"GET / HTTP/1.0\r\n"
"Host: www.google.com\r\n"
"\r\n";
const char hostname[] = "www.google.com";
struct sockaddr_in sin;
struct hostent* h;
const char* cp;
int fd;
ssize_t n_written, remaining;
char buf[1024];
/* Look up the IP address for the hostname. Watch out; this isn’t
threadsafe on most platforms.*/
h = gethostbyname(hostname);
if (!h) {
fprintf(stderr, "Couldn’t lookup %s: %s", hostname, 	hstrerror(h_errno));
	return 1;
}
if (h->h_addrtype != AF_INET) {
	fprintf(stderr, "No ipv6 support, sorry.");
	return 1;
}
/* Allocate a new socket*/
fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) {
	perror("socket");
	return 1;
}
/* Connect to the remote host*/
sin.sin_family = AF_INET;
sin.sin_port = htons(80);
sin.sin_addr =
* (struct in_addr * )h->h_addr;
if (connect(fd, (struct sockaddr * ) &sin, sizeof(sin))) {
	perror("connect");
	close(fd);
	return 1;
}
/* Write the query.*/
/* XXX Can send succeed partially?*/
cp = query;
remaining = strlen(query);
while (remaining) {
	n_written = send(fd, cp, remaining, 0);
	if (n_written <= 0) {
	perror("send");
	return 1;
}
remaining -= n_written;
cp += n_written;
}
/* Get an answer back.*/
while (1) {
	ssize_t result = recv(fd, buf, sizeof(buf), 0);
	if (result == 0) {
	break;
} else if (result < 0) {
	perror("recv");
	close(fd);
	return 1;
}
fwrite(buf, 1, result, stdout);
}
close(fd);
return 0;
}

我们可以看出,上面的代码均是阻塞式操作:调用gethostbyname的去解析www.google.com的时候不等到它成功或者失败不会返回;调用connect的时候不等到它连接成功不会返回;调用recv的时候不等到它接受到数据或关连接关闭的时候不会返回;同样,调用send的时候至少等到待把输出区间待发送数据刷新到内核的写缓冲区之后才会返回.

 

 

现在看起来阻塞式IO还没有让人多厌烦,因为当你的程序在处理网络IO的同时不会处理其它业务,那么阻塞式IO能满足你的编程需求,但是想一下,当你想写一个程序支持多个连接,比如说需要同时从两个连接中读取数据,那么这个时候你就不知道到底先从哪个连接读取数据.

一个糟糕的例子:

/* This won’t work.*/
char buf[1024];
int i, n;
while (i_still_want_to_read()) 
{
for (i=0; i<n_sockets; ++i) {
n = recv(fd[i], buf, sizeof(buf), 0);
if (n==0)
handle_close(fd[i]);
else if (n<0)
handle_error(fd[i], errno);
else
handle_input(fd[i], buf, n);
}
}

因为如果数据到达fd[2]首先,程序甚至不会尝试从fd[2]读取数据,直到读取fd[0]fd[1]得到一些完成数据的读取.

 

有时候人们使用多线程或多进程服务来解决这个问题.最简单的方法就是让单独的每个进程或线程来处理它们各自的连接.由于每个连接都有自己的处理过程所以等待一个连接过程的阻塞IO方法的调用将不会影响到其它任何别的连接的处理过程.

 

这里有一个别的程序示例.这是一个很小的服务器,在端口40713上侦听TCP连接,每次一条一条地将数据读出来,当数据读出来的时候立刻进行R13加密,一条一条地写进输出缓冲区,在这个过程中它调用Unix fork()来创建一个新的过程来处理服务器接受到的连接.

示例:Forking ROT13 服务器

* For sockaddr_in*/
#include <netinet/in.h>
/* For socket functions*/
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_LINE 16384
char rot13_char(char c)
{
	/* We don’t want to use isalpha here; setting the locale would 
change which characters are considered alphabetical.*/
if ((c >= ’a’ && c <= ’m’) || (c >= ’A’ && c <= ’M’))
return c + 13;
else if ((c >= ’n’ && c <= ’z’) || (c >= ’N’ && c <= ’Z’))
return c - 13;
else
return c;
}
void child(int fd)
{
char outbuf[MAX_LINE+1];
size_t outbuf_used = 0;
ssize_t result;
while (1) {
char ch;
result = recv(fd, &ch, 1, 0);
if (result == 0) {
break;
} else if (result == -1) {
perror("read");
break;
}
/* We do this test to keep the user from overflowing the buffer.*/
if (outbuf_used < sizeof(outbuf)) {
outbuf[outbuf_used++] = rot13_char(ch);
}
if (ch == ’\n’) {
send(fd, outbuf, outbuf_used, 0);
outbuf_used = 0;
continue;
}
}
}
void run(void)
{
int listener;
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(40713);
listener = socket(AF_INET, SOCK_STREAM, 0);
#ifndef WIN32
{
	int one = 1;
	setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one,    
	sizeof(one));
}
#endif
	if (bind(listener, (struct sockaddr * )&sin, sizeof(sin)) < 0) {
	perror("bind");
	return;
}
if (listen(listener, 16)<0) 
{
	perror("listen");
	return;
}
while (1) 
{
	struct sockaddr_storage ss;
	socklen_t slen = sizeof(ss);
	int fd = accept(listener, (struct sockaddr * )&ss, &slen);
	if (fd < 0) 
	{
		perror("accept");
	} 
	else 
	{
		if (fork() == 0) 
		{
			child(fd);
			exit(0);
		}
	}
}
}
int main(int c, char** v)
{
run();
return 0;
}

那么,我们有了一个完美的解决方案来处理多个即时连接,我就可以停这本书去做别的事了么?不完全是.首先,进程创建(甚至线程创建)的代价在一些平台上可能会非常昂贵.在实际中,你可能更想使用一个线程池,而不是创建新的进程.但更重要的是,线程在规模上并不尽如人意.如果您的程序同事需要处理成千上万的连接,可能会效率极低因为cpu同时运行的线程只有屈指可数的几个.

 

但是如果连多线程都不能解决多个连接中的问题的话,那么还有什么别的方法?在Unix编程中,你就需要将你的socket设置成为非阻塞模式.Unix的调用方法如下:

fcntl(fd, F_SETFL, O_NONBLOCK);

这里fd代表的是socket的文件描述符.一旦你设置fd(套接字)为非阻塞模式,从那以后,无论什么时候,你调用fd函数都将立即完成操作或返回表示 "现在我无法取得任何进展,再试一次"的错误码.所以我们的两个套接字的例子可能会天真地写成下面的代码:

 

糟糕的例子:忙查询所有套接字

/* This will work, but the performance will be unforgivably bad.*/
int i, n;
char buf[1024];
for (i=0; i < n_sockets; ++i)
	fcntl(fd[i], F_SETFL, O_NONBLOCK);
while (i_still_want_to_read())
{
	for (i=0; i < n_sockets; ++i) 
	{
		n = recv(fd[i], buf, sizeof(buf), 0);
		if (n == 0) 
		{
			handle_close(fd[i]);
		} 
		else if (n < 0)
		{
			if (errno == EAGAIN) 
			; 
		/* The kernel didn’t have any data for us to read.*/
			else handle_error(fd[i], errno);
		} 
		else 
		{
			handle_input(fd[i], buf, n);
		}
	}
}

现在我们正在使用非阻塞套接字,上面的代码会有效,但只有很少.性能将会很糟糕,有两个原因:首先,当任何连接中都没有数据的时候,该循环将继续,并且消耗完你的CPU;其次,如果你想处理一个或两个以上连接,无论有没有数据你都需要为每个连接调用系统内核.所以我们需要一种方法告诉内核:你必须等到其中一个socket已经准备好给我数据才通知我,并且告诉我是哪一个socket.解决这个问题人们常用的是select()方法.Select()方法的调用需要三套fd(数组),一个作为写,一个作为读,一个作为异常.该函数等待socket集合,并且修改这个集合以知道哪些socket可以使用了.

下面是一个select()例子:

示例:使用select()

/* If you only have a couple dozen fds, this version won’t be awful*/
fd_set readset;
int i, n;
char buf[1024];
while (i_still_want_to_read())
{
	int maxfd = -1;
	FD_ZERO(&readset);
	/* Add all of the interesting fds to readset
	*/
	for (i=0; i < n_sockets; ++i) 
	{
		if (fd[i]>maxfd) maxfd = fd[i];
		FD_SET(fd[i], &readset);
	}
	/* Wait until one or more fds are ready to read*/
	select(maxfd+1, &readset, NULL, NULL, NULL);
	/* Process all of the fds that are still set in readset*/
	for (i=0; i < n_sockets; ++i) 
	{
		if (FD_ISSET(fd[i], &readset)) 
		{
			n = recv(fd[i], buf, sizeof(buf), 0);
			if (n == 0) 
			{
				handle_close(fd[i]);
			}
			else if (n < 0)
			{
				if (errno == EAGAIN)
				; 
/* The kernel didn’t have any data for us to read.*/
				else
				handle_error(fd[i], errno);
			} 
			else
			{
				handle_input(fd[i], buf, n);
			}
		}
	}
}

这是我们ROT13服务器重新实现,使用select().

示例:基于select()ROT13服务器

/* For sockaddr_in*/
#include <netinet/in.h>
/* For socket functions*/
#include <sys/socket.h>
/* For fcntl*/
#include <fcntl.h>
/* for select*/
#include <sys/select.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#define MAX_LINE 16384

char rot13_char(char c)
{
	/* We don’t want to use isalpha here; setting the locale 
    change which characters are considered alphabetical.*/
	if ((c >= ’a’ && c <= ’m’) || (c >= ’A’ && c <= ’M’))
	return c + 13;
	else if ((c >= ’n’ && c <= ’z’) || (c >= ’N’ && c <= ’Z’))
	return c - 13;
	else
	return c;
}
struct fd_state 
{
	char buffer[MAX_LINE];
	size_t buffer_used;
	int writing;
	size_t n_written;
	size_t write_upto;
};
struct fd_state*alloc_fd_state(void)
{
	struct fd_state
	* state = malloc(sizeof(struct fd_state));
	if (!state)
	return NULL;
	state->buffer_used = state->n_written = state->writing =
	state->write_upto = 0;
	return state;
}
void free_fd_state(struct fd_state* state)
{
	free(state);
}
void make_nonblocking(int fd)
{
	fcntl(fd, F_SETFL, O_NONBLOCK);
}
int do_read(int fd, struct fd_state* state)
{
	char buf[1024];
	int i;
	ssize_t result;
	while (1) 
	{
		result = recv(fd, buf, sizeof(buf), 0);
		if (result <= 0)
		break;
		for (i=0; i < result; ++i) 
		{
			if (state->buffer_used < sizeof(state->buffer))
			state->buffer[state->buffer_used++] = rot13_char(buf[i]);
			if (buf[i] == ’\n’) 
			{
				state->writing = 1;
				state->write_upto = state->buffer_used;
			}
		}
	}
	if (result == 0) 
	{
		return 1;
	} 
	else if (result < 0) 
	{
		if (errno == EAGAIN)
			return 0;
		return -1;
	}
	return 0;
}
int do_write(int fd, struct fd_state * state)
{
	while (state->n_written < state->write_upto) 
	{
		ssize_t result = send(fd, state->buffer + state->n_written,
		state->write_upto - state->n_written, 0);
		if (result < 0) 
		{
			if (errno == EAGAIN)
				return 0;
			return -1;
		}
		assert(result != 0);
		state->n_written += result;
	}
	if (state->n_written == state->buffer_used)
	state->n_written = state->write_upto = state->buffer_used = 0;
	state->writing = 0;
	return 0;
}
void run(void)
{
	int listener;
	struct fd_state
	* state[FD_SETSIZE];
	struct sockaddr_in sin;
	int i, maxfd;
	fd_set readset, writeset, exset;
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = 0;
	sin.sin_port = htons(40713);
	for (i = 0; i < FD_SETSIZE; ++i)
	state[i] = NULL;
	listener = socket(AF_INET, SOCK_STREAM, 0);
	make_nonblocking(listener);
#ifndef WIN32
	{
		int one = 1;
		setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
	}
#endif
	if (bind(listener, (struct sockaddr * )&sin, sizeof(sin)) < 0) {
		perror("bind");
		return;
	}
	if (listen(listener, 16)<0) 
	{
		perror("listen");
		return;
	}
	FD_ZERO(&readset);
	FD_ZERO(&writeset);
	FD_ZERO(&exset);
	while (1) 
	{
		maxfd = listener;
		FD_ZERO(&readset);
		FD_ZERO(&writeset);
		FD_ZERO(&exset);
		FD_SET(listener, &readset);
		for (i=0; i < FD_SETSIZE; ++i) 
		{
			if (state[i]) 
			{
				if (i > maxfd)
				maxfd = i;
				FD_SET(i, &readset);
				if (state[i]->writing) 
					{
						FD_SET(i, &writeset);
					}
				}
		}
		
		if (select(maxfd+1, &readset, &writeset, &exset, NULL) < 0) 
		{
			perror("select");
			return;
		}
		if (FD_ISSET(listener, &readset)) 
		{
			struct sockaddr_storage ss;
			socklen_t slen = sizeof(ss);
			int fd = accept(listener, (struct sockaddr * )&ss, &slen);
			if (fd < 0) 
			{
				perror("accept");
			} 
			else if (fd > FD_SETSIZE) 
			{
				close(fd);
			} 
			else 
			{
				make_nonblocking(fd);
				state[fd] = alloc_fd_state();
				assert(state[fd]);/* XXX */
			}
		}
		for (i=0; i < maxfd+1; ++i) 
		{
			int r = 0;
			if (i == listener)
			continue;
			if (FD_ISSET(i, &readset)) 
			{
				r = do_read(i, state[i]);
			}
			if (r == 0 && FD_ISSET(i, &writeset)) 
			{
				r = do_write(i, state[i]);
			}
			if (r) 
			{
				free_fd_state(state[i]);
				state[i] = NULL;
				close(i);
			}
		}
	}
}
int main(int c, char** v)
{
	setvbuf(stdout, NULL, _IONBF, 0);
	run();
	return 0;
}

这还没完,因为生成和读select()的二进制流花费的时间与需要的最大fd成正比,而当socket的数量非常大的时候,select()的花费将会更恐怖.

 

不同的操作系统提供了不同的替代select()功能的函数,例如poll()eopll()kqueqe()evports/dev/poll.这些都比select()具有更好的性能,除了poll()之外增加一个套接字、删除一个套接字以及通知套接字已经为IO准备好了这些动作的时间花费都是O(1).

 

不幸的是,这些都没有一个有效的接口统一标准.Linuxeopll(),BSD(包含Darwin)kqueue(),Solarisevports/dev/poll,除此之外这些操作系统没有别的接口了.所以如果你想要写一个可移植的高效异步处理应用程序,你需要用抽象的方法封装这些所有的接口,并且提供其中最有效的方法.

 

这些都是LibEventAPI最底层工作的API,提供了可代替select()的各种方法的统一接口,在运行的计算机上使用可用的最有效的版本.

 

下面有另一个版本的ROT13异步服务器,这一次,我们将使用LibEvent2来代替select(),注意fd_sets已经变为:使用通过select()poll()epoll()kqueue()等一系列函数实现的event_base结构来聚合和分离事件.

 

示例:一个给予LibEvent实现的低级ROT13

/*For sockaddr_in*/ 
#include <netinet/in.h>
/*For socket functions*/ 
#include <sys/socket.h>
/*For fcntl*/ 
#include <fcntl.h>
#include <event2/event.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#define MAX_LINE 16384
void do_read(evutil_socket_t fd, short events, void*arg);
void do_write(evutil_socket_t fd, short events, void*arg);
char rot13_char(char c)
{
   	/*We don 't want to use isalpha here; setting the locale would
     change*which characters are considered alphabetical.*/ 
	if ((c >= a ' && c<= 'm ') || (c >= 'A ' && c <= 'M '))return c + 13; 
  	else if ((c >= 'n' && c <= 'z') || (c >= 'N' && c <= 'Z'))return c - 13; 
  	else
  	return c; 
}

struct fd_state
{
  	char buffer[MAX_LINE]; 
	size_t buffer_used; 
	size_t n_written; 
	size_t
    	write_upto; 
	struct event*read_event; 
	struct event*write_event; 
}; 
struct fd_state*alloc_fd_state(struct event_base*base, evutil_socket_t fd)
{
  	struct fd_state*state = malloc(sizeof(struct fd_state)); 
	if (!state)	return NULL; 
	state->read_event = event_new(base, fd, EV_READ | EV_PERSIST, do_read,state); 
	if (!state->read_event)
  	{
    		free(state); return NULL; 
  	} 
	state->write_event = event_new(base, fd, EV_WRITE | EV_PERSIST, do_write,state); 
	if (!state->write_event)
  	{
    		event_free(state->read_event); free(state); return NULL; 
  	}
  	state->buffer_used = state->n_written = state->write_upto = 0; 
	assert(state->write_event); 
	return state; 
}

void free_fd_state(struct fd_state*state)
{
  	event_free(state->read_event); 
	event_free(state->write_event); 
	free(state); 
} 
void do_read(evutil_socket_t fd, short events, void*arg)
{
  	struct fd_state*state = arg; char buf[1024]; int i; ssize_t result; 
  	while (1)
  	{
    		assert(state->write_event); 
		result = recv(fd, buf, sizeof(buf), 0); 
		if(result <= 0)break; 
		for (i = 0; i < result; ++i)
    		{
      			if (state->buffer_used < sizeof(state->buffer))
			state->buffer[state->buffer_used++] = rot13_char(buf[i]); 
	 		if (buf[i] == '\n')
      			{
        				assert(state->write_event); 				
				event_add(state->write_event, NULL); 
				state->write_upto = state->buffer_used; 
      			} 
    		}
  	}
  	if (result == 0)
  	{
    		free_fd_state(state); 
  	}
  	else if (result < 0)
  	{
    		if (errno == EAGAIN) // XXXX use evutil macro
    			return ; 
		perror("recv"); 
		free_fd_state(state); 
  	}
}

void do_write(evutil_socket_t fd, short events, void*arg)
{
  	struct fd_state*state = arg; 
  	while (state->n_written < state->write_upto)
  	{
    		ssize_t result = send(fd, state->buffer + state->n_written, state
      		->write_upto - state->n_written, 0); 
	if (result < 0)
    {
      	if (errno == EAGAIN) // XXX use evutil macro
      		return ; 
	free_fd_state(state); 
	return ; 
    } 
	assert(result != 0); 
	state->n_written += result; 
  }
  if (state->n_written == state->buffer_used)
	state->n_written = state->write_upto = state->buffer_used = 1; 		event_del(state->write_event); 
}

void do_accept(evutil_socket_t listener, short event, void*arg)
{
  	struct event_base*base = arg; 
	struct sockaddr_storage ss; 
	socklen_t slen =sizeof(ss); 
	int fd = accept(listener, (struct sockaddr*) &ss, &slen); 
	if (fd < 0)
  	{
    		// XXXX eagain??
    		perror("accept"); 
  	} 
 	else if (fd > FD_SETSIZE)
  	{
    		close(fd);  // XXX replace all closes with EVUTIL_CLOSESOCKET
    	}
 	else
 	{
    		struct fd_state*state; 
		evutil_make_socket_nonblocking(fd); 
		state =alloc_fd_state(base, fd); 
		assert(state);  /*XXX err */ 
		assert(state->write_event); 
		event_add(state->read_event, NULL); 
  	}
}

void run(void)
{
  	evutil_socket_t listener; 
	struct sockaddr_in sin; 
	struct event_base*base;
    	struct event*listener_event; 
	base = event_base_new(); 
	if (!base)return ;  /*XXXerr */ 
	sin.sin_family = AF_INET; sin.sin_addr.s_addr = 0;
    	sin.sin_port = htons(40713); 
	listener = socket(AF_INET, SOCK_STREAM, 0);
    	evutil_make_socket_nonblocking(listener); 
  	#ifndef WIN32
    	{
      		int one = 1; 
		setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); 
    	} 
  	#endif 
  	if (bind(listener, (struct sockaddr*) &sin, sizeof(sin)) < 0)
  	{
    		perror("bind"); return ; 
  	} 
	if (listen(listener, 16) < 0)
  	{
    		perror("listen"); 
		return ; 
  	}
  	listener_event = event_new(base, listener, EV_READ | EV_PERSIST, do_accept, 
    	(void*)base);  /*XXX check it*/ 
	event_add(listener_event, NULL);
    	event_base_dispatch(base); 
}

int main(int c, char**v)
{
  	setvbuf(stdout, NULL, _IONBF, 0); 
	run(); 
	return 0; 
}

代码中需要注意:socket的类型定义我们使用evutil_socket_t类型来代替int,使用evutil_make_socket_noblocking来代替fcntl(O_NOBLOCK)生成一个非阻塞式socket.这样使我们的代码能够很好的兼容win32网络API.

3.1怎样才能更方便?(Windows下怎么弄)

你可能也注意到了,随着我们的代码的效率越来越高,代码的也越来越复杂.回到之前,我们不必亲自管理每个连接的缓冲区,而只需要为每个过程分配栈内存.我们也不必去监控socket是可以读还是可写,这些都是自动隐含在我们的代码中的.我们也不需要一个结构体去跟踪每个操作有多少完成了,只需要使用循环和栈变量就够了.

 

此外,如果你深入Windows网络编程,你可能意识到在上面的例子中LibEvent可能并没有表现出最优的性能.Windows下要进行高速的异步IO不会使用select()接口,而是使用IOCP(完成端口) API.与其他快速的网络API不同,socket为操作准备好的时候,程序在即将执行操作之前IOCP是不会通知的.相反,程序会告诉Windows网络栈开始网络操作,IOCP会通知程序操作完成.

 

幸运的是,LibEvent2bufferevents接口解决了这两个问题:首先使程序更加简单,其次提供了在WindowsLinux上高效运行的接口.

 

下面是我们用bufferevents编写的最后一个ROT13服务器.

 

示例:使用LibEvent编写的简单ROT13服务器

/* For sockaddr_in*/
#include <netinet/in.h>
/* For socket functions*/
#include <sys/socket.h>
/* For fcntl*/
#include <fcntl.h>
#include <event2/event.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#define MAX_LINE 16384
void do_read(evutil_socket_t fd, short events, void* arg);
void do_write(evutil_socket_t fd, short events, void* arg);
char rot13_char(char c)
{
	/* We don’t want to use isalpha here; setting the locale would change
	which characters are considered alphabetical.*/
	if ((c >= ’a’ && c <= ’m’) || (c >= ’A’ && c <= ’M’))
		return c + 13;
	else if ((c >= ’n’ && c <= ’z’) || (c >= ’N’ && c <= ’Z’))
		return c - 13;
	else
		return c;
}
void readcb(struct bufferevent* bev, void * ctx)
{
	struct evbuffer* input, * output;
	char* line;
	size_t n;
	int i;
	input = bufferevent_get_input(bev);
	output = bufferevent_get_output(bev);
	while ((line = evbuffer_readln(input, &n, EVBUFFER_EOL_LF))) 
	{
		for (i = 0; i < n; ++i)
			line[i] = rot13_char(line[i]);
		evbuffer_add(output, line, n);
		evbuffer_add(output, "\n", 1);
		free(line);
	}
	if (evbuffer_get_length(input) >= MAX_LINE) 
	{
		/* Too long; just process what there is and go on so
		that the buffer doesn’t grow infinitely long.*/
		char buf[1024];
		while (evbuffer_get_length(input)) 
		{
			int n = evbuffer_remove(input, buf, sizeof(buf));
			for (i = 0; i < n; ++i)
			buf[i] = rot13_char(buf[i]);
			evbuffer_add(output, buf, n);
		}
		evbuffer_add(output, "\n", 1);
	}
}
void errorcb(struct bufferevent* bev, short error, void * ctx)
{
	if (error & BEV_EVENT_EOF) 
	{
		/* connection has been closed, do any clean up here*/

	} else if (error & BEV_EVENT_ERROR) 
	{
		/* check errno to see what error occurred*/
	} else if (error & BEV_EVENT_TIMEOUT) 
	{
		/* must be a timeout event handle, handle it*/
	}
	bufferevent_free(bev);
}

void do_accept(evutil_socket_t listener, short event, void* arg)
{
	struct event_base* base = arg;
	struct sockaddr_storage ss;
	socklen_t slen = sizeof(ss);
	int fd = accept(listener, (struct sockaddr * )&ss, &slen);
	if (fd < 0) 
	{
		perror("accept");
	} 
	else if (fd > FD_SETSIZE) 
	{
		close(fd);
	} 
	else 
	{
		struct bufferevent* bev;
		evutil_make_socket_nonblocking(fd);
		bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
		bufferevent_setcb(bev, readcb, NULL, errorcb, NULL);
		bufferevent_setwatermark(bev, EV_READ, 0, MAX_LINE);
		bufferevent_enable(bev, EV_READ|EV_WRITE);
	}
}

void run(void)
{
	evutil_socket_t listener;
	struct sockaddr_in sin;
	struct event_base * base;
	struct event* listener_event;
	base = event_base_new();
	if (!base)	return; /*XXXerr*/
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = 0;
	sin.sin_port = htons(40713);
	listener = socket(AF_INET, SOCK_STREAM, 0);
	evutil_make_socket_nonblocking(listener);
	#ifndef WIN32
	{
		int one = 1;
		setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
	}
	#endif
	if (bind(listener, (struct sockaddr * )&sin, sizeof(sin)) < 0) 
	{
		perror("bind");
		return;
	}
	if (listen(listener, 16)<0) 
	{
		perror("listen");
		return;
	}
	listener_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, 
	(void * )base);
	/* XXX check it*/
	event_add(listener_event, NULL);
	event_base_dispatch(base);
}

int main(int c, char** v)
{
	setvbuf(stdout, NULL, _IONBF, 0);
	run();
	return 0;
}

3.2这一切效率如何,当真?

在这里写了一个高效的代码.libevent页面上的基准是过时了

  • 这些文件是版权(c)2009 - 2012年由尼克·马修森和可用创意下议院Attribution-Noncommercial-Share都许可,3.0版.未来版本

  • 可能会用更少的限制性许可协议.

  • 此外,这些文档的源代码示例都是基于"3条款""修改的"BSD许可,请参考license_bsd文件全部条款.

  • 本文档的最新版本,请参阅【http://libevent.org/】

  • 本文档对应的最新版本代码,请安装git然后执行【git clone git://github.com/nmathewson/libevent-book.git】

4.正文前页

4.11000英尺看LibEvent

LibEvent是用于编写高速可移植的非阻塞IO,它的目标是:

  • 可移植性:使用LibEvent编写的程序应该在LibEvent支持跨越的所有平台上工作,即使没有更好的方法来处理非阻塞式IO:LibEvent也应该支持一般的方法使程序可以运行在某些限制的环境中.

  • 速度:LibEvent试图在每一个平台实现最快的非阻塞式IO,而不会引入太多的额外开销.

  • 可扩展性:LibEvent设计为即使在成千上万的socket情况下也能良好工作.

  • 方便:无论在什么情况下,用LibEvent来编写程序最自然的方式都应该是稳定可靠的.

     

    LibEvent由下列组件构成:

  • evutil:用于抽象出不同平台网络实现的通用功能.

  • event and event_base:libevent的核心,为各种平台特定的、基于事件的非阻塞 IO后端提供抽象 API,让程序可以知道套接字何时已经准备好,可以读或者写,并且处理基本的超时功能,检测 OS信号.

  • eufferevent: libevent基于事件的核心提供使用更方便的封装.除了通知程序套接字已经准备好读写之外,还让程序可以请求缓冲的读写操作,可以知道何时 IO已经真正发生.(bufferevent接口有多个后端,可以采用系统能够提供的更快的非阻塞 IO方式 , Windows中的 IOCP)

  • evbuffer: bufferevent层之下实现了缓冲功能,并且提供了方便有效的访问函数.

  • evhttp:一个简单的 HTTP客户端/服务器实现.

  • evdns:一个简单的 DNS客户端/服务器实现.

  • evrpc:一个简单的 RPC实现.

     

4.2

创建 libevent,默认安装下列库:

  • libevent_core:所有核心的事件和缓冲功能,包含了所有的 event_baseevbufferbufferevent和工具函数.

  • libevent_extra:定义了程序可能需要,也可能不需要的协议特定功能,包括 HTTPDNS RPC.

  • libevent:这个库因为历史原因而存在,它包含 libevent_core libevent_extra的内容 .不应该使用这个库未来版本的 libevent可能去掉这个库.

     

    某些平台上可能安装下列库:

  • libevent_pthreads:添加基于 pthread可移植线程库的线程和锁定实现.它独立于

  • libevent_core,这样程序使用 libevent时就不需要链接到 pthread,除非是以多线程方式使用 libevent.

  • libevent_openssl:这个库为使用 bufferevent OpenSSL进行加密的通信提供支持 .它独立于 libevent_core,这样程序使用 libevent时就不需要链接到 OpenSSL,除非是进行加密通信.

     

4.3头文件

libevent公用头文件都安装在 event2目录中,分为三类:

  • API头文件:定义 libevent公用接口.这类头文件没有特定后缀.

  • 兼容头文件:为已废弃的函数提供兼容的头部包含定义.不应该使用这类头文件,除非是在移植使用较老版本 libevent的程序时.

  • 结构头文件:这类头文件以相对不稳定的布局定义各种结构体.这些结构体中的一些是为了提供快速访问而暴露;一些是因为历史原因而暴露.直接依赖这类头文件中的任何结构体都会破坏程序对其他版本 libevent的二进制兼容性,有时候是以非常难以调试的方式出现.这类头文件具有后缀"_struct.h".(还存在不在 event2目录中的较老版本 libevent的头文件,请参考下节:如果需要使用老版本 libevent)

     

4.4如果需要使用老版本libevent

libevent 2.0以更合理的、不易出错的方式修正了 API.如果可能,编写新程序时应该使用libevent 2.0.但是有时候可能需要使用较老的 API,例如在升级已存的应用时,或者支持因为某些原因不能安装2.0或者更新版本 libevent的环境时.较老版本的 libevent头文件较少,也不安装在 event2目录中.

老版本头文件

当版本前头文件

event.h

event2/event*.h,event2/buffer*.h,

event2/bufferevent*.h,event2/tag*.h

evdns.h

event2/dns*.h

evhttp.h

event2/http*/

evrpc.h

event2/rpc*.h

evutil.h

event2/util*.h

 

2.0以及以后版本的 libevent,老的头文件仍然会作为新头文件的封装而存在.

 

其他关于使用较老版本的提示:

  • 1.4版之前只有一个库 libevent,它包含现在分散到 libevent_core libevent_extra中的所有功能.

  • 2.0版之前不支持锁定:只有确定不同时在多个线程中使用同一个结构体时,libevent才是线程安全的.

    下面的节还将讨论特定代码区域可能遇到的已经废弃的 API.

4.4.1版本状态告知

之前的LibEvent版本1.4.7应该是已经完全放弃了.版本1.3e之前的也应该是满是bug.(另外,请不要向LibEvent维护者发送任何在1.4x或更早版本的新的特色,这些版本都已经发行了realease版本.如果你在1.3x或更早的版本发现了一个bug,在你提交反馈报告之前请确认在最新版本也出现了这些bug.所以后续版本的发行都是有原因的.)

 

<<下一章>>




  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值