热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

转libevent使用demo

这篇文章介绍下libevent在socket异步编程中的应用。在一些对性能要求较高的网络应用程序中,为了防止程序阻塞在socketIO操作上造成程序性能的下降

这篇文章介绍下libevent在socket异步编程中的应用。在一些对性能要求较高的网络应用程序中,为了防止程序阻塞在socket I/O操作上造成程序性能的下降,需要使用异步编程,即程序准备好读写的函数(或接口)并向系统注册,然后在需要的时候只向系统提交读写的请求之后就继续 做自己的事情,实际的读写操作由系统在合适的时候调用我们程序注册的接口进行。异步编程会给一些程序猿带来一些理解和编写上的困难,因为我们通常写的一些 简单的程序都是顺序执行的,而异步编程将程序的执行顺序打乱了,有些代码什么情况下执行往往不是太清晰,因此也使得编程的复杂度大大增加。

    Note:这里系统这个词使用的不准确,实际上可以是自己封装的异步调用机制,更常见的是一些可用的库,比如libevent,ACE等

 

    想了解libevent的工作原理可以自行查询资料,网上相关的介绍一大堆,也可以自己阅读源码进行分析,本文仅从使用的角度做一个简单的介绍,看如何快 速的将libevent引入我们的程序中。任何应用都免不了需要承载其功能的底层OS,libevent也不例外,其内部是通过封装操作系统的IO复用机 制实现的,在linux系统上可能是epoll、kqueu之类的,取决于具体的OS所支持的IO复用方式,在我的系统上是epoll,因此可以理解为 libevent提供了一个比epoll更为友好的操作接口,将程序猿从网络IO处理的细节中解放出来,使其可以专注于目标问题的处理上。

 

    首先,安装libevent到任意目录下

wget http://monkey.org/~provos/libevent-1.4.13-stable.tar.gz

tar –xzvf libevent-1.4.13-stable.tar.gz

cd libevent-1.4.13-stable

./configure --prefix=/home/mydir/libevent

make && make install

 

    现在假定我们要设计一个服务器程序,用于接收客户端的数据,并将接收的数据回写给客户端。下面来构造该程序,由于本仅仅是展示一个Demo,因此程序中将不对错误进行处理,假设所有的调用都成功

#define PORT 25341
#define BACKLOG 5
#define MEM_SIZE 1024

struct event_base* base;

int main(int argc, char* argv[])
{
10     struct sockaddr_in my_addr;
11     int sock;
12 
13     sock = socket(AF_INET, SOCK_STREAM, 0);
14     int yes = 1;
15     setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
16     memset(&my_addr, 0, sizeof(my_addr));
17     my_addr.sin_family = AF_INET;
18     my_addr.sin_port = htons(PORT);
19     my_addr.sin_addr.s_addr = INADDR_ANY;
20     bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));
21     listen(sock, BACKLOG);
22 
23     struct event listen_ev;
24     base = event_base_new();
25     event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);
26     event_base_set(base, &listen_ev);
27     event_add(&listen_ev, NULL);
28     event_base_dispatch(base);
29 
30     return 0;
31 }

    第13行说明创建的是一个TCP socket。第15行是服务器程序的通常做法&#xff0c;设置了该选项后&#xff0c;在父子进程模型中&#xff0c;当子进程为客户服务的时候如果父进程退出&#xff0c;可以重新启动程序完成服务 的无缝升级&#xff0c;否则在所有父子进程完全退出前再启动程序会在该端口上绑定失败&#xff0c;也即不能完成无缝升级的操作(更多信息可以参考该函数说明或Steven先生 的<网络编程>)。第24行用于创建一个事件处理的全局变量&#xff0c;可以理解为这是一个负责集中处理各种出入IO事件的总管家&#xff0c;它负责接收和派发所 有输入输出IO事件的信息&#xff0c;这里调用的是函数event_base_new(), 很多程序里这里用的是event_init()&#xff0c;区别就是前者是线程安全的、而后者是非线程安全的&#xff0c;后者在其官方说明中已经被标志为过时的函数、且建议用 前者代替&#xff0c;libevent中还有很多类似的函数&#xff0c;比如建议用event_base_dispatch代替event_dispatch&#xff0c;用 event_assign代替event_set和event_base_set等&#xff0c;关于libevent接口的详细说明见其官方说明libevent_doc. 第25行说明在listen_en这个事件监听sock这个描述字的读操作&#xff0c;当读消息到达是调用on_accept函数&#xff0c;EV_PERSIST参数告诉系 统持续的监听sock上的读事件&#xff0c;如果不加该参数&#xff0c;每次要监听该事件时就要重复的调用26行的event_add函数&#xff0c;从前面的代码可知&#xff0c;sock这个描 述字是bind到本地的socket端口上&#xff0c;因此其对应的可读事件自然就是来自客户端的连接到达&#xff0c;我们就可以调用accept无阻塞的返回客户的连接了。 第26行将listen_ev注册到base这个事件中&#xff0c;相当于告诉处理IO的管家请留意我的listen_ev上的事件。第27行相当于告诉处理IO的 管家&#xff0c;当有我的事件到达时你发给我(调用on_accept函数)&#xff0c;至此对listen_ev的初始化完毕。第28行正式启动libevent的事件处理 机制&#xff0c;使系统运行起来&#xff0c;运行程序的话会发现event_base_dispatch是一个无限循环。

 

下面是on_accept函数的内容

1: void on_accept(int sock, short event, void* arg)

2: {

3: struct sockaddr_in cli_addr;

4: int newfd, sin_size;

5: // read_ev must allocate from heap memory, otherwise the program would crash from segmant fault

6: struct event* read_ev &#61; (struct event*)malloc(sizeof(struct event));;

7: sin_size &#61; sizeof(struct sockaddr_in);

8: newfd &#61; accept(sock, (struct sockaddr*)&cli_addr, &sin_size);

9: event_set(read_ev, newfd, EV_READ|EV_PERSIST, on_read, read_ev);

10: event_base_set(base, read_ev);

11: event_add(read_ev, NULL);

12: }

    第9-12与前面main函数的24-26相同&#xff0c;即在代表客户的描述字newfd上监听可读事件&#xff0c;当有数据到达是调用on_read函数。这里有亮点需要 注意&#xff0c;一是read_ev需要从堆里malloc出来&#xff0c;如果是在栈上分配&#xff0c;那么当函数返回时变量占用的内存会被释放&#xff0c;因此事件主循环 event_base_dispatch会访问无效的内存而导致进程崩溃(即crash)&#xff1b;第二个要注意的是第9行read_ev作为参数传递给了 on_read函数。

 

下面是on_read函数的内容

1: void on_read(int sock, short event, void* arg)

2: {

3: struct event* write_ev;

4: int size;

5: char* buffer &#61; (char*)malloc(MEM_SIZE);

6: bzero(buffer, MEM_SIZE);

7: size &#61; recv(sock, buffer, MEM_SIZE, 0);

8: printf("receive data:%s, size:%d\n", buffer, size);

9: if (size &#61;&#61; 0) {

10: event_del((struct event*)arg);

11: free((struct event*)arg);

12: close(sock);

13: return;

14: }

15: write_ev &#61; (struct event*) malloc(sizeof(struct event));;

16: event_set(write_ev, sock, EV_WRITE, on_write, buffer);

17: event_base_set(base, write_ev);

18: event_add(write_ev, NULL);

19: }

    第9行&#xff0c;当从socket读返回0标志对方已经关闭了连接&#xff0c;因此这个时候就没必要继续监听该套接口上的事件&#xff0c;由于EV_READ在on_accept函数 里是用EV_PERSIST参数注册的&#xff0c;因此要显示的调用event_del函数取消对该事件的监听。第18-21行与on_accept函数的6-11 行类似&#xff0c;当可写时调用on_write函数&#xff0c;注意第19行将buffer作为参数传递给了on_write。这段程序还有比较严重的问题&#xff0c;后面进行说明。

 

on_write函数的实现

void on_write(int sock, short event, void* arg)
{
    char* buffer &#61; (char*)arg;
    send(sock, buffer, strlen(buffer), 0);

6     free(buffer);

     on_write函数中向客户端回写数据&#xff0c;然后释放on_read函数中malloc出来的buffer。在很多书合编程指导中都很强调资源的所有权&#xff0c;经 常要求谁分配资源、就由谁释放资源&#xff0c;这样对资源的管理指责就更明确&#xff0c;不容易出问题&#xff0c;但是通过该例子我们发现在异步编程中资源的分配与释放往往是由不同的所 有者操作的&#xff0c;因此也是比较容易出问题的地方。

 

    其实在on_read函数中从socket读取数据后程序就可以直接调用write/send接口向客户回写数据了&#xff0c;因为写事件已经满足&#xff0c;不存在异步不异 步的问题&#xff0c;这里进行on_write的异步操作仅仅是为了说明异步编程中资源的管理与释放的问题&#xff0c;另外一方面&#xff0c;直接调用write/send函数向客户端 写数据可能导致程序较长时间阻塞在IO操作上&#xff0c;比如socket的输出缓冲区已满&#xff0c;则write/send操作阻塞到有可用的缓冲区之后才能进行实际的写 操作&#xff0c;而通过向写事件注册on_accept函数&#xff0c;那么libevent会在合适的时间调用我们的callback函数&#xff0c;(比如对于会引起IO阻塞的情况 比如socket输出缓冲区满&#xff0c;则由libevent设计算法来处理&#xff0c;如此当回调on_accept函数时我们在调用IO操作就不会发生真正的IO之外的 阻塞)。注&#xff1a;前面括号中是我个人认为一个库应该实现的功能&#xff0c;至于libevent是不是实现这样的功能并不清楚也无意深究。

 

    再来看看前面提到的on_read函数中存在的问题&#xff0c;首先write_ev是动态分配的内存&#xff0c;但是没有释放&#xff0c;因此存在内存泄漏&#xff0c;另外&#xff0c;on_read中进 行malloc操作&#xff0c;那么当多次调用该函数的时候就会造成内存的多次泄漏。这里的解决方法是对socket的描述字可以封装一个结构体来保护读、写的事件 以及数据缓冲区&#xff0c;整理后的完整代码如下

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>

#include <event.h>


#define PORT        25341
#define BACKLOG     5
#define MEM_SIZE    1024

struct event_base* base;
struct sock_ev {
    struct event* read_ev;
    struct event* write_ev;
    char* buffer;
};

void release_sock_event(struct sock_ev* ev)
{
    event_del(ev->read_ev);
    free(ev->read_ev);
    free(ev->write_ev);
    free(ev->buffer);
    free(ev);
}

void on_write(int sock, short event, void* arg)
{
    char* buffer &#61; (char*)arg;
    send(sock, buffer, strlen(buffer), 0);

    free(buffer);
}

void on_read(int sock, short event, void* arg)
{
    struct event* write_ev;
    int size;
    struct sock_ev* ev &#61; (struct sock_ev*)arg;
    ev->buffer &#61; (char*)malloc(MEM_SIZE);
    bzero(ev->buffer, MEM_SIZE);
    size &#61; recv(sock, ev->buffer, MEM_SIZE, 0);
    printf("receive data:%s, size:%d\n", ev->buffer, size);
    if (size &#61;&#61; 0) {
        release_sock_event(ev);
        close(sock);
        return;
    }
    event_set(ev->write_ev, sock, EV_WRITE, on_write, ev->buffer);
    event_base_set(base, ev->write_ev);
    event_add(ev->write_ev, NULL);
}

void on_accept(int sock, short event, void* arg)
{
    struct sockaddr_in cli_addr;
    int newfd, sin_size;
    struct sock_ev* ev &#61; (struct sock_ev*)malloc(sizeof(struct sock_ev));
    ev->read_ev &#61; (struct event*)malloc(sizeof(struct event));    ev->write_ev &#61; (struct event*)malloc(sizeof(struct event));    sin_size &#61; sizeof(struct sockaddr_in);    newfd &#61; accept(sock, (struct sockaddr*)&cli_addr, &sin_size);    event_set(ev->read_ev, newfd, EV_READ|EV_PERSIST, on_read, ev);    event_base_set(base, ev->read_ev);    event_add(ev->read_ev, NULL);}int main(int argc, char* argv[]){    struct sockaddr_in my_addr;    int sock;    sock &#61; socket(AF_INET, SOCK_STREAM, 0);    int yes &#61; 1;    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));    memset(&my_addr, 0, sizeof(my_addr));    my_addr.sin_family &#61; AF_INET;    my_addr.sin_port &#61; htons(PORT);    my_addr.sin_addr.s_addr &#61; INADDR_ANY;    bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));    listen(sock, BACKLOG);    struct event listen_ev;    base &#61; event_base_new();    event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);    event_base_set(base, &listen_ev);    event_add(&listen_ev, NULL);    event_base_dispatch(base);    return 0;

}

 

    程序编译的时候要加 -levent 连接选项&#xff0c;以连接libevent的共享库&#xff0c;但是执行的时候依然爆出如下错误&#xff1a;error while loading shared libraries: libevent-1.4.so.2: cannot open shared object file: No such file or directory&#xff0c; 这个是程序找不到共享库的位置&#xff0c;通过执行echo $LD_LIBRARY_PATH可以看到系统库的环境变量里没有我们安装的路径&#xff0c;即由--prefix制定的路径&#xff0c;执行export LD_LIBRARY_PATH&#61;/home/mydir/libevent/lib/:$LD_LIBRARY_PATH将该路径加入系统环境变量里&#xff0c; 再执行程序就可以了。

转:https://www.cnblogs.com/stephen-init/p/3900479.html



推荐阅读
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • Linux重启网络命令实例及关机和重启示例教程
    本文介绍了Linux系统中重启网络命令的实例,以及使用不同方式关机和重启系统的示例教程。包括使用图形界面和控制台访问系统的方法,以及使用shutdown命令进行系统关机和重启的句法和用法。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • CEPH LIO iSCSI Gateway及其使用参考文档
    本文介绍了CEPH LIO iSCSI Gateway以及使用该网关的参考文档,包括Ceph Block Device、CEPH ISCSI GATEWAY、USING AN ISCSI GATEWAY等。同时提供了多个参考链接,详细介绍了CEPH LIO iSCSI Gateway的配置和使用方法。 ... [详细]
  • Vagrant虚拟化工具的安装和使用教程
    本文介绍了Vagrant虚拟化工具的安装和使用教程。首先介绍了安装virtualBox和Vagrant的步骤。然后详细说明了Vagrant的安装和使用方法,包括如何检查安装是否成功。最后介绍了下载虚拟机镜像的步骤,以及Vagrant镜像网站的相关信息。 ... [详细]
author-avatar
欧美盛
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有