Erlang下与其他程序和语言的通信机制(1)
在Erlang运行时中,提供了几种机制来实现与其它程序或者语言的通信。一种为分布式Erlang,一种为端口,其中端口分为普通端口和链入式驱动端口,还有后面引入的NIFs。
分布式Erlang:一个Erlang运行时可以看作一个分布式Erlang Node,并通过名字访问它。一个Erlang Node可以连接和监视其他的Node,甚至在其他Node上面创建Process。消息传递和异常捕获在不同的Node之间是透明的,其分布式通信在底层上是使用的Tcp/Ip,并且现有库中有大量模块可以用于操作Node,比如global就提供了全局Node名字注册的机制。分布式Erlang主要用于Erlang与Erlang之间的通信,当然也可以用于Erlang与C之间通信,当与C之间通信时,C将被视为一个C Node。C Node指的是在C中使用Erl_interface函数库来设置连接,并与Erlang Node通信,也称为hidden node。当使用C Node与Erlang通信时,对Erlang程序员来说是完全透明的,他并不知道,也不需要关心这个Node是C还是Erlang。
端口:从Erlang的角度来看,端口提供了一种方式来与外部程序进行通信,主要用于本机上与外部程序通信。对于普通端口来说,它通过一种面向字节流的通信方式来与Erlang通信,而链入式驱动端口则是通过回调。普通端口通信的实现方式依赖于具体平台,在Unix上,通信通过Pipe实现,外部程序通过stdin读入,通过stdout输出。理论上外部程序可以使用任何语言实现,只要它支持这种通信方式。至于链入式驱动端口出现的原因,主要还是效率问题。因为普通端口通信的外部程序是另外一个OS进程,因此在效率要求较高的场景下,这种方式很难胜任。所谓有得必有失,链入式驱动端口由于是直接载入动态库并嵌入虚拟机内部,而动态库由C语言按回调约定编写。所以在C语言编写上要注意很多问题,比如并发,内存分配,函数可重入等等。如果C代码崩溃,也会造成整个虚拟机的崩溃。
NIF(Native Implemented Function):一种类似于erlang中的BIF,给我的第一感觉就想到了luajit的FFI,在R13B03版本中被引入。它是由C直接实现的函数,并且由Erlang一个模块引用,其它模块通过这个引用模块对函数进行调用。和链入式驱动端口一样,NIF也是先将C编译成动态库(so in Unix,dll in windows),然后由Erlang模块动态加载进入虚拟机,所以它也会存在链入式驱动端口的问题。当然它也是与C通信方式中效率最高的一种,调用NIFs不需要上下文切换。
端口通信的一些接口:在C语言这边提供了Erl_interface接口,包含erl_marshal,erl_eterm,erl_format,erl_malloc来处理Erlang项式结构,erl_connect与远端Node通信等等,并且在Erlang端有term_to_binary/1,binary_to_term/1来对通信的数据进行编码与解码。
接下来我们详细聊下普通端口,其它方式在后面几节介绍:
- 普通端口
我们在Erlang中通过打开一个端口与C通信,而打开这个端口的Erlang进程被称为连接进程(Connected process)。所有与端口的通信都需要通过这个连接进程,如果这个进程终止了,那么这个端口与外部程序都会被关闭。(外部程序被关闭依赖编码)。我们可以通过BIF open_port/2函数来打开一个端口。第一个参数使用{spwan,ExtPrg}。其中ExtPrg为外部程序名称,包含该程序的启动cmd line。第二个参数是一个选项列表,比如说{packet,2}。该选项表示使用2个字节的消息头来保存长度,这个消息头在Erlang端时由Erlang端口自动帮你填充,在C端时就需要你自己填充了。除了这个外,还有许多其它选项,可以查看open_port/2文档。
我们先看一个简单普通端口实现,然后在这个实现的基础上使用Erl_interface实现消息体的编码与解码
/* complex.c */ int foo(int x) {
return x+;
} int bar(int y) {
return y*;
} 我们通过普通端口,实现对C语言foo,bar函数的访问。最后的效果就如同调用complex模块的函数一样,与C函数的通信被隐藏在complex.erl内部。 % Erlang code
...
Res = complex:foo(X),
...
下面是erlang部分:complex模块的实现
-module(complex1).
-export([start/1, init/1]). start(ExtPrg) -> %%模块入口,spawn连接进程
spawn(?MODULE, init, [ExtPrg]).
stop() -> %%发送关闭消息
complex ! stop.
init(ExtPrg) -> %%连接进程初始化函数
register(complex, self()), %%注册complex为连接进程的名字
process_flag(trap_exit, true), %%接收外部程序退出的信号
Port = open_port({spawn, ExtPrg}, [{packet, 2}]), %%打开端口
loop(Port). foo(X) ->
call_port({foo, X}).
bar(Y) ->
call_port({bar, Y}). %%complex/foo,complex/bar 接口向complex连接进程发送消息,连接进程再将消息发送给端口 call_port(Msg) ->
complex ! {call, self(), Msg},
receive
{complex, Result} ->
Result
end. %%连接进程消息接收,转发给端口
loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, encode(Msg)}}, %%接收call_port发来的消息,并将消息转换成字节流转发给端口
receive
{Port, {data, Data}} -> %%接收端口回来的消息,转发给调用者
Caller ! {complex, decode(Data)}
end,
loop(Port); stop ->
Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;
{'EXIT', Port, Reason} ->
exit(port_terminated)
end.
encode({foo, X}) -> [1, X]; %%这里简化了一下,约定参数和结果都小于256,实际运用中一般可以使用term_to_binary/1,binary_to_term/1接口来做erlang项式到二进制的转换
encode({bar, Y}) -> [2, Y]. decode([Int]) -> Int.
/************************************************************************************************************/
/************************************************************************************************************/ 下面是C代码实现部分,在C这边,首先在读时要对消息头的长度进行解析,在写时需要填充消息头。从fd0读,往fd1写 /* erl_comm.c */ typedef unsigned char byte; read_cmd(byte *buf)
{
int len; if (read_exact(buf, 2) != 2) %%读取两个字节的消息头
return(-1);
len = (buf[0] << 8) | buf[1]; %%进行大小编转换,因为erlang的端口消息在发送后会转换到网络字节序,而我们测试的环境是小编平台的话,就需要转换
return read_exact(buf, len);
} write_cmd(byte *buf, int len)
{
byte li; li = (len >> 8) & 0xff; %%填充消息头,先填充高位,网络字节序
write_exact(&li, 1); li = len & 0xff;
write_exact(&li, 1); return write_exact(buf, len);
} read_exact(byte *buf, int len)
{
int i, got=0; do {
if ((i = read(0, buf+got, len-got)) <= 0)
return(i);
got += i;
} while (got<len); return(len);
} write_exact(byte *buf, int len)
{
int i, wrote = 0; do {
if ((i = write(1, buf+wrote, len-wrote)) <= 0)
return (i);
wrote += i;
} while (wrote<len); return (len);
} /* port.c */ main函数比较简单,就是一个循环读取端口发来的数据,将数据解析后,调用消息对应的函数,返回结果。其实类似c/s结构。
typedef unsigned char byte; int main() {
int fn, arg, res;
byte buf[100]; while (read_cmd(buf) > 0) {
fn = buf[0];
arg = buf[1]; if (fn == 1) {
res = foo(arg);
} else if (fn == 2) {
res = bar(arg);
} buf[0] = res;
write_cmd(buf, 1);
}
}
上面的代码在消息体上有一些限制,比如fn,arg,res这些变量限制在255大小以内。所以现实中,我们一般使用Erl_interface来对消息进行封装。使用Erl_interface封装消息,我们需要改变两处代码,第一Erl_interface处理外部Erlang项式,需要端口输出二进制流,因此在打开端口时,需要添加binary选项。
open_port({spawn, ExtPrg}, [{packet, 2}]) 改变为 open_port({spawn, ExtPrg}, [{packet, 2}, binary])
第二我们不需要自己发明消息体的编码解码约定,直接使用term_to_binary/1,binary_to_term/1来进行Erlang任何项式到二进制流的转换与逆转换
Port ! {self(), {command, encode(Msg)}},
receive
{Port, {data, Data}} ->
Caller ! {complex, decode(Data)}
end 改变为 Port ! {self(), {command, term_to_binary(Msg)}},
receive
{Port, {data, Data}} ->
Caller ! {complex, binary_to_term(Data)}
end
在C端,我们需要使用Erl_interface来编解码。首先,从端口传入的Erlang项式结构流需要转换成ETERM struct,它在C端用来表示Erlang项式,最后由C函数计算的结果也必须首先转成ETERM,再传回端口。
/* ei.c */ #include "erl_interface.h"
#include "ei.h" typedef unsigned char byte; int main() {
ETERM *tuplep, *intp;
ETERM *fnp, *argp;
int res;
byte buf[];
long allocated, freed; erl_init(NULL, ); //初始化函数 while (read_cmd(buf) > ) { //得到消息体的字节流
tuplep = erl_decode(buf); //erl_decode 解码得到ETERM
fnp = erl_element(, tuplep);
argp = erl_element(, tuplep); if (strncmp(ERL_ATOM_PTR(fnp), "foo", ) == ) {
res = foo(ERL_INT_VALUE(argp));
} else if (strncmp(ERL_ATOM_PTR(fnp), "bar", ) == ) {
res = bar(ERL_INT_VALUE(argp));
} intp = erl_mk_int(res); //将结果保存入项式intp
erl_encode(intp, buf);
write_cmd(buf, erl_term_len(intp)); erl_free_compound(tuplep);
erl_free_term(fnp);
erl_free_term(argp);
erl_free_term(intp);
}
}
普通端口就聊到这里,后面几篇再聊下链入式驱动端口,NIFs,以及C Node。
Erlang下与其他程序和语言的通信机制(1)的更多相关文章
- Erlang下与其他程序和语言的通信机制(3)
这部分主要聊C Nodes.首先我们需要了解下Erlang的分布式系统: 分布式Erlang: 分布式Erlang是由一组相互通信的Erlang运行时组成,其中每一个运行时称为一个Node.不同Nod ...
- Erlang下与其他程序和语言的通信机制(2)
前面聊了普通端口,今天聊下链入式驱动端口,以及NIFs. 链入式驱动端口 如上图所示,链入式驱动端口与Erlang虚拟机存在于同一个OS进程中. 在Erlang这边与普通端口类似,所有与链入式驱动端口 ...
- netlink---Linux下基于socket的内核和上层通信机制 (转)
需要在linux网卡 驱动中加入一个自己的驱动,实现在内核态完成一些报文处理(这个过程可以实现一种零COPY的网络报文截获),对于复杂报文COPY下必要的数据交给用户 态来完成(因为过于复杂的报文消耗 ...
- Linux下who命令之C语言实现
Linux下who命令之C语言实现 Step1:前期准备 首先要有一个清楚的认识:linux中一切皆文件 实现who命令,who命令也是Linux中的一个文件,那我们怎么找到它呢?我们可以" ...
- JAVA设置环境变量和在DOS下运行java程序
在学校实训的这几天,老师带着我们开始深入的复习java.这是第一天的内容哦 对于“JAVA设置环境变量和在DOS下运行java程序”,许多初学者是陌生的,但了解这个却对后期的学习很重要. http:/ ...
- erlang下lists模块sort(排序)方法源码解析(一)
排序算法一直是各种语言最简单也是最复杂的算法,例如十大经典排序算法(动图演示)里面讲的那样 第一次看lists的sort方法的时候,蒙了,几百行的代码,我心想要这么复杂么(因为C语言的冒泡排序我记得不 ...
- 微信小程序开发语言的选择
微信使用的开发语言和文件很「特殊」. 小程序所使用的程序文件类型大致分为以下几种: ①WXML(WeiXin Mark Language,微信标记语言) ②WXSS(WeiXin Style Shee ...
- Linux下的 sniff-andthen-spoof程序编写
Linux下的 sniff-andthen-spoof程序编写 一.任务描述 在本任务中,您将结合嗅探和欺骗技术来实现以下嗅探然后欺骗程序.你需要两台机器在同一个局域网.从机器A ping IP_X, ...
- VB.net 2010下关联与程序图标设置
'*************************************************************************'**模 块 名:VB.net 2010下关联与程序 ...
随机推荐
- flask 初始
一.flask安装 这里提供两种安装方式: 第一种: pip3 install flask 第二种: pip3 install -i https://pypi.douban.com/simple/ f ...
- 在Swift中,如何像Objective-C定义可选接口?
Objective-C中的protocol里存在@optional关键字,被这个关键字修饰的方法并非必须要被实现.我们可以通过接口定义一系列方法,然后由实现接口的类选择性地实现其中几个方法.在Coco ...
- Extension Methods(扩展方法)
在 OOPL 中,有静态方法.实例方法和虚方法,如下: public sealed class String { public static bool IsNullOrEmpty(st ...
- fresh_bank、、
最近新学习了一个bank系统来和大家分享一下,新人求罩! 破索式之_链子枪_ 废话不多说了直接本主题 如果我们要写出bank系统,就要先考虑这个问题:总共需要几个类? 既然是银行系统,那么必不可少的就 ...
- StackOverflowError&OutOfMemoryError区别
在Java虚拟机规范中,针对内存分配规定两种异常状况,即StackOverflowError和OutOfMemoryError. StackOverflowError:当线程请求的内存大小大于所配置的 ...
- 这辈子写过的比较有意思的几个sql
递归 with myRecursion as( select * from recursion where id=1 union all select r.* from myRecursion m,r ...
- 【译】x86程序员手册12-4.2系统指令
4.2 Systems Instructions 系统指令 Systems instructions deal with such functions as: 系统指令具有以下功能: Verifica ...
- Memcached 在Linux上的安装
1.安装libevent wget https://github.com/libevent/libevent/releases/download/release-2.1.8-stable/libeve ...
- javascript模块化编程(一)(http://www.ruanyifeng.com/blog/2012/10/javascript_module.html)
Javascript模块化编程(一):模块的写法 随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂. 网页越来越像桌面程序,需要一个 ...
- java数据类型和码表、转义字符
类型名称 字节空间 范围 整数型 byte 1 -27到27-1 或者 -128到127 short 2 -215到215-1 int 4 -231到231-1 long 8 ...