[原]我在Windows环境下的首个Libevent测试实例

  libevent对Windows环境也有很好的支持,不过初次学习和编译libevent简单实例,总是有一些陌生感的,只有成功编译并测试了一个实例,才会有恍然大悟的感觉。下面将要讲到的一个实例是我从网上抄过来的,原创文章地址为:http://www.felix021.com/blog/read.php?2068,表示感谢!

  在给出我的第一个测试成功的例子代码之前,简要介绍一下libevent入门的基本知识。原文中作者有一段话是这样的:

  “基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求(所谓c10k problem?),例如内存:默认情况下每个线程需要占用2~8M的栈空间。posix定义了可以使用异步的select系统调用,但是因为其采用了轮询的方式来判断某个fd是否变成active,效率不高[O(n)],连接数一多,也还是撑不住。于是各系统分别提出了基于异步/callback的系统调用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是对这些高效IO的封装,提供统一的API,简化开发。”

  这段话简单来说就是表达了这样一个意思:各大操作系统在内核层面上支持了异步(回调)形式的Socket编程技术,而libevent就是对这些接口进行了一下统一封装罢了。如果操作系统不作这样的支持呢?那么这一切都是白扯。

  libevent基础模型大致分为以下几个步骤:

(1)event_base

  每一个线程都有且仅有一个event_base,暂且称之为“事件管理器”吧(我自己随便起的名字),对应着一个struct event_base结构体,负责管理schedule托管给它的一系列事件(即下面要介绍的event)。当一个事件发生时,它负责在适当的时间(不一定是立即)去调用相关的回调函数。当回调函数执行完之后,再返回schedule其他事件。

struct event_base* base = event_base_new();

(2)event 与 event_new函数

  event_base内部有一个事件管理循环,阻塞在epoll/kqueue等系统调用上,直至有事件发生为止。event_base中管理的对象就是事件(event),这些事件通过event_new来创建和绑定。

struct event* listen_event = event_new(  base,            // 事件管理器对象  listener,         // 监听的对象,如socket  EV_READ | EV_PERSIST,  // 事件类型及属性  do_accept,         // 回调函数  (void*)base);       // 传递给回调函数的参数

  这里面涉及到一点libevent相关的类型声明,一个是事件类型及属性,包括如下种类:

(a)EV_TIMEOUT: 超时
(b)EV_READ: 只要网络缓冲中还有数据,回调函数就会被触发
(c)EV_WRITE: 只要塞给网络缓冲的数据被写完,回调函数就会被触发
(d)EV_SIGNAL: POSIX信号量,参考manual吧
(e)EV_PERSIST: 不指定这个属性的话,回调函数被触发后事件会被删除
(f)EV_ET: Edge-Trigger边缘触发,参考EPOLL_ET

  回调函数的声明原型为:

typedef void(* event_callback_fn)(  evutil_socket_t sockfd,  // 关联的句柄\文件描述符  short event_type,     // 事件类型  void *arg)         // 传递给回调函数的参数

(3)event_add 函数

  创建好的事件对象用event_add函数添加到消息循环队列中。

event_add(  listen_event,   // 事件对象  NULL);       // struct timeval* 类型指针,用于设置超时时间,NULL表示无超时设置

(4)event_base_dispatch 函数

  事件准备就绪之后,利用event_base_dispatch 函数启动消息循环。

event_base_dispatch(base);  // 事件管理器对象

  其实多看两遍就看熟悉了,libevent最简单的模型就是这四步,真的很简单。不过下面的例子还涉及到另一个问题,在网络编程中,当tcp服务器段accept建立了一个新的socket之后,如何准备读操作和写操作呢?我参考的原文中对这个问题给出了比较详细的历史描述。历史上,write和read事件分开管理,各自管理数据缓冲区等内容,操作起来非常的麻烦。在libevent2版本开始,引进了为bufferevent类型的管理器,这个管理器能够同时管理write\read\error事件。下面简要介绍一下使用步骤:

<A>设置SOCKET为非阻塞模式

evutil_make_socket_nonblocking(listener);

<B>创建bufferevent对象

struct bufferevent *bev = bufferevent_socket_new(  base,   // 事件管理器  fd,    // 关联的句柄\文件描述符  BEV_OPT_CLOSE_ON_FREE);  // 参数

<C>设置回调函数

bufferevent_setcb(  bev,      // bufferevent对象  read_cb,   // 读操作回调函数  NULL,     // 写操作回调函数  error_cb,   // 错误处理回调函数  arg);     // 参数

<D>启用事件管理

bufferevent_enable(bev, EV_READ | EV_WRITE | EV_PERSIST);

<E>执行回调函数

  这里涉及到三个回调函数:write_cb、read_cb和error_cb,其函数原型分别是:

// read_cb和write_cb的原型是
void read_or_write_callback(struct bufferevent *bev, void *arg)

// error_cb的原型是
void error_cb(struct bufferevent *bev, short error, void *arg)

  好了,基础知识就介绍到这里,下面直接给出成功实例代码(在Win7 + VS2008上亲测):

  1 #include <iostream>
  2 #include <WinSock2.h>    // Windows环境下的网络通信
  3 #include "event.h"        // 使用libevent函数库
  4 using namespace std;
  5
  6 // 加载所有相关的静态链接库,也可以在工程参数中指定
  7 #pragma comment(lib, "ws2_32.lib")
  8 #pragma comment(lib, "libevent.lib")
  9 #pragma comment(lib, "libevent_core.lib")
 10 #pragma comment(lib, "libevent_extras.lib")
 11
 12 // 回调函数声明
 13 void do_accept(evutil_socket_t listener, short event, void *arg);
 14 void read_cb(struct bufferevent *bev, void *arg);
 15 void error_cb(struct bufferevent *bev, short event, void *arg);
 16 void write_cb(struct bufferevent *bev, void *arg);
 17
 18 int main()
 19 {
 20     // 创建SOCKET
 21     WSAData wsaData;
 22     WSAStartup(MAKEWORD(2, 1), &wsaData);    // Windows环境下网络编程必备
 23     int ret = 0;
 24     evutil_socket_t listener;
 25     listener = socket(AF_INET, SOCK_STREAM, 0);
 26     evutil_make_listen_socket_reuseable(listener);    // 通用函数:设置SOCKET复用
 27
 28     // 绑定本地地址
 29     SOCKADDR_IN sin;
 30     sin.sin_family = AF_INET;
 31     sin.sin_addr.s_addr = ADDR_ANY;
 32     sin.sin_port = htons(6789);
 33     if (bind(listener, (SOCKADDR *)&sin, sizeof(sin)) < 0)
 34     {
 35         cout << "bind出错!" << endl;
 36         return -1;
 37     }
 38     if (listen(listener, 32) < 0)
 39     {
 40         cout << "listen出错!" << endl;
 41         return -1;
 42     }
 43     cout << "正在监听……" << endl;
 44
 45     // <A>设置SOCKET无阻塞模式
 46     evutil_make_socket_nonblocking(listener);
 47
 48     // (1)创建“事件管理器”
 49     struct event_base* base = event_base_new();
 50     if (NULL == base)
 51     {
 52         cout << "event_base_new出错!" << endl;
 53         return -1;
 54     }
 55
 56     // (2)创建事件
 57     struct event* listen_event = event_new(base, listener, EV_READ | EV_PERSIST, do_accept, (void*)base);
 58
 59     // (3)添加事件
 60     event_add(listen_event, NULL);
 61
 62     // (4)启动事件管理循环
 63     event_base_dispatch(base);
 64
 65     cout << "Done!" << endl;
 66     return 0;
 67 }
 68
 69 void do_accept(evutil_socket_t listener, short event, void *arg)
 70 {
 71     struct event_base* base = (struct event_base *)arg;
 72     SOCKADDR_IN sin;
 73     int slen = sizeof sin;
 74     evutil_socket_t fd = accept(listener, (SOCKADDR *)&sin, &slen);
 75     if (fd < 0)
 76     {
 77         cout << "accept出错!" << endl;
 78         return;
 79     }
 80     //if (fd > FD_SETSIZE)
 81     //{
 82     //    cout << "accept返回fd超出FD_SETSIZE限制" << endl;
 83     //    return;
 84     //}
 85     cout << "accept:fd=" << fd << endl;
 86
 87     // <B>创建“读写事件管理器”,自libevent2之后才有的
 88     struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
 89
 90     // <C>设置“读写事件管理器”的回调函数
 91     bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
 92
 93     // <D>启动“读写事件管理器”
 94     bufferevent_enable(bev, EV_READ | EV_WRITE | EV_PERSIST);
 95 }
 96
 97 void read_cb(struct bufferevent *bev, void *arg)
 98 {
 99 #define MAX_LINE    256
100     char szLine[MAX_LINE + 1];
101     evutil_socket_t fd = bufferevent_getfd(bev);
102
103     int n = 0;
104     while (n = bufferevent_read(bev, szLine, MAX_LINE), n > 0)
105     {
106         szLine[n] = ‘\0‘;
107         cout << "Read Line:" << szLine << endl;
108         bufferevent_write(bev, szLine, n);
109     }
110 }
111
112 void write_cb(struct bufferevent *bev, void *arg)
113 {
114
115 }
116
117 void error_cb(struct bufferevent *bev, short event, void *arg)
118 {
119     evutil_socket_t fd = bufferevent_getfd(bev);
120     cout << "error:fd=" << fd << endl;
121     if (event & BEV_EVENT_TIMEOUT)
122     {
123         cout << "Time out!" << endl;
124     }
125     else if (event & BEV_EVENT_EOF)
126     {
127         cout << "EOF!" << endl;
128     }
129     else if    (event & BEV_EVENT_ERROR)
130     {
131         cout << "Error!" << endl;
132     }
133     bufferevent_free(bev);
134 }

时间: 2024-10-25 19:35:15

[原]我在Windows环境下的首个Libevent测试实例的相关文章

【大数据系列】windows环境下搭建hadoop开发环境从hadoop URL读取数据

前言 搭建完hadoop集群之后在windows环境下搭建java项目进行测试 操作hdfs中的文件 版本一 package com.slp.hadoop274.hdfs; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import org.apache.hadoop.fs.FsUrlStreamHandlerFactory;

Windows环境下堆管理系统的快表介绍

实验环境: 操作系统: Windows 2000 Service Pack 4 集成开发环境: Microsoft Visual C++ 6.0 SP6 构建版本:Release版本 实验代码: 1 #include <stdio.h> 2 #include <windows.h> 3 4 void main() 5 { 6 HLOCAL h1, h2, h3, h4; 7 HANDLE hp; 8 9 // 启用快表 10 hp = HeapCreate(0, 0, 0); 11

定制windows环境下cmd替代软件ConEmu

定制windows环境下cmd替代软件ConEmu 公认的是Windows环境下命令行界面太难用. 不管是cmd还是powershell都不顺手!!窗口宽度不能全屏,字体太难看还不能调整,窗口背景不能更换,永远的黑白配,最痛苦的是复制和粘贴.都不知道MS是怎么想的?搞个这么个反人类的东西出来,还万年不变,从Windows诞生到Windows7不论是桌面版还是服务器版都一样,到了windows8/windows10换成powershell一样的鸡肋. 在百度搜了一下,找到cmd的替代软件conem

《高可用MySQL》1 – Windows环境下压缩版MySQL安装

近日在读O'REILIY系列的<高可用MySQL>, 自然少不了主从(Master-Slave)配置和横向扩展相关的内容. Master-Slave这东西吧.在很多公司都是标配.开发中基本天天都用.遇到的问题自然也不少(如主从不同步,Master宕机).但操作权限很有限.有些东西,仅仅有自己看了.做了,才干真正知道原理是什么,也才干更好的去把握. 本文是高可用MySQL的第一篇读书笔记,主要记录Windows环境下压缩版MySQL(基于安装版的傻瓜式安装过程这里不再提及)的安装过程. 1. 从

python MySQLdb在windows环境下的快速安装、问题解决方式

使用Python访问MySQL,需要一系列安装 Linux下MySQLdb安装见 Python MySQLdb在Linux下的快速安装 http://blog.csdn.net/wklken/article/details/7271019 ------------------------------------------------------------- 以下是windows环境下的: 1.      安装数据库mysql 下载地址:http://www.mysql.com/downloa

Windows环境下Android Studio v1.0安装教程

Windows环境下Android Studio v1.0安装教程 Windows环境下Android Studio v1.0安装教程 准备工具 JDK安装包. 要求:JDK 7以及以上版本. Android Studio安装文件. Windows: exe(包含SDK) (813 MB) exe(不包含SDK) (250 MB) zip (235 MB) Mac dmg (234 MB) zip (233 MB) Linux: zip (233 MB) 说明: 32位系统和64位系统是同一个安

手把手教你在Windows环境下升级R

在Windows环境下,我们可以使用installr包自动将R升级到最新版本.并且可以安装软件.下面主要演示如何在Windows环境下升级R,并将旧版本安装的R包复制到更新版本的R. 1.加载installr包 install.packages("installr") library(installr) 2.在“installr”菜单中选择“update R” 3.installr将会检测是否发布了新版本的R,如果检查到有单击“确定”即可更新R. 4.如果想了解新版本R的相关特性介绍,

[转]Windows环境下尝试安装并配置PHP PEAR备忘

转自:http://wangye.org/blog/archives/266/ 什么是PEAR 来自百度百科:PEAR 是PHP扩展与应用库(the PHP Extension and Application Repository)的缩写.它是一个PHP扩展及应用的一个代码仓库,简单地说,PEAR之于PHP就像是CPAN(Comprehensive Perl Archive Network)之于Perl. 由此可见PEAR是PHP代码的仓库,在这里可以找到很多有用的代码,避免我们重复写一些功能,

Windows环境下使用Redis缓存工具的图文详细方法

网上找了两篇关于Redis的博客,记录下! Java 使用Redis缓存工具的图文详细方法 Windows环境下使用Redis缓存工具的图文详细方法