libevent的使用(socket)
这篇文章介绍下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
如今假定我们要设计一个server程序,用于接收client的数据,并将接收的数据回写给client。以下来构造该程序。因为本不过展示一个Demo。因此程序中将不正确错误进行处理,如果全部的调用都成功
3 #define BACKLOG 5
4 #define MEM_SIZE 1024
5
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行是server程序的通常做法,设置了该选项后,在父子进程模型中,当子进程为客户服务的时候假设父进程退出。能够又一次启动程序完毕服务的无缝升级,否则在全部父子进程全然退出前再启动程序会在该port上绑定失败,也即不能完毕无缝升级的操作(很多其它信息能够參考该函数说明或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到本地的socketport上,因此其相应的可读事件自然就是来自client的连接到达,我们就能够调用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函数的实现
2 {
3 char* buffer = (char*)arg;
4 send(sock, buffer, strlen(buffer), 0);
5
6 free(buffer);
7 }
on_write函数中向client回写数据。然后释放on_read函数中malloc出来的buffer。在非常多书合编程指导中都非常强调资源的全部权,常常要求谁分配资源、就由谁释放资源,这样对资源的管理指责就更明白,不easy出问题,可是通过该样例我们发如今异步编程中资源的分配与释放往往是由不同的全部者操作的,因此也是比較easy出问题的地方。
事实上在on_read函数中从socket读取数据后程序就能够直接调用write/send接口向客户回写数据了。由于写事件已经满足。不存在异步不异步的问题。这里进行on_write的异步操作不过为了说明异步编程中资源的管理与释放的问题。另外一方面,直接调用write/send函数向client写数据可能导致程序较长时间堵塞在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/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 = (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将该路径增加系统环境变量里。再运行程序就能够了。
文档查阅:http://libevent.org/
libevent的使用(socket)的更多相关文章
- MFC 配合 protobuff libevent 实现的Socket 的GM工具 框架
MFC 配合 protobuff libevent 实现的Socket 的GM工具 框架
- 使用libevent进行多线程socket编程demo
最近要对一个用libevent写的C/C++项目进行修改,要改成多线程的,故做了一些学习和研究. libevent是一个用C语言写的开源的一个库.它对socket编程里的epoll/select等功能 ...
- libevent 实现的socket 通信 server以及解决找不到动态库的方法
注: client 个人觉得没必要用 libvent来实现就没写 注:由于 涉及到回调函数的使用 如有疑惑的可以先去了解回调函数的实现机理 先来说一下 libevent主要是干啥的 : 内部封装了 s ...
- libevent之基于socket的bufferevent
基于socket的bufferevent由一个socket的传输层和read/write buffer组成.区别于常规的event,当socket可读或者可写时会回调用户的callback,buffe ...
- 【转】libevent源码分析
libevent源码分析 转自:http://www.cnblogs.com/hustcat/archive/2010/08/31/1814022.html 这两天没事,看了一下Memcached和l ...
- PHP中的Libevent学习
wangbin@2012,1,3 目录 Libevent在php中的应用学习 1. Libevent介绍 2. 为什么要学习libevent 3. Php libeven ...
- libevent系列文章
Libevent 2 提供了 bufferevent 接口,简化了编程的难度,bufferevent 实际上是对底层事件核心的封装,因此学习 bufferevent 的实现是研究 Libevent 底 ...
- C10K问题和Libevent库介绍
http://blog.chinaunix.net/uid-20761674-id-75056.html 一.C10K的问题 C10K的问题在上个世纪90年代就被提出来了.大概的意思是当用户数超过1万 ...
- (转)Libevent(5)— 连接监听器
转自:http://name5566.com/4220.html 参考文献列表:http://www.wangafu.net/~nickm/libevent-book/ 此文编写的时候,使用到的 Li ...
随机推荐
- 多个 WindowsFormsHost 叠加顺序调整
原文:多个 WindowsFormsHost 叠加顺序调整 工作中遇到多个 WindowsFormsHost 包装的控件叠加顺序的调整问题,用了 BingToFront 和 BringToBack,不 ...
- jsoup抓取网页+具体解说
jsoup抓取网页+具体解说 Java 程序在解析 HTML 文档时,相信大家都接触过 htmlparser 这个开源项目.我以前在 IBM DW 上发表过两篇关于 htmlparser 的文章.各自 ...
- POJ 3664 Election Time 题解
这道题网上非常多人都会说easy,水题之类的话,只是我看了下说这种话的人的程序,能够说他们的程序都不及格! 为什么呢?由于他们的程序都是使用简单的二次排序水过(大概你能搜索到的多是这种程序).那样自然 ...
- python实战之编码问题:中文!永远的痛
编码的思维图谱: 也就是说文件没有编码之说,事实上都是按二进制格式保存在硬盘中的.不过在写入读取时须使用相应的编码进行处理,以便操作系统配合相关软件/字体,绘制到屏幕中给人看.所以关键问题是得知道原先 ...
- SpringMVC+Spring+Hibernate框架整合原理,作用及使用方法
转自:https://blog.csdn.net/bieleyang/article/details/77862042 SSM框架是spring MVC ,spring和mybatis框架的整合,是标 ...
- 17.广度优先遍历bfs
#include <iostream> #include <boost/config.hpp> //图(矩阵实现) #include <boost/graph/adjac ...
- ORACLE 11g RAC-RAC DG Duplicate 搭建(生产操作文档)
环境:rhel 6.7 64位源库:ORACLE 11204 RAC 未打PSU备库:ORACLE 11204 RAC PSU 20170718 一.停止中间件并做全库备份 1.在节点2做全备 2.首 ...
- android全屏去掉title栏的多种实现方法
android全屏去掉title栏的多种实现方法 作者: 字体:[增加 减小] 类型:转载 时间:2013-02-18我要评论 android全屏去掉title栏包括以下几个部分:实现应用中的所有ac ...
- 51nod-1134 最长递增子序列,用线段树将N^2的dp降到NlogN
题目链接 给出长度为N的数组,找出这个数组的最长递增子序列.(递增子序列是指,子序列的元素是递增的) 例如:5 1 6 8 2 4 5 10,最长递增子序列是1 2 4 5 10. Input 第1行 ...
- hiho 1590 - 紧张的会议室。区间问题
题目链接 小Hi的公司最近员工增长迅速,同时大大小小的会议也越来越多:导致公司内的M间会议室非常紧张. 现在小Hi知道公司目前有N个会议,其中第i个会议的时间区间是(Si, Ei). 注意这里时间区间 ...