这篇文章介绍下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,因此程序中将不对错误进行处理,假设所有的调用都成功


2 #define PORT 253413 #define BACKLOG 54 #define MEM_SIZE 10245 6 struct event_base* base;7 8 int main(int argc, char* argv[])9 {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行是服务器程序的通常做法,设置了该选项后,在父子进程模型中,当子进程为客户服务的时候如果父进程退出,可以重新启动程序完成服务
的无缝升级,否则在所有父子进程完全退出前再启动程序会在该端口上绑定失败,也即不能完成无缝升级的操作(更多信息可以参考该函数说明或Steven先生
的<网络编程>)。第24行用于创建一个事件处理的全局变量,可以理解为这是一个负责集中处理各种出入IO事件的总管家,它负责接收和派发所
有输入输出IO事件的信息,这里调用的是函数event_base_new(),
很多程序里这里用的是event_init(),区别就是前者是线程安全的、而后者是非线程安全的,后者在其官方说明中已经被标志为过时的函数、且建议用
前者代替,libevent中还有很多类似的函数,比如建议用event_base_dispatch代替event_dispatch,用
event_assign代替event_set和event_base_set等,关于libevent接口的详细说明见其官方说明libevent_doc.

第25行说明在listen_en这个事件监听sock这个描述字的读操作,当读消息到达是调用on_accept函数,EV_PERSIST参数告诉系
统持续的监听sock上的读事件,如果不加该参数,每次要监听该事件时就要重复的调用26行的event_add函数,从前面的代码可知,sock这个描
述字是bind到本地的socket端口上,因此其对应的可读事件自然就是来自客户端的连接到达,我们就可以调用accept无阻塞的返回客户的连接了。
第26行将listen_ev注册到base这个事件中,相当于告诉处理IO的管家请留意我的listen_ev上的事件。第27行相当于告诉处理IO的
管家,当有我的事件到达时你发给我(调用on_accept函数),至此对listen_ev的初始化完毕。第28行正式启动libevent的事件处理
机制,使系统运行起来,运行程序的话会发现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 = (struct event*)malloc(sizeof(struct event));;
   7:     sin_size = sizeof(struct sockaddr_in);
   8:     newfd = 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相同,即在代表客户的描述字newfd上监听可读事件,当有数据到达是调用on_read函数。这里有亮点需要 注意,一是read_ev需要从堆里malloc出来,如果是在栈上分配,那么当函数返回时变量占用的内存会被释放,因此事件主循环 event_base_dispatch会访问无效的内存而导致进程崩溃(即crash);第二个要注意的是第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 = (char*)malloc(MEM_SIZE);
   6:     bzero(buffer, MEM_SIZE);
   7:     size = recv(sock, buffer, MEM_SIZE, 0);
   8:     printf("receive data:%s, size:%d\n", buffer, size);
   9:     if (size == 0) {
  10:         event_del((struct event*)arg);
  11:         free((struct event*)arg);
  12:         close(sock);
  13:         return;
  14:     }
  15:     write_ev = (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行,当从socket读返回0标志对方已经关闭了连接,因此这个时候就没必要继续监听该套接口上的事件,由于EV_READ在on_accept函数 里是用EV_PERSIST参数注册的,因此要显示的调用event_del函数取消对该事件的监听。第18-21行与on_accept函数的6-11 行类似,当可写时调用on_write函数,注意第19行将buffer作为参数传递给了on_write。这段程序还有比较严重的问题,后面进行说明。

on_write函数的实现


1 void on_write(int sock, short event, void* arg)2 {3     char* buffer = (char*)arg;4     send(sock, buffer, strlen(buffer), 0); 5 6     free(buffer); 

7 }

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

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

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


#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    1024struct 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 = (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 = (struct sock_ev*)arg;    ev->buffer = (char*)malloc(MEM_SIZE);    bzero(ev->buffer, MEM_SIZE);    size = recv(sock, ev->buffer, MEM_SIZE, 0);    printf("receive data:%s, size:%d\n", ev->buffer, size);    if (size == 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 = (struct sock_ev*)malloc(sizeof(struct sock_ev));    ev->read_ev = (struct event*)malloc(sizeof(struct event));    ev->write_ev = (struct event*)malloc(sizeof(struct event));    sin_size = sizeof(struct sockaddr_in);    newfd = 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 = socket(AF_INET, SOCK_STREAM, 0);    int yes = 1;    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));    memset(&my_addr, 0, sizeof(my_addr));    my_addr.sin_family = AF_INET;    my_addr.sin_port = htons(PORT);    my_addr.sin_addr.s_addr = INADDR_ANY;    bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));    listen(sock, BACKLOG);    struct event listen_ev;    base = 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 连接选项,以连接libevent的共享库,但是执行的时候依然爆出如下错误:error while loading shared libraries: libevent-1.4.so.2: cannot open shared object file: No such file or directory,
这个是程序找不到共享库的位置,通过执行echo
$LD_LIBRARY_PATH可以看到系统库的环境变量里没有我们安装的路径,即由--prefix制定的路径,执行export
LD_LIBRARY_PATH=/home/mydir/libevent/lib/:$LD_LIBRARY_PATH将该路径加入系统环境变量里,
再执行程序就可以了。

<转>libevent基本使用demo的更多相关文章

  1. windows环境libevent搭建和demo分析

    libevent框架之前有做过分析,这次是谈谈如何将libevent搭建在vs工作环境下, 并且编写一个demo进行测试.测试过程中会再一次带大家分析消息是怎么传递 的. 我的libevent版本li ...

  2. libevent(一)定时器Demo

    开始研究libevent,使用的版本是2.0.22. 实现一个定时器:每2秒执行一次printf. #include <stdio.h> #include <stdlib.h> ...

  3. libevent笔记1:安装及DEMO

    本篇简单记录了libevent的安装过程及基础的先进先出管道Demo,其中demo来自这篇博客,安装过程在这篇博客 实验环境 系统:Ubuntu 18.04.3 libevent版本:libevent ...

  4. <转>libevent使用demo

    这篇文章介绍下libevent在socket异步编程中的应用.在一些对性能要求较高的网络应用程序中,为了防止程序阻塞在socket I/O操作上造成程序性能的下降,需要使用异步编程,即程序准备好读写的 ...

  5. 使用libevent进行多线程socket编程demo

    最近要对一个用libevent写的C/C++项目进行修改,要改成多线程的,故做了一些学习和研究. libevent是一个用C语言写的开源的一个库.它对socket编程里的epoll/select等功能 ...

  6. 服务器 libevent中epoll使用实例demo

    名词解释:man epoll之后,得到如下结果: NAME       epoll - I/O event notification facility SYNOPSIS       #include ...

  7. socket异步编程--libevent的使用

    使用 libevent 和 libev 提高网络应用性能 http://www.ibm.com/developerworks/cn/aix/library/au-libev/ libevent实现ht ...

  8. [转] socket异步编程--libevent的使用

    这篇文章介绍下libevent在socket异步编程中的应用.在一些对性能要求较高的网络应用程序中,为了防止程序阻塞在socket I/O操作上造成程序性能的下降,需要使用异步编程,即程序准备好读写的 ...

  9. PHP7 学习笔记(五)安装event扩展(libevent)

    一.描述:有效安排I/O,时间和信号的扩展 使用可用于特定平台的最佳I/O通知机制的事件,是PHP基础设施的libevent端口. 二.下载地址:http://pecl.php.net/package ...

随机推荐

  1. Hadoop集群 增加节点/增加磁盘

    在虚拟机中新建一个机器. 设置静态IP 将修改/etc/hosts 192.168.102.10 master 192.168.102.11 slave-1 192.168.102.12 slave- ...

  2. 安装mysql会出现start service错误

    安装MySQL时无法启动服务(could not start the service MYSQL .Error:0)安装mysql会出现start service错误安装mysql时 配置到start ...

  3. liunx下安装mysql(8.0.27)

    一.软件下载: 1.通过官网下载: https://dev.mysql.com/downloads/repo/yum/ 本文使用的系统为centos7,基于RedHat7的版本 2.下载完成后文件 m ...

  4. 关于JAVA中顺序IO的基本操作

    关于JAVA中顺序IO的基本操作 写在前面 最近研究一下JAVA中的顺序IO,在网络上找了一会儿,发现少有详细的介绍,顾此在此处说说顺序IO,才学疏浅,如有不对,望赐教. 什么是顺序IO 事实上JAV ...

  5. [luogu5387]人形演舞

    先对每一个求sg函数,暴力复杂度为$o(m^{2})$ 取$k$满足$2^{k}\le x<2^{k+1}$(即$x$二进制下的最高位),考虑$y$与$2^{k}$的关系 1.若$1\le y& ...

  6. 下一代的 3D Tiles 前瞻

    下一代的 3D Tiles 前瞻 原文:Introducing 3D Tiles Next, Streaming Geospatial to the Metaverse 原文发布时间:2021年11月 ...

  7. 网络协议之:一定要大写的SOCKS

    目录 简介 SOCKS的故事 SOCKS的历史 SOCKS协议的具体内容 SOCKS4 SOCKS4a SOCKS5 总结 简介 很久很久以前,人们还穿的是草鞋,草鞋虽然穿着舒服,但是不够美观.然后人 ...

  8. 洛谷 P5902 [IOI2009]salesman(dp)

    题面传送门 题意: 有 \(n\) 个展销会,每个展销会给出它的时间 \(t_i\),举办展销会的位置 \(l_i\),和参加这个展销会你能得到的收益 \(m_i\). 你现在在位置 \(s\),你可 ...

  9. Codeforces 1332G - No Monotone Triples(数据结构综合)

    Codeforces 题目传送门 & 洛谷题目传送门 首先打表即可发现对于任意长度 \(\ge 5\) 的序列总存在一个 Monotone triple,证明不会实在不行直接 \(5^5\) ...

  10. Python 包管理工具 pip 与 conda

    简介 pip是接触 python 后最早认识的包管理工具.通过使用 pip 能够自动下载和解决不同 python 模块的依赖问题,使 python 的配置过程变得简单. 与 pip 类似,conda ...