在 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
相关文章推荐
- Comparable接口的实现和使用
- Redis使用总结(2):Python接口
- 如何使用Lock接口来实现等待/通知机制
- Python接口(1):使用Python调用C/C++的种种方法
- Effective C++:条款18:让接口容易被正确使用,不易被误用
- C++中使用接口
- 接口对象的实例化在接口回调中的使用
- ITopologicalOperator接口使用方法
- 在VB中如何直接使用CLSID生成接口实例
- ArrayList容器排序 comparator接口与comparable接口的使用
- 微信推广二维码接口使用总结
- 使用HttpClient工具调用WebService接口的示例
- 使用apidoc管理RESTful风格Flask项目接口文档方法
- eclipse + JBoss 5 + EJB3开发指南(3):使用Session Bean的本地接口
- Java学习笔记(七) Enumertation接口的理解和使用和StringTokenizer的使用
- 如何使用opencv的c++接口来读取、写结构体数组到xml文件中
- Postman接口测试工具的使用_2
- Struts2.X PreResultListener接口的使用
- Asp.Net WebService 使用后来管理系统对接口方法进行公开控制
- 微信公众号接口开发为什么不能使用循环语句?