zoukankan      html  css  js  c++  java
  • [原]我在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 eaderror事件。下面简要介绍一下使用步骤:

    <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] = '';
    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 }
    View Code
  • 相关阅读:
    Docker 日志都在哪里?怎么收集?
    docker link 过时不再用了?那容器互联、服务发现怎么办?
    Centos7.0 配置docker 镜像加速
    使用docker搭建公司redmine服务器
    Jenkins pipeline:pipeline 使用之语法详解
    Xcode脚本自动化打包问题:xcrun: error: unable to find utility "PackageApplication", not a developer tool or in PATH
    java基础(7)---IO流
    java基础(6)---面向对象,类,包
    java基础(5)---内存分配
    java基础(4)---引用数据类型(数组、字符串、集合)
  • 原文地址:https://www.cnblogs.com/kuliuheng/p/3985737.html
Copyright © 2011-2022 走看看