1.erlang与c通信的原理
erlang与c或者其它语言通信方式:首先建立一个中介者(port),他们分别与中介者通信然后中介者为他们传递消息;
实现的方式有两种:一种是c程序建立进程独立执行,一种是C程序与erlang不独立,以动态库的形式被加载(效率高,程序容易崩溃)
通信媒介:二进制流.
2.erlang与c通信的方法
通信的方法:
1)外部程序独立
port由erlang创建,erlang->c 外部地址是c程序的pid,c->erlang地址是port;
如果通信?最简单的是该语句:模块名:函数(参数)能够调用到c程序里面的函数。 如何调用的?调用的时候通信的话协议怎么设计?随便设计肯定会存在问题,格式不匹配取得的值会出错。
步骤:
1.编写C函数;
2.协议设计;如调用 c中的add(a,b),erlang发送的字节为:(0,3,2,10,23)四个字节,0,3为真实数据有3个字节,2为函数顺序得由接口来解析,实现对应的调用,后面两个分别为参数。
那么,erlang程序如何调用呢?分三个步骤:1.注册port并启动监听进程;2.erlang中实现add函数(与port通信);3.进程完成port信号的接收并把add函数的信号发给c的接口(驱动)函数 。
(1)实现监听进程
对应的erlang模块需要以进程的形式运行,然后接收到请求就进行类似
{call,Caller,Msg}->
Port ! {self(),{command,encode(Msg)(编码然后好让接口C函数调用)}},
%接收结果
receive
{Port,{data,Data}}->
Caller ! {example1,decode{Data](对data进行解码)}
end.
的操作
(2)erlang中add函数
add(x,y)->callport({add,x,y})->example1(port启动进程注册好的) ! {call,self(),Msg},-->接收结果 receive {example1,Result}->Result end
注:port之前得先注册和打开,
3.编写接口(用于读取,编码,解析,发送C程序的结果),以进程的形式运行;
4.编写erlang程序用于调用C函数,并与接口通信。
2)外部程序不独立(不推荐,暂时不写)
3.erlang与c通信的具体实现
1)外部程序独立
文件个数:
1.c函数.c
实现int add(int x, int y){return x+y;}
2.基本通信函数(按协议读取,编码,解析).c
//读
int read_cmd(byte *buf)
{
int len;
if (read_exact(buf, 2) != 2)
return(-1);
len = (buf[0] << 8) | buf[1];
return read_exact(buf, len);
}
int 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);
}
//写
int 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);
}
int 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);
}
3.接口(驱动程序,进程).c
int main() {
int fn, arg1, arg2, result;
byte buff[100];
while (read_cmd(buff) > 0) {
fn = buff[0];
if (fn == 1) {
arg1 = buff[1];
result = twice(arg1);
} else if (fn == 2) {
arg1 = buff[1];
arg2 = buff[2];
/* debug -- you can print to stderr to debug
fprintf(stderr,"calling sum %i %i\n",arg1,arg2); */
result = sum(arg1, arg2);
}
buff[0] = result;
write_cmd(buff, 1);
}
}
4.erlang程序.erl
-module(example1).
-export([start/0, stop/0]).
-export([twice/1, sum/2]).
start() ->
spawn(fun() ->
register(example1, self()),%注册端口控制进程(即erlang进程)
process_flag(trap_exit, true),%变成系统进程可以监控别的进程的退出
Port = open_port({spawn, "./example1"}, [{packet, 2}]),%打开一个端口,使得与example1(即那个接口c进程关联),在头部加两个字节用于表示发送消息的长度
loop(Port)
end).
stop() ->
example1 ! stop.
twice(X) -> call_port({twice, X}).
sum(X,Y) -> call_port({sum, X, Y}).
call_port(Msg) ->
example1 ! {call, self(), Msg},%发送到erlang进程
receive
{example1, Result} ->%从erlang进程取结果
Result
end.
loop(Port) ->
receive
{call, Caller, Msg} ->%接收到命令行请求,请求都用Caller标记
Port ! {self(), {command, encode(Msg)}}, %往接口发数据,command由port进程来处理,encode是对协议编码,即c程序取到的协议字段
receive
{Port, {data, Data}} ->%接收到c函数返回数据
Caller ! {example1, decode(Data)}%把返回数据解码后发给erlang进程
end,
loop(Port);
stop ->
Port ! {self(), close},
receive
{Port, closed} ->
exit(normal)
end;
{'EXIT', Port, Reason} ->
exit({port_terminated,Reason})
end.
encode({twice, X}) -> [1, X];
encode({sum, X, Y}) -> [2, X, Y].
decode([Int]) -> Int.