第十二章 接口技术
Table of Contents
- 第十二章 接口技术
- 12.1 端口
- 创建端口
- 发送数据
- 改变连接进程的PID
- 关闭端口
- 为一个外部C程序添加接口
- 12.2.1 C程序
- 12.2.2 Erlang程序
- 12.3 open_port
- PortName
- Opt
- 12.4 内联驱动
- 12.5 注意
- 12.1 端口
第十二章 接口技术
Erlang运行第三方代码时需要一个与Erlang运行时系统相互独立的外部程序, 两者通过二进制通道进行通信。在Erlang中是通过端口连接进程来作为中间人管理两者之间的通信。
12.1 端口
创建端口
Port = open_port(PortName, PortSettings)
发送数据
Port ! {PidC, {command, Data}}
改变连接进程的PID
Port ! {PidC, connect, Pid1}
关闭端口
Port ! {PidC, close}
为一个外部C程序添加接口
为Erlang与C的程序调用实现一个通信协议:
- 数据包前两个字节表示数据的长度Len, 之后跟上长度为Len的数据
- 调用第一个函数twice(需要一个参数x), 则数据格式为[1, N];
- 调用第二个函数sum(需要两个参数x, y),则数据格式为[2, M, N]
- 返回值规定为一个字节
12.2.1 C程序
- example1.c
包含具体调用的函数
int twice(int x) { return 2*x; } int sum(int x, int y) { return x+y; }
- example_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); } }
- erl_comm.c
读写内存缓冲中的数据
#include <unistd.h> typedef unsigned char byte; int read_cmd(byte *buff); int write_cmd(byte *buff, int len); int read_exact(byte *buff, int len); int write_exact(byte *buff, int len); /* 首先读取两个字节的包头, 然后根据其值(数据包长度)继续读取 */ int read_cmd(byte *buff) { int len; if (read_exact(buff, 2) != 2) { return -1; } len = (buff[0] << 8) | buff[1]; return read_exact(buff, len); } /* 依次写入运行结果:两字节的包头+一字节计算结果 */ int write_cmd(byte *buff, int len) { byte li; li = (len >> 8) & 0xff; write_exact(&li, 1); li = len & 0xff; write_exact(&li, 1); return write_exact(buff, len); } /* 从标准输入中读取指定长度的数据 */ int read_exact(byte *buff, int len) { int i, got = 0; do { if ((i == read(0, buff+got, len-got)) <= 0) { return i; } got += i; } while (got < len); return len; } /* 向标准输出中写入指定长度的数据 */ int write_exact(byte *buff, int len) { int i, wrote = 0; do { if ((i == write(1, buff+wrote, len-wrote)) <= 0) { return i; } wrote += 1; } while (wrote < len); return len; }
12.2.2 Erlang程序
-module(example1). -export([start/0, stop/0]). -export([twice/1, sum/2]). %% 创建一个注册名为example1的进程 %% 使用open_port创建端口, 指明数据包需要两字节长的包头 %% 使用loop等待消息处理 start() -> spawn(fun() -> register(example1, self()), process_flag(trap_exit, true), Port = open_port({spawn, "./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.
运行结果:
1> example1:start(). <0.34.0> 2> example1:sum(45, 32). 77 3> example1:twice(10). 20 4> example1:stop(). stop
12.3 open_port
open_port函数的标准形式:
open_port(PortName, [Opt]) -> Port
其中
PortName
可以是如下形式:
- {spawn, Command}
- {fd, In, Out}
Opt
- {packet, N}
以N字节长度的数据作为包的长度计数
- stream
不包含数据包长度
- {line, Max}
基于行发送消息, 超过Max字节则折行
- {cd, Dir}
只针对当PortName为{spawn, Command}方式时有效, 即使外部程序在Dir目录中启动
- {env, Env}
只针对当PortName为{spawn, Command}方式时有效, 即为外部程序配置运行参数
12.4 内联驱动
把第三方程序编译成共享库, 然后动态的内联到Erlang的运行时系统中称为内联驱动方式。创建内联驱动程序是Erlang与其它语言对接的最有效方式, 但因为内联驱动程序的任何错误都会导致Erlang系统崩溃, 因此这种方式比较危险。
- example1_lid.erl
%% 相比于example1.erl只是多了加载共享库的过程 start() -> start("example1_drv"). start(SharedLib) -> case erl_ddll:load_driver(".", SharedLib) of ok ->ok; {error, already_loaded} ->ok; _ ->exit({error, could_not_load_driver}) end, spawn(fun() ->init(SharedLib) end). init(SharedLib) -> register(example1_lid, self()), Port = open_port({spawn, SharedLib}, []), loop(Port).
- example1_lid.c
/* example1_lid.c */ #include <stdio.h> #include <erl_driver.h> typedef struct { ErlDrvPort port; } example_data; static ErlDrvData example_drv_start(ErlDrvPort port, char *buff) { example_data* d = (example_data*)driver_alloc(sizeof(example_data)); d->port = port; return (ErlDrvData)d; } static void example_drv_stop(ErlDrvData handle) { driver_free((char*)handle); } static void example_drv_output(ErlDrvData handle, char *buff, int bufflen) { example_data* d = (example_data*)handle; char fn = buff[0], arg = buff[1], res; if (fn == 1) { res = twice(arg); } else if (fn == 2) { res = sum(buff[1], buff[2]); } driver_output(d->port, &res, 1); } ErlDrvEntry example_driver_entry = { NULL, /* F_PTR init, N/A */ example_drv_start, /* L_PTR start, 端口打开时调用 */ example_drv_stop, /* F_PTR stop, 端口关闭时调用 */ example_drv_output, /* F_PTR output, 发送数据时调用 */ NULL, /* F_PTR ready_input, 输入设备就绪时调用 */ NULL, /* F_PTR ready_output, 输出设备就绪时调用 */ "example1_drv", /* char *driver_name, 驱动名称, 调用open_port函数时使用 */ NULL, /* F_PTR finish, 卸载驱动时调用 */ NULL, /* F_PTR control, 端口回调 */ NULL, /* F_PTR timeout, 保留 */ NULL /* F_PTR outputv, 保留 */ }; DRIVER_INIT(example_drv) /* must match name in driver_entry */ { return &example_driver_entry; }
运行结果:
5> c(example1_lid). {ok,example1_lid} 6> example1_lid:start(). <0.35.0> 7> example1_lid:sum(45, 32). 77 8> example1_lid:twice(10). 20 9> example1_lid:stop(). stop
12.5 注意
Erlang提供了几个与第三方语言通信的库。
Erl接口(ei), 针对C, 文档链接更新至:
http://www.erlang.org/doc/apps/erl_interface/erl_interface.pdf
Jinteface, 针对Java, 文档链接更新至:
http://www.erlang.org/doc/apps/jinterface/jinterface.pdf
来源:oschina
链接:https://my.oschina.net/zhenggao/blog/4411533