查看源代码 Erl_Interface 用户指南
简介
Erl_Interface
库包含帮助您集成用 C 和 Erlang 编写的程序的函数。 Erl_Interface
中的函数支持以下功能:
- 操作表示为 Erlang 数据类型的数据
- 在 C 和 Erlang 格式之间转换数据
- 编码和解码 Erlang 数据类型,以便传输或存储
- C 节点和 Erlang 进程之间的通信
- 将 C 节点状态备份和恢复到 Mnesia
注意
默认情况下,
Erl_Interface
库仅保证与来自同一版本的其他 Erlang/OTP 组件兼容。有关如何与早期版本的 Erlang/OTP 组件进行通信的信息,请参阅函数ei_set_compat_rel
。
范围
以下部分将介绍这些主题:
- 编译代码以与
Erl_Interface
一起使用 - 初始化
Erl_Interface
- 编码、解码和发送 Erlang 项
- 构建项和模式
- 模式匹配
- 连接到分布式 Erlang 节点
- 使用 Erlang 端口映射守护进程 (EPMD)
- 发送和接收 Erlang 消息
- 远程过程调用
- 使用全局名称
先决条件
假定读者熟悉 Erlang 编程语言。
编译和链接代码
要使用任何 Erl_Interface
函数,请在代码中包含以下行:
#include "ei.h"
确定您的 OTP 安装的顶层目录在哪里。要找到它,请启动 Erlang 并在 Eshell 提示符下输入以下命令:
Eshell V4.7.4 (abort with ^G)
1> code:root_dir().
/usr/local/otp
要编译您的代码,请确保您的 C 编译器知道在哪里可以找到 ei.h
,方法是在命令行上指定适当的 -I
参数,或将其添加到 Makefile
中的 CFLAGS
定义。此路径的正确值是 $OTPROOT/lib/erl_interface-$EIVSN/include
,其中:
$OTPROOT
是上面示例中code:root_dir/0
报告的路径。$EIVSN
是Erl_Interface
应用程序的版本,例如,erl_interface-3.2.3
。
编译代码
$ cc -c -I/usr/local/otp/lib/erl_interface-3.2.3/include myprog.c
链接时
- 使用
-L$OTPROOT/lib/erl_interface-3.2.3/lib
指定libei.a
的路径。 - 使用
-lei
指定库的名称。
在命令行上执行此操作,或将这些标志添加到 Makefile
中的 LDFLAGS
定义。
链接代码
$ ld -L/usr/local/otp/lib/erl_interface-3.2.3/
lib myprog.o -lei -o myprog
在某些系统上,可能需要链接更多库(例如,Solaris 上的 libnsl.a
和 libsocket.a
,或 Windows 上的 wsock32.lib
)才能使用 Erl_Interface
的通信功能。
如果您在基于 POSIX 线程或 Solaris 线程的多线程应用程序中使用 Erl_Interface
函数,则 Erl_Interface
需要访问线程包中的某些同步设施。 您必须指定额外的编译器标志,以指示您使用的软件包。 定义 _REENTRANT
和 STHREADS
或 PTHREADS
。 如果指定了 _REENTRANT
,则默认使用 POSIX 线程。
初始化库
在调用库中的任何其他函数之前,通过调用 ei_init()
准确初始化一次。
编码、解码和发送 Erlang 项
在分布式 Erlang 节点之间发送的数据以 Erlang 外部格式进行编码。 因此,如果要使用分布式协议在 C 程序和 Erlang 之间进行通信,则必须将 Erlang 项编码和解码为字节流。
Erl_Interface
库支持此活动。 它有几个 C 函数可以创建和操作 Erlang 数据结构。 以下示例显示了如何创建和编码 Erlang 元组 {tobbe,3928}
ei_x_buff buf;
ei_x_new(&buf);
int i = 0;
ei_x_encode_tuple_header(&buf, 2);
ei_x_encode_atom(&buf, "tobbe");
ei_x_encode_long(&buf, 3928);
有关完整说明,请参阅 ei
模块。
构建项
可以通过使用 ei_x_format_wo_ver
函数来创建 Erlang 项,从而简化前面的示例。
ei_x_buff buf;
ei_x_new(&buf);
ei_x_format_wo_ver(&buf, "{~a,~i}", "tobbe", 3928);
有关不同格式指令的完整说明,请参阅 ei_x_format_wo_ver
函数。
以下示例更复杂:
ei_x_buff buf;
int i = 0;
ei_x_new(&buf);
ei_x_format_wo_ver(&buf,
"[{name,~a},{age,~i},{data,[{adr,~s,~i}]}]",
"madonna",
21,
"E-street", 42);
ei_print_term(stdout, buf.buff, &i);
ei_x_free(&buf);
与前面的示例一样,您有责任释放为 Erlang 项分配的内存。 在此示例中,ei_x_free()
确保释放 buf
指向的数据。
连接到分布式 Erlang 节点
要连接到分布式 Erlang 节点,您必须首先使用 ei_connect_init_*
函数之一初始化连接例程,该函数存储主机名和节点名称等信息以供以后使用。
int identification_number = 99;
int creation=1;
char *cookie="a secret cookie string"; /* An example */
const char* node_name = "einode@durin";
const char *cookie = NULL;
short creation = time(NULL) + 1;
ei_cnode ec;
ei_connect_init(&ec,
node_name,
cookie,
creation);
有关详细信息,请参阅 ei_connect
模块。
初始化后,设置与 Erlang 节点的连接。要指定要连接到的 Erlang 节点,请使用 ei_connect_*()
系列函数。以下示例设置连接,并生成有效的套接字文件描述符:
int sockfd;
const char* node_name = "einode@durin"; /* An example */
if ((sockfd = ei_connect(&ec, nodename)) < 0)
fprintf(stderr, "ERROR: ei_connect failed");
使用 EPMD
erts:epmd
是 Erlang 端口映射守护进程。 分布式 Erlang 节点在本地主机上向 epmd
注册,以向其他节点指示它们存在并且可以接受连接。epmd
维护节点和端口号信息的注册表,当节点希望连接到另一个节点时,它首先联系 epmd
以查找要连接的正确端口号。
当您使用 ei_connect
连接到 Erlang 节点时,首先与 epmd
建立连接,如果已知该节点,则与 Erlang 节点建立连接。
C 节点也可以向 epmd
注册自己,如果它们希望系统中的其他节点能够找到并连接到它们。
在向 epmd
注册之前,您必须首先创建一个侦听套接字并将其绑定到端口。然后
int pub = ei_publish(&ec, port);
pub
是现在连接到 epmd
的文件描述符。epmd
监视连接的另一端。如果检测到连接已关闭,则该节点将取消注册。因此,如果您显式关闭描述符或节点失败,它将从 epmd
中取消注册。
请注意,在某些系统上,此机制不会检测到失败的节点,因为操作系统不会自动关闭节点失败时保持打开状态的描述符。如果节点以这种方式失败,epmd
会阻止您使用旧名称注册新节点,因为它认为旧名称仍在使用。在这种情况下,您必须显式关闭端口:
发送和接收 Erlang 消息
使用以下两个函数之一发送消息:
与 Erlang 中一样,消息可以发送到 pid 或注册名称。向注册名称发送消息更容易,因为它避免了查找合适 pid 的问题。
使用以下两个函数之一接收消息:
发送消息示例
在以下示例中,{Pid, hello_world}
被发送到注册的进程 my_server
:
ei_x_buff buf;
ei_x_new_with_version(&buf);
ei_x_encode_tuple_header(&buf, 2);
ei_x_encode_pid(&buf, ei_self(ec));
ei_x_encode_atom(&buf, "Hello world");
ei_reg_send(&ec, fd, "my_server", buf.buff, buf.index);
发送的元组的第一个元素是您自己的 pid。这使 my_server
可以回复。有关原语的详细信息,请参阅 ei_connect
模块。
接收消息示例
在此示例中,接收到 {Pid, Something}
。
erlang_msg msg;
int index = 0;
int version;
int arity = 0;
erlang_pid pid;
ei_x_buff buf;
ei_x_new(&buf);
for (;;) {
int got = ei_xreceive_msg(fd, &msg, &x);
if (got == ERL_TICK)
continue;
if (got == ERL_ERROR) {
fprintf(stderr, "ei_xreceive_msg, got==%d", got);
exit(1);
}
break;
}
ei_decode_version(buf.buff, &index, &version);
ei_decode_tuple_header(buf.buff, &index, &arity);
if (arity != 2) {
fprintf(stderr, "got wrong message");
exit(1);
}
ei_decode_pid(buf.buff, &index, &pid);
为了提供稳健性,分布式 Erlang 节点会定期轮询所有已连接的邻居,以尝试检测失败的节点或通信链路。 收到此类消息的节点应立即使用 ERL_TICK
消息进行响应。 这由 ei_xreceive_msg()
自动完成。 但是,当这种情况发生时,ei_xreceive_msg
会向调用方返回 ERL_TICK
,而不会将消息存储到 erlang_msg
结构中。
收到消息后,调用方有责任释放接收到的消息。
有关详细信息,请参阅 ei_connect
和 ei
模块。
远程过程调用
作为另一个 Erlang 节点的客户端的 Erlang 节点通常会发送请求并等待回复。 此类请求包含在远程节点的函数调用中,称为远程过程调用。
以下示例检查特定的 Erlang 进程是否处于活动状态:
int index = 0, is_alive;
ei_x_buff args, result;
ei_x_new(&result);
ei_x_new(&args);
ei_x_encode_list_header(&args, 1);
ei_x_encode_pid(&args, &check_pid);
ei_x_encode_empty_list(&args);
if (ei_rpc(&ec, fd, "erlang", "is_process_alive",
args.buff, args.index, &result) < 0)
handle_error();
if (ei_decode_version(result.buff, &index) < 0
|| ei_decode_bool(result.buff, &index, &is_alive) < 0)
handle_error();
有关 ei_rpc()
及其同伴 ei_rpc_to()
和 ei_rpc_from()
的详细信息,请参阅 ei_connect
模块。
使用全局名称
C 节点可以访问通过 Kernel 中的 global
模块注册的名称。 可以查找名称,从而允许 C 节点向命名的 Erlang 服务发送消息。 C 节点还可以注册全局名称,从而允许它们向 Erlang 进程或其他 C 节点提供命名服务。
Erl_Interface
不提供全局服务的本机实现。 相反,它使用“附近”Erlang 节点提供的全局服务。 要使用本节中描述的服务,必须首先打开与 Erlang 节点的连接。
要查看有哪些名称:
char **names;
int count;
int i;
names = ei_global_names(&ec,fd,&count);
if (names)
for (i=0; i<count; i++)
printf("%s\n",names[i]);
free(names);
ei_global_names
分配并返回一个缓冲区,其中包含 Kernel
中 global
模块已知的所有名称。count
被初始化以指示数组中名称的数量。 名称中的字符串数组以 NULL
指针终止,因此无需使用 count
来确定何时到达最后一个名称。
调用者有责任释放数组。ei_global_names
使用对 malloc()
的单次调用来分配数组和所有字符串,因此只需要 free(names)
即可。
要查找其中一个名称
ETERM *pid;
char node[256];
erlang_pid the_pid;
if (ei_global_whereis(&ec,fd,"schedule",&the_pid,node) < 0)
fprintf(stderr, "ei_global_whereis error\n");
如果 "schedule"
在 Kernel
的 global
模块中已知,则会将 Erlang pid 写入 the_pid。可以使用此 pid 向 schedule 服务发送消息。此外,node
被初始化为包含服务注册所在节点的名称,因此您可以通过简单地将该变量传递给 ei_connect
来建立连接。
在注册名称之前,您应该已经使用 epmd
注册了您的端口号。 这并非绝对必要,但如果您忽略这样做,则其他希望与您的服务通信的节点将无法找到或连接到您的进程。
创建一个 Erlang 进程可以用来与您的服务通信的名称
ei_global_register(fd,servicename,ei_self(ec));
注册名称后,使用 ei_accept
来等待传入的连接。
注意
记住稍后使用
ei_x_free
释放pid
。
要取消注册名称
ei_global_unregister(&ec,fd,servicename);