Libevent设计的精化之一在于把Timer事件、Signal事件和IO事件统一集成在一个Reactor中,以统一的方式去处理这三种不同的事件,更确切的说是把Timer事件和Signal事件融合到了IO多路复用机制中。

  Timer事件的融合相对清晰简单,其套用了Reactor和Proactor模式(如Windows上的IOCP)中处理Timer事件的经典方法,其实Libevent就是一个Reactor嘛。由于IO复用机制(如Linux下的select、epoll)允许使用一个最大等待时间(即最大超时时间)timeout,当超过了这段最大等待时间,即使没有发生IO事件,也会返回并执行用户设置好的函数。那么在Libevent中将Timer时间融合到正常的IO事件中的方法就是,把系统IO复用的最大超时时间设置为一系列Timer时间中最小的超时时间。这样就能如我们所愿在IO事件能顺利执行的情况下我们去执行IO事件而忽略Timer事件,如果到达timeout时间没有IO事件执行,系统复用也会因为超时而返回,这时候刚好就能执行Timer事件。

  而Signal事件统一到系统的IO复用机制中就没那么自然了,由于Signal事件的出现的随机的,进程不能只是测试一个变量来判别是否发生了一个信号,而是必须告诉内核“在此信号发生时,请执行如下的操作”。这似乎是一件不可以完成的任务,但有时候我们只要换个角度思考,就会发现到达终点的路不只有一条,虽然不是笔直的那条。我们发现,当Signal事件发生时,只要触发系统的IO复用机制,使其返回,再去统一处理所有的待处理事件就可以了。那么如何触发呢?最简单的就是当一个套接字可读时,IO复用就能被触发,而我们的任务就是在Signal事件发生时,向这个套接字(假设为A)的另一端(另一个套接字,我们称之为B)写入数据(不用多,一个字节即可)即可使套接字A有数据读,使得IO复用返回继而处理事件。而用户只需要向Reactor在套接字A处注册一个persist的可读事件,就如同注册其他事件一样,把Signal事件融合到IO复用机制中。

  上述的套接字A和B由于都在本地,目的是为了实现两个进程之间的通信,可以将其看作是一个"数据结构",成为socketpair,在任何一个套接字上写数据都能发送到另一个套接字,是一个全双工的实现。进程间的通信我们最直接的办法就是使用pipe,Linux 提供了 popen 和 pclose 函数,用于创建和关闭管道与另外一个进程进行通信。

FILE *popen(const char *command, const char *mode);
int pclose(FILE *stream);

  遗憾的是,popen 创建的管道只能是单向的 -- mode 只能是 "r" 或 "w" 而不能是某种组合--用户只能选择要么往里写,要么从中读,而不能同时在一个管道中进行读写。如果非得用pipe来实现“全双工”,就要popen两次,打开两个管道。有没有更简单的办法呢?答案就是上述我们所讲到的socketpair,而BSD的内核已经实现了一个socketpair函数,该系统调用能创建一对已连接的UNIX族socket。在Linux中,完全可以把这一对socket当成pipe返回的文件描述符一样使用,唯一的区别就是这一对文件描述符中的任何一个都可读和可写,函数原型如下:

int socketpair(int d, int type, int protocol, int sv[]); 

  socketpair()函数建立一对匿名的已经连接的套接字,其特性由协议族d、类型type、协议protocol决定,建立的两个套接字描述符会放在sv[0]和sv[1]中。
  第1个参数d,表示协议族,只能为AF_LOCAL或者AF_UNIX;
  第2个参数type,表示类型,只能为0。
  第3个参数protocol,表示协议,可以是SOCK_STREAM或者SOCK_DGRAM。用SOCK_STREAM建立的套接字对是管道流,与一般的管道相区别的是,套接字对建立的通道是双向的,即每一端都可以进行读写。参数sv,用于保存建立的套接字对。

  关于Unix域协议和源自BSD的socketpair函数在《Unix网络编程 卷1 <第三版>》中第15章Stevens先生已经给我们详细的讲解了,在中文版第330页也有使用socketpair来实现描述符传递的例子,可仔细研读。

  Libevent中也有对socketpair的实现,由于在原来的函数中有一些特定的宏和变量名,直接阅读和使用会不方便,所以我将他抽出来进行通用化(^_^),作为一个可复用的函数,供学习和使用。下面是源码:

#include <stdio.h>
#include <stdlib.h> #include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h> /**
* 创建一个SocketPair,通过返回的两个fd可以进行进程间通信
* @param family : 套接字对间使用的协议族,可以是AF_INET或AF_LOCAL
* @param type : 套接字类型
* @param protocol : 协议类型
* @param fd[2] : 将要创建的Socketpair两端的文件描述符
*/
int
Socketpair(int family, int type, int protocol, int fd[])
{
int32_t listener = -;
int32_t connector = -;
int32_t acceptor = -;
struct sockaddr_in listen_addr;
struct sockaddr_in connect_addr;
unsigned int size; if (protocol ||
(family != AF_INET && family != AF_LOCAL)) {
fprintf(stderr, "EAFNOSUPPORT\n");
return -;
}
if (!fd) {
fprintf(stderr, "EINVAL\n");
return -;
} /*创建listener,监听本地的换回地址,端口由内核分配*/
listener = socket(AF_INET, type, );
if (listener < )
return -;
memset(&listen_addr, , sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
listen_addr.sin_port = ; /* kernel chooses port. */
if (bind(listener, (struct sockaddr *) &listen_addr, sizeof(listen_addr))
== -)
goto fail;
if (listen(listener, ) == -)
goto fail; /*创建connector, 连接到listener, 作为Socketpair的一端*/
connector = socket(AF_INET, type, );
if (connector < )
goto fail;
/* We want to find out the port number to connect to. */
size = sizeof(connect_addr);
if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -)
goto fail;
if (size != sizeof(connect_addr))
goto fail;
if (connect(connector, (struct sockaddr *) &connect_addr,
sizeof(connect_addr)) == -)
goto fail; size = sizeof(listen_addr);
/*调用accept函数接受connector的连接,将返回的文件描述符作为Socketpair的另一端*/
acceptor = accept(listener, (struct sockaddr *) &listen_addr, &size);
if (acceptor < )
goto fail;
if (size != sizeof(listen_addr))
goto fail;
close(listener); /**
* 至此,我们已经创建了两个连接在一起的文件描述符,
* 通过向其中任意一个发送数据,都会“转发”到另一个,即可以实现进程间的通信
*/ /* Now check we are talking to ourself by matching port and host on the
two sockets. */
if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -)
goto fail;
if (size != sizeof(connect_addr)
|| listen_addr.sin_family != connect_addr.sin_family
|| listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr
|| listen_addr.sin_port != connect_addr.sin_port)
goto fail;
fd[] = connector;
fd[] = acceptor; return ; fail:
if (listener != -)
close(listener);
if (connector != -)
close(connector);
if (acceptor != -)
close(acceptor); return -;
}

简单测试一下:

#include <sys/types.h>
#include <sys/socket.h> #include <stdlib.h>
#include <stdio.h> int Socketpair(int, int, int, int[]); int main ()
{
int fds[]; int r = Socketpair(AF_INET, SOCK_STREAM, , fds);
if (r < ) {
perror( "socketpair()" );
exit( );
} if(fork()) {
/* Parent process: echo client */
int val = ;
close( fds[] );
while ( ) {
sleep();
++val;
printf( "Sending data: %d\n", val );
write( fds[], &val, sizeof(val) );
read( fds[], &val, sizeof(val) );
printf( "Data received: %d\n", val );
}
}
else {
/* Child process: echo server */
int val;
close( fds[] );
while ( ) {
read( fds[], &val, sizeof(val) );
++val;
write( fds[], &val, sizeof(val) );
}
}
}

测试结果:

=============================神奇的分割线============================

源码请猛戳{ 这里

================================================================

参考资料:

Linux上实现双向进程间通信管道(socketpair)

libevent源码深度剖析

Libevent源码

《Unix网络编程 卷一 <第三版>》

Libevent学习之SocketPair实现的更多相关文章

  1. libevent学习之二:Windows7(Win7)下编译libevent

    Linux下编译参考源码中的README文件即可,这里主要记录Windows下的编译. 一.准备工作 去官网下载最新的稳定发布版本libevent-2.0.22-stable 官网地址:http:// ...

  2. PHP中的Libevent学习

    wangbin@2012,1,3 目录 Libevent在php中的应用学习 1.      Libevent介绍 2.      为什么要学习libevent 3.      Php libeven ...

  3. libevent学习笔记 一、基础知识【转】

    转自:https://blog.csdn.net/majianfei1023/article/details/46485705 欢迎转载,转载请注明原文地址:http://blog.csdn.net/ ...

  4. libevent学习笔记 —— 牛刀小试:简易的服务器

    回想起之前自己用纯c手动写epoll循环,libevent用起来还真是很快捷啊!重写了之前学习的时候的一个例子,分别用纯c与libevent来实现.嗯,为了方便对比一下,就一个文件写到黑了. 纯c版: ...

  5. Libevent学习笔记(五) 根据例子学习bufferevent

    libevent中提供了一个Hello-world.c 的例子,从这个例子可以学习libevent是如何使用bufferevent的. 这个例子在Sample中 这个例子之前讲解过,这次主要看下buf ...

  6. libevent学习文档(三)working with event

    Events have similar lifecycles. Once you call a Libevent function to set up an event and associate i ...

  7. libevent学习笔记(参考libevent深度剖析)

    最近自学libevent事件驱动库,参考的资料为libevent2.2版本以及张亮提供的<Libevent源码深度剖析>, 参考资料: http://blog.csdn.net/spark ...

  8. 【传智播客】Libevent学习笔记(五):基本类型和函数

    目录 00. 目录 01. 基本类型 1.1 evutil_socket_t类型 1.2 标准类型 1.3 各种兼容性类型 02. 可移植的定时器函数 03. 套接字API兼容性 04. 可移植的字符 ...

  9. 【传智播客】Libevent学习笔记(一):简介和安装

    目录 00. 目录 01. libevent简介 02. Libevent的好处 03. Libevent的安装和测试 04. Libevent成功案例 00. 目录 @ 01. libevent简介 ...

随机推荐

  1. mysql闯关练习

    1.表关系                 班级表:class       学生表:student       cid caption grade_id   sid sname gender clas ...

  2. UGUI Canvas

    Render Mode Screen Space - Overlay 在此模式下不会参照到Camera,UI直接显示在任何对象之上 1.Pixel Perfect:可以使图像更清晰,但是有额外的性能开 ...

  3. java内存溢出的原因

    前几天 ,面试被问到这个, 我只说了个死循环,所以上网查了下 ,下面给个总结: 内存溢出就是系统可以提供给Java虚拟机的内存不足导致的,主要分为以下几种情况: 1.要加载的数据量过大,比如加载一个很 ...

  4. pre换行段落间距

    <!DOCTYPE html><html><head lang="en"><meta charset="UTF-8"& ...

  5. vprintf 和 vsnpintf 的用法

    函数定义: int vprintf ( const char * format, va_list arg ); printf() and friends are for normal use. vpr ...

  6. 可重入函数reentrant function

    可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数:而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能 ...

  7. Plants vs. Zombies(二分好题+思维)

    Plants vs. Zombies http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5819 BaoBao and DreamG ...

  8. ArrayList LinkList比较

    1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构.      2.对于随机访问get和set,ArrayList优于LinkedList,因为ArrayLi ...

  9. UVa 1592 Database(巧用map)

    Peter studies the theory of relational databases. Table in the relational database consists of value ...

  10. C#通过反射获得对象所有属性和值

    C#获得对象的所有属性和值 public void GetPros() { UserInfo userInfo = new UserInfo(); userInfo.ID = ; userInfo.N ...