Libevent学习之SocketPair实现
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) );
}
}
}
测试结果:
=============================神奇的分割线============================
源码请猛戳{ 这里 }
================================================================
参考资料:
Libevent源码
Libevent学习之SocketPair实现的更多相关文章
- libevent学习之二:Windows7(Win7)下编译libevent
Linux下编译参考源码中的README文件即可,这里主要记录Windows下的编译. 一.准备工作 去官网下载最新的稳定发布版本libevent-2.0.22-stable 官网地址:http:// ...
- PHP中的Libevent学习
wangbin@2012,1,3 目录 Libevent在php中的应用学习 1. Libevent介绍 2. 为什么要学习libevent 3. Php libeven ...
- libevent学习笔记 一、基础知识【转】
转自:https://blog.csdn.net/majianfei1023/article/details/46485705 欢迎转载,转载请注明原文地址:http://blog.csdn.net/ ...
- libevent学习笔记 —— 牛刀小试:简易的服务器
回想起之前自己用纯c手动写epoll循环,libevent用起来还真是很快捷啊!重写了之前学习的时候的一个例子,分别用纯c与libevent来实现.嗯,为了方便对比一下,就一个文件写到黑了. 纯c版: ...
- Libevent学习笔记(五) 根据例子学习bufferevent
libevent中提供了一个Hello-world.c 的例子,从这个例子可以学习libevent是如何使用bufferevent的. 这个例子在Sample中 这个例子之前讲解过,这次主要看下buf ...
- libevent学习文档(三)working with event
Events have similar lifecycles. Once you call a Libevent function to set up an event and associate i ...
- libevent学习笔记(参考libevent深度剖析)
最近自学libevent事件驱动库,参考的资料为libevent2.2版本以及张亮提供的<Libevent源码深度剖析>, 参考资料: http://blog.csdn.net/spark ...
- 【传智播客】Libevent学习笔记(五):基本类型和函数
目录 00. 目录 01. 基本类型 1.1 evutil_socket_t类型 1.2 标准类型 1.3 各种兼容性类型 02. 可移植的定时器函数 03. 套接字API兼容性 04. 可移植的字符 ...
- 【传智播客】Libevent学习笔记(一):简介和安装
目录 00. 目录 01. libevent简介 02. Libevent的好处 03. Libevent的安装和测试 04. Libevent成功案例 00. 目录 @ 01. libevent简介 ...
随机推荐
- DrawDib 使用例子<转>
#include<vfw.h>#pragma comment(lib,"Vfw32.lib") BITMAPINFOHEADER biHeader; memset(&a ...
- playbook相关
ansible-playbook site.yml -f 10 ansible-playbook常用参数说明: -f 10 启用10个并发进程数执行playbook -u RM ...
- one by one 项目 part 1
今天安装MySQL,我的系统是win8.1,安装包是mysql-5.7.17-winx64.zip,遇到了不少问题,特在此总结,希望能帮到遇到同样情况的人. 1.前面按照网上教程,先解压,然后在cmd ...
- redmine邮件配置
网上找了半天,有很多答案,最后自己测试找出一个解决办法. 1.找到安装位置 D:\Bitnami\redmine-2.5.2-2\apps\redmine\htdocs\config下的文件confi ...
- Nexus 按项目类型分配不同的工厂来发布不同 项目
但是有时候,一个公司会有很多项目[crm,oa,erp]等等的项目.如果把这些项目全部都放到releases或者snapshots中的话会有点混乱.比较好的办法是,按项目来分.每个项目一个工厂:cms ...
- 安装RabbitMq-----windows
在官网download我们所需要的版本,安装rabbitMq需要erlang支持 rabbitMq :http://www.rabbitmq.com/download.html erlang :ht ...
- ASP.NET中UrlEncode应该用Uri.EscapeDataString()(转)
今天,茄子_2008反馈他博客中的“C++”标签失效.检查了一下代码,生成链接时用的是HttpUtility.UrlEncode(url),从链接地址获取标签时用的是HttpUtility.UrlDe ...
- Largest Rectangle in a Histogram(附上几组测试数据)
Largest Rectangle in a Histogram http://acm.hdu.edu.cn/showproblem.php?pid=1506 Time Limit: 2000/100 ...
- [leetcode]403. Frog Jump青蛙过河
A frog is crossing a river. The river is divided into x units and at each unit there may or may not ...
- C#中int? 转换为 int 型
用 “ var a= zongfen.Score;”