您的位置:首页 > 其它

在 Erlang 中使用 C 接口

2014-02-05 02:27 344 查看

Erlang 端口

Erlang 使用 端口来同其他的操作系统进程进行通信,这个所谓的端口不是那些更为常见的硬件、软件、协议端口,这里说的端口是 Erlang 中的端口。

为了区分,把 Erlang 中的端口称为 Erlang端口好了。

Erlang 端口可以通过标准输入和标准输出同外部操作系统进程进行通信,所以它也需要软件端口的功能;

它还能支持简单的数据格式,完成数据收发功能,可被看作一个 Erlang 进程。

connected process

创建端口的进程被称为 connected process,或 port owner。

端口负责与其他进程通信,而要与其他应用进程通信的 Erlang 进程则必须通过 connected process 来使用 Erlang 端口。

这是个完成简单乘法和加法运算的 C 程序,example1.c:

int twice(int x){
return 2*x;
}

int sum(int x, int y){
return x+y;
}


Erlang 调用外部进程

通过 Erlang 端口,就可以在 Erlang 中调用它。首先需要确定进程间通信数据的格式:

2 个字节作为消息头部,指定消息体部分数据的长度,消息体部分,一个字节指明要进行的操作:用 1 代表twice运算, 2 代表 sum 运算,接下来

的字节为运算参数,如要计算 twice(3), 那么 Erlang进程要发送的消息的十进制格式为: 0213 ,02 为头部,指定消息长度为 2,1指定 twice操作,3 为参数。

刚才说,Erlang 端口可看作一个进程,可自行收发数据,那么在 Erlang 进程中就不需要过多关心数据格式的问题了,不过在外部进程,即 C 这边,

需要简单处理一下,接受和发送一条消息的操作如下,erl_comm.c:

#include <unistd.h>

typedef unsigned char byte;

int read_cmd(byte *buf);
int write_cmd(byte *buf, int len);
int read_exact(byte *buf, int len);
int write_exact(byte *buf, int len);

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 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 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_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);
}


read_cmd 只把消息体部分读入缓存,将消息头的两个字节丢弃,具体为先从标准输入读取 2 byte 数据,作为协议头部,获取消息体数据长度,接下来读取指定长度的消息体放入缓存。

write_cmd 发送消息时。先向标准输出写入2 字节长的要发送的消息长度作为消息头,先写入高 8 位,再写入低 8 位,然后再将 缓冲区 buf 中数据全部写入标准输出

另外,还需要一个 main 来启动外部进程,example1_driver.c:

#include <stdio.h>
typedef unsigned char byte;

int read_cmd(byte *buff);
int write_cmd(byte *buff, int len);

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];
result = sum(arg1, arg2);
}

buff[0] = result;
write_cmd(buff, 1);
}
}


编译这三个文件:

gcc -o example1 example1.c erl_comm.c example1_driver.c


虽然可以,但现在还不能运行它,这是准备在 Erlang 中启动的,Erlang 进程就简单了,只要一个文件, example1.erl:

-module(example1).
-export([start/0, stop/0]).
-export([twice/1, sum/2]).

start() ->
spawn(fun() ->
register(example1, self()),
process_flag(trap_exit, true),
Port = open_port({spawn, "../server/example1"}, [{packet, 2}]),
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},
receive
{example1, Result} ->
Result
end.

loop(Port) ->
receive
{call, Caller, Msg} ->
Port ! {self(), {command, encode(Msg)}},
receive
{Port, {data, Data}} ->
Caller ! {example1, decode(Data)}
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.


说明一下这里创建端口的参数:

Port = open_port({spawn, "../server/example1"}, [{packet, 2}]),

"../server/example1" 是当前目录到刚才编译出的可执行文件的相对路径。

参数 {packet,2} 声明了发送消息前要先发送 2 字节长的数据,值为消息的长度,这个值还可以是 1 或者4 ,即 {packet, 1}或 {packet, 4}

运行:

1> example1:start().
<0.35.0>
2>
2>
2> example1:sum(22,29).
51
3> example1:sum(11.2,55.7).

=ERROR REPORT==== 5-Feb-2014::00:58:46 ===
Bad value on output port '../server/example1'


发生了错误,因为在 erl_common.c 中假设了函数参数均为1个字节,所以无法计算浮点数。

在 Erlang 中数据是不限长度的,只受限于物理内存, C 中则严格规定了各种类型数据的长度,上面这个简单的“协议”并没考虑这些

Reference

Joe Armstrong .Programming Erlang
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: