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下关联与程序 ...
随机推荐
- ZOJ 3666 博弈 SG函数
SG函数: 对于任意状态,定义SG(x)=mex(S),其中S是x的后继状态的SG函数值集合,mex(S)表示不再S内的最小非负整数 SG(X)=0当且仅当x为必败态. 解: 构造一个有向无环图(树) ...
- 332 Reconstruct Itinerary 重建行程单
Given a list of airline tickets represented by pairs of departure and arrival airports [from, to], r ...
- Hadoop Hive概念学习系列之hive里的视图(十二)
不多说,直接上干货! 可以先,从MySQL里的视图概念理解入手 视图是由从数据库的基本表中选取出来的数据组成的逻辑窗口,与基本表不同,它是一个虚表.在数据库中,存放的只是视图的定义,而不存放视图包含的 ...
- favourite和favorite的区别
同一个词,英式和美式的拼写法而已.通常英式英语里的-our-字母组合,到了美式英语里面都成了-or-字母组合,最常见的有英式的 colour,到美式英语成了 color.
- c# winform控件dock属性停造位置、摆放顺序详解
dock : [英文释义- 码头.依靠][winform释义- 获取或设置当前控件依靠到父容器的哪一个边缘.] 用途:多数控件都有这个属性,主要用来设置控件的布局. 但对于不太了解这个属性的朋友来说有 ...
- 笨拙而诡异的 Oracle(之二)
有一张表,很多数据: 想取某个月的数据.初始的想法很简单,根据日期(RQ)形成条件即可: 符合条件的记录数是 129835,但耗时太长:14.515 秒(RQ字段是做过索引的)!直观的反应是 O ...
- Burn Down Chart(2018.6.4~2018.6.10)
Burn Down Chart (2018.6.4~2018.6.10) 娄雨禛[前端部分] 曾子轩[后端部分+燃尽图] 前端 1. 娄雨禛+李鑫 1)在总工程中完成跳转,实现图片显示,并发布到Git ...
- jQuery——this
js注册事件this代表的dom对象 jQuery注册事件this代表的也是dom对象,所以需要$(this)转成jQuery对象
- 比较简单的替换配置文件的shell脚本
作为测试,日常更新部署测试版本,修改配置文件是每天必不可少的一个工作.特别是如果需要更改的配置文件存在于多个文件里,更是繁琐不堪. 找了一下Linux shell脚本里有个sed 命令可以实现这个需求 ...
- accmcolor
accmcolor c; c.setcolorindex(1); playertablerecord.setcolor(c);