利用libevent库开发网路程序

 


【简介】
libevent是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、

kqueue等系统调用管理事件机制。著名分布式缓存软件memcached也是libevent based,而且libevent在使

用上可以做到跨平台,而且根据libevent官方网站上公布的数据统计,似乎也有着非凡的性能。

 

【基础函数】
1. event_init() event_base_new()
   在使用事件API前必须使用event_init()或event_base_new()初始化,注意其不支持多线程,稍后有介

绍。
   event_base_new()也可以进行初始化,其配合互斥锁可以用于多线程,是线程安全的。

2. event_dispatch() event_base_dispatch()
   为了能够处理事件,应用程序必须调用event_dispatch(),该函数只当错误时返回,这时应该由用应程序

接管事件. event_base_dispatch()配合event_base_new使用,线程安全。

3. event_set() event_base_set()
event_set(struct event *ev, int fd, short event, void (*fn)(int, short, void *), void *arg);
用于生成事件结构体ev,以备event_add()和event_del()使用.事件驱动程序将会调用void (*fn)(int,

short, void *)中fn指定的函数,并传递三个参数int:文件描述符,short:事件类型,void*:其它参数由arg

参数指定. 初始化event结构体变量中的回调函数是程序调用者必须提供的.
参数:
  int fd 指定要监视的文件描述符,

  short event 可以是EV_READ,EV_WRITE或EV_READ|EV_WRITE表示该文件可以无阻塞地进行读写.

  fn函数将会被调用,并传递给三个变量:

  int fd:触发事件的文件描述符.

  short event:触发事件的类型EV_TIMEOUT,EV_SIGNAL, EV_READ, or EV_WRITE.

  void* :由arg参数指定的变量.

  另外重复注册的事件将会产生重复的事件通知.EV_PERSIST可以让注册的事件在执行完后不被删除,直

到调用event_del()删除.

  结构体初始化完成后,在无需改变内容的情况下,可以被event_add(),event_del()重复使用.但是当结

构体被 event_add()添加之后,必须保持结构体的内容,直到事件被执行后退出或调用event_del()删除该事

件.不允许将这个结构体变量注册完事件后重复使用.每一个描述符都需要一个单独的event结构体变量.

线程安全版本,配合event_base_new要使用event_base_set。
int event_base_set  ( struct event_base *  struct event * )   
Associate a different event base with an event.
Parameters:
 eb  the event base 
 ev  the event 

4. event_add()
event_add()函数使通过event_set()设置的事件在事件匹配或超时时(如果设置了超时)被执行.
event结构体变量必须先用event_set()初始化过,并且在事件被删除前不得再次调用event_set()来初始化

之.如果事件发生超时,旧的超时时间会被新的超时时间所取代.


5. event_del()
event_del()函数会取消event结构体所指定的事件,如果该事件已经执行或没有注册(在事件链表中不存

在),该函数不会产生任何作用.
6. 时间定时器系列
  evtimer_set(), evtimer_add(), evtimer_del(),evtimer_initialized() evtimer_pending()等函数

()用于设置定时或超时操作.在这些函数中,文件描述符为-1,事件类型为EV_TIMEOUT.
7. 信号系列
  signal_set(), signal_add(), signal_del(),signal_initialized(),signal_pending()等函数从略,

其中事件类型为 EV_SIGNAL.那就意味着signal_set() 添加了EV_PERSIST.
为了避免信号竞争,事件API提供了两程变量:event_sigcb 和 event_gotsig.某个信号的句柄设置

event_gotsig表示收到信号.应用程序把event_sigcb设置成一个回调函数.当信号句柄设置了event_gotsig

之后,event_dispatch函数会执行回调函数处理接收到的信号.当没有事件注册时回调函数返回1.回调函数

可以返回-1表示错误,这将导致event_dispatch()结束,错误代码为EINTR.
8. event_pending
event_pending
  event_pending()用于检测event结构体变量指定的事件是否处于等待状态.如果设定了EV_TIMEOUT,并

且tv结构体指针变量非空,则事件终止时间由tv返回.
9.event_initialized
    event_initialized()用于检测event结构体变量是否已经初始化.
【示例代码】
---------------------------------开始---------------------------------------
#ifdef WIN32
#include <winsock2.h>
#include <windows.h>
#include <io.h>
#include <fcntl.h>
#endif

#include "event-config.h"

#ifdef _EVENT___func__
#define __func__ _EVENT___func__
#endif //

#if 0
#include <sys/types.h>
#include <sys/stat.h>
#ifdef _EVENT_HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <sys/queue.h>
#include <signal.h>
#include <errno.h>
#endif //if 0

#ifdef _EVENT_HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/queue.h>

#ifndef WIN32
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <netdb.h>
#endif

#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>

#include <event2/event.h>
#include "event2/event_struct.h"
#include <event2/event_compat.h>
#include "event2/tag.h"
#include <event2/dns.h>
#include <event2/dns_compat.h>
#include <event2/bufferevent.h>
#include "event2/bufferevent_struct.h"
#include <event2/buffer.h>
#include "event2/buffer_compat.h"
#include <event2/thread.h>
#include "event-internal.h"
#include <event2/util.h>
#include "log-internal.h"
//#include "event-config.h"
#include <iocp-internal.h>


/关键代码段
#ifdef WIN32
#define write(fd,buf,len) send((fd),(buf),(len),0)
#define read(fd,buf,len) recv((fd),(buf),(len),0)
#endif

// .;"../libevent/include/WIN32-Code";../libevent/include/compat;../libevent/include
#ifdef WIN32
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "../libevent/lib/libevent.lib")
#endif //WIN32


static void simple_read_cb(evutil_socket_t fd, short event, void *arg);
static int cleanup_test(void);
//int pair[2];

#define TEST1_STR "test 1 string"
typedef struct basic_test_data {
 struct event_base *base;
 int pair[2];

 void (*legacy_test_fn)(void);

 void *setup_data;
}basic_test_data;

struct basic_test_data *g_data = NULL;
int g_spair[2] = { -1, -1 };

static void *basic_test_setup()
{
 struct event_base *base = NULL;
 //make socket pair for test
 if (evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, g_spair) == -1) {
  fprintf(stderr, "%s: socketpair\n", "basic_test_setup");
  exit(1);
 }

 if (evutil_make_socket_nonblocking(g_spair[0]) == -1) {
  fprintf(stderr, "fcntl(O_NONBLOCK)");
  exit(1);
 }

 if (evutil_make_socket_nonblocking(g_spair[1]) == -1) {
  fprintf(stderr, "fcntl(O_NONBLOCK)");
  exit(1);
 }


 //event_init()

 base = event_base_new();
 if (!base)
  exit(1);

 //base = event_base_new_with_config(NULL);
 //if (base == NULL) {
 //event_errx(1, "%s: Unable to construct event_base", __func__);
 // exit(1);
 //}

 g_data = (basic_test_data*)calloc(1, sizeof(*g_data));
 if (!g_data)
  exit(1);
 g_data->base = base;
 g_data->pair[0] = g_spair[0];
 g_data->pair[1] = g_spair[1];

 return g_data;

}

static int cleanup_test(void) {
    event_base_free(g_data->base);
#ifndef WIN32 
    close(g_spair[0]);
    close(g_spair[1]);
#else 
    CloseHandle((HANDLE)g_spair[0]); 
    CloseHandle((HANDLE)g_spair[1]);
#endif 
    free(g_data);
    return (0);
}
int test1_main(void *ptr)
{
 struct basic_test_data *data = (basic_test_data*)ptr;
 struct event ev1;

 

 write(g_spair[0], TEST1_STR, strlen(TEST1_STR)+1);
 shutdown(g_spair[0], 1);
 //write(g_spair[0], NULL, 0);
 
 event_set(&ev1, g_spair[1], EV_READ, simple_read_cb, &ev1);
 event_base_set(data->base, &ev1);
 if (event_add(&ev1, NULL) == -1)
  exit(1);
 
 event_base_dispatch(data->base);
 //event_base_loop(data->base,EVLOOP_NONBLOCK);
 
 return 0;

}

int main(int argc, char **argv)
{

 WORD wVersionRequested;
 WSADATA wsaData;
 int err;
 void * env = NULL;

 wVersionRequested = MAKEWORD(2, 2);
 err = WSAStartup(wVersionRequested, &wsaData);

 evthread_enable_lock_debuging();
 //初始化
 env = basic_test_setup();

 test1_main(env);  //simple read/write test
 //test2_main(env);  //iocp test
 //test3_main(env);    //bufferevent test
 cleanup_test();
 return 1;
}

---------------------------------结束---------------------------------------

 

【evbuffer简介】

 evbuffer数据缓冲区
libevent的缓冲是一个连续的内存区域,其处理数据的方式(写数据和读数据)更像一个

队列操作方式:从后写入,从前读出。evbuffer分别设置相关指针(一个指标)用于指示

读出位置和写入位置。其大致结构如图:

[K2-2]利用libevent库开发网路程序(二)

 

orig_buffer指向由realloc分配的连续内存区域,buffer指向有效数据的内存区域,totallen表示orig_buffer指向的内存区域的大小,misalign表示buffer相对于orig_buffer的偏移,off表示有效数据的长度。

 

【evbuffer相关函数】

 

X. evbuffer_new
创建缓冲evbuffer


X. evbuffer_read 
从文件描述符(socket句柄)中读取数据到evbuffer数据结构中
int evbuffer_read  ( struct evbuffer *  buffer, 
  evutil_socket_t  fd, 
  int  howmuch  
   

Read from a file descriptor and store the result in an evbuffer.
Parameters:
 buf  the evbuffer to store the result 
 fd  the file descriptor to read from 
 howmuch  the number of bytes to be read 

Returns:
the number of bytes read, or -1 if an error occurred

X. evbuffer_write 
evbuffer数据结构中数据写到文件描述符(socket句柄)中。

int evbuffer_write  ( struct evbuffer *  buffer, 
  evutil_socket_t  fd  
   

Write the contents of an evbuffer to a file descriptor.

The evbuffer will be drained after the bytes have been successfully written.

Parameters:
 buffer  the evbuffer to be written and drained 
 fd  the file descriptor to be written to 

Returns:
the number of bytes written, or -1 if an error occurred

 

X. evbuffer_drain:
    该函数主要操作一些指标,当每次从evbuffer里读取数据时,libevent便会将

buffer指针后移,同时增大misalign,减小off,而该函数正是做这件事的。说白了,

该函数就是用于调整缓冲队列的前向指标。
void  evbuffer_drain (struct evbuffer *, size_t)
  Remove a specified number of bytes data from the beginning of an evbuffer.
Parameters:
 buf  the evbuffer to be drained 
 len  the number of bytes to drain from the beginning of the buffer 

X. evbuffer_remove:
    该函数用于将evbuffer中的数据复制给用户空间(读数据)。简单地将数据memcpy,

然后调用evbuffer_drain移动相关指标。
int  evbuffer_remove (struct evbuffer *, void *, size_t)
Read data from an event buffer and drain the bytes read.
Parameters:
 buf  the event buffer to be read from 
 data  the destination buffer to store the result 
 datlen  the maximum size of the destination buffer 
Returns:
the number of bytes read

X. evbuffer_add:
    该函数用于添加一段用户数据到evbuffer中。很简单,就是先判断是否有足够的空

闲内存,如果没有则调用evbuffer_expand扩充之,然后直接memcpy,更新off指标。

int  evbuffer_add (struct evbuffer *, const void *, size_t)
Append data to the end of an evbuffer.
Parameters:
 buf  the event buffer to be appended to 
 data  pointer to the beginning of the data buffer 
 datlen  the number of bytes to be copied from the data buffer 

X. evbuffer_expand:
    该函数用于扩充evbuffer的容量。每次向evbuffer写数据时,都是将数据写到

buffer+off后,buffer到buffer+off之间已被使用,保存的是有效数据,而

orig_buffer和buffer之间则是因为读取数据移动指标而形成的无效区域。

evbuffer_expand的扩充策略在于,首先判断如果让出orig_buffer和buffer之间的空闲

区域是否可以容纳添加的数据,如果可以,则移动buffer和buffer+off之间的数据到

orig_buffer和orig_buffer+off之间(有可能发生内存重叠,所以这里移动调用的是

memmove),然后把新的数据拷贝到orig_buffer+off之后;如果不可以容纳,那么重新

分配更大的空间(realloc),同样会移动数据。扩充内存的策略为:确保新的内存区域

最小尺寸为256,且以乘以2的方式逐步扩大(256、512、1024、...)。

 

【示例代码】
主要函数代码,其它代码参考上篇文章

---------------------------------开始---------------------------------------

int test3_main(void *ptr)
{
 
 struct basic_test_data *data = (basic_test_data*)ptr;
 struct event ev1;

 write(g_spair[0], TEST1_STR, strlen(TEST1_STR)+1);
 shutdown(g_spair[0], 1);

 event_set(&ev1, g_spair[1], EV_READ, simple_evbuffer_read_cb, &ev1);
 event_base_set(data->base, &ev1);
 if (event_add(&ev1, NULL) == -1)
  exit(1);

 event_base_dispatch(data->base);

 return 0;
}

static void
simple_evbuffer_read_cb(evutil_socket_t fd, short event, void *arg)
{
 char buf[256];
 int len;
 struct evbuffer *tmp = evbuffer_new();
 len = evbuffer_read(tmp, fd, -1);
 

 

 if (len) {
  evbuffer_remove(tmp, buf, len);
  printf("simple_read_cb fd:�uf:%s\n", fd, buf);
  if (event_add(arg, NULL) == -1)
   exit(1);
 } else {
  printf("simple_read_cb fd:%d连接关闭\n",fd);
 }

}

 

---------------------------------结束---------------------------------------

 

【其它方面】
1. libevent中的epoll用的是水平触发,这种效率还是没有边缘触发高,直接使用

epoll还能带来性能上的提升可能性。

2. 线程安全问题
libevent如果用 event_init初始化,将不支持多线程,或者说它是多线程不安全的,

因此也受到限制。
解决的方案一:
参考文章:http://www.cppblog.com/converse/archive/2009/01/12/71809.html
http://it.newnew.cn/it53180.aspx
解决方案二:使用event_base_new()进行初始化,并使用互斥锁。

 

【参考】
X. libevent主页
http://monkey.org/~provos/libevent/
X. 官方文档
http://monkey.org/~provos/libevent/doxygen-2.0.1/index.html
X. 源码分析-libevent事件处理框架分析
http://www.cppblog.com/converse/archive/2009/01/03/71040.html
X. socketpair相关
http://liulixiaoyao.blog.51cto.com/1361095/533469
X. 教程
libevent网络编程
http://www.wangafu.net/~nickm/libevent-book/TOC.html
libevent网络编程 中文翻译
http://blog.sina.com.cn/s/articlelist_1457448730_0_1.html
http://blog.sina.com.cn/s/blog_56dee71a0100q2i9.html
http://blog.sina.com.cn/s/blog_56dee71a0100qx4s.html
其它
http://hustlg.bokee.com/6573083.html
http://blog.csdn.net/dlmu2001/archive/2010/06/20/5682456.aspx
http://monkey.org/~provos/libevent/doxygen-1.4.10/event_8h.html
X. echo server
http://ishbits.googlecode.com/svn-

history/r5/trunk/libevent_echosrv/libevent_echosrv1.c
X. evbuffer使用
http://www.cppblog.com/kevinlynx/archive/2008/07/16/56291.html
http://team.eyou.com/?p=32
X. spserver相关
http://code.google.com/p/spserver/
http://iunknown.javaeye.com/blog/59804

----------------------------------------------------------------------

本文原创,转载请注明出处 http://blog.sina.com.cn/faithfish

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值