7. C节点 | 7. C Nodes

7C节点

本节概述了如何Problem Example使用C节点解决示例问题的示例。注意C节点通常不用于解决像这样的简单问题,一个端口就足够了。

7.1 Erlang程序

从Erlang的角度来看,C节点被视为正常的Erlang节点。因此,调用的功能foobar仅涉及发送一个消息到C节点要求的功能被调用,以及接收结果。发送消息需要一个接收者,也就是一个可以使用pid或tuple定义的进程,这个进程可以由注册名称和节点名称组成。在这种情况下,元组是唯一的选择,因为没有pid是已知的:

{RegName, Node} ! Msg

节点名称Node将是C节点的名称。如果使用短节点名称,则节点的普通名称是cN,其中N是整数。如果使用长节点名称,则不存在这种限制。因此c1@idril,使用短节点名称的C节点名称的示例是使用长节点名称的示例cnode@idril.ericsson.se

注册名称RegName可以是任何原子。该名称可以被C代码忽略,或者例如用于区分不同类型的消息。下面是使用短节点名称的Erlang代码示例:

-module(complex3). -export([foo/1, bar/1]). foo(X) -> call_cnode{foo, X}). bar(Y) -> call_cnode{bar, Y}). call_cnode(Msg) -> {any, c1@idril} ! {call, self(), Msg}, receive {cnode, Result} -> Result end.

使用长节点名称时,代码略有不同,如下例所示:

-module(complex4). -export([foo/1, bar/1]). foo(X) -> call_cnode{foo, X}). bar(Y) -> call_cnode{bar, Y}). call_cnode(Msg) -> {any, 'cnode@idril.du.uab.ericsson.se'} ! {call, self(), Msg}, receive {cnode, Result} -> Result end.

7.2 C程序

设置通信

在调用Erl_Interface中的任何其他函数之前,必须启动内存处理:

erl_init(NULL, 0

现在可以启动C节点。如果使用短节点名称,则通过调用erl_connect_init()

erl_connect_init(1, "secretcookie", 0

在此:

  • 第一个参数是用于构造节点名称的整数。在这个例子中,普通的节点名称是c1

  • 第二个参数是一个定义魔术cookie的字符串。

  • 第三个参数是一个用于标识C节点的特定实例的整数。

如果使用长节点节点名称,则通过调用erl_connect_xinit()以下命令完成初始化:

erl_connect_xinit("idril", "cnode", "cnode@idril.ericsson.se", &addr, "secretcookie", 0

在此:

  • 第一个参数是主机名。

  • 第二个参数是普通节点名。

  • 第三个参数是完整的节点名。

  • 第四个参数是指向in_addr具有主机IP地址的结构。

  • 第五个参数是魔术饼干。

  • 第六个参数是实例号。

设置Erlang-C通信时,C节点可以充当服务器或客户端。如果它作为一个客户端,它通过调用连接到一个Erlang节点erl_connect(),这会在成功时返回一个打开的文件描述符:

fd = erl_connect("e1@idril"

如果C节点充当服务器,它必须首先创建一个套接字(调用bind()listen())侦听某个端口号port。然后它发布它的名字和端口号epmd,Erlang端口映射器守护进程。有关详细信息,请参阅epmdERTS中的手册页面:

erl_publish(port

现在C节点服务器可以接受来自Erlang节点的连接:

fd = erl_accept(listen, &conn

第二个参数erl_accept是一个结构ErlConnect,它包含建立连接时的有用信息,例如Erlang节点的名称。

发送和接收消息

C节点可以通过调用从Erlang接收消息erl_receive msg()。该函数从打开的文件描述符中读取数据fd到缓冲区中,并将结果放入ErlMessage结构中emsgErlMessage有一个字段type定义接收什么样的数据。在这种情况下,感兴趣的类型ERL_REG_SEND表示Erlang向C节点的注册进程发送消息。实际的信息ETERM是,在msg现场。

还需要注意类型ERL_ERROR(发生错误)和ERL_TICK(从其他节点的活动检查,将被忽略)。其他可能的类型表示过程事件,如链接,取消链接和退出:

while (loop) { got = erl_receive_msg(fd, buf, BUFSIZE, &emsg if (got == ERL_TICK) { /* ignore */ } else if (got == ERL_ERROR) { loop = 0; /* exit while loop */ } else { if (emsg.type == ERL_REG_SEND) {

由于消息是一个ETERM结构体,因此可以使用Erl_Interface函数来操作它。在这种情况下,消息变成了3元组,因为这就是Erlang代码的写法。第二个元素将是调用者的pid,第三个元素将是{Function,Arg}决定调用哪个函数的元组以及使用哪个参数。调用该函数的结果也被编译为一个ETERM结构体,并使用erl_send()后者返回给Erlang ,它将打开的文件描述符,一个pid和一个术语作为参数:

fromp = erl_element(2, emsg.msg tuplep = erl_element(3, emsg.msg fnp = erl_element(1, tuplep argp = erl_element(2, tuplep if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) { res = foo(ERL_INT_VALUE(argp) } else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) { res = bar(ERL_INT_VALUE(argp) } resp = erl_format("{cnode, ~i}", res erl_send(fd, fromp, resp

最后,ETERM创建函数分配的内存(包括erl_receive_msg()必须释放的内存:

erl_free_term(emsg.from erl_free_term(emsg.msg erl_free_term(fromp erl_free_term(tuplep erl_free_term(fnp erl_free_term(argp erl_free_term(resp

下面的示例显示了生成的C程序。首先,使用短节点名称的C节点服务器:

/* cnode_s.c */ #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include "erl_interface.h" #include "ei.h" #define BUFSIZE 1000 int main(int argc, char **argv) { int port; /* Listen port number */ int listen; /* Listen socket */ int fd; /* fd to Erlang node */ ErlConnect conn; /* Connection data */ int loop = 1; /* Loop flag */ int got; /* Result of receive */ unsigned char buf[BUFSIZE]; /* Buffer for incoming message */ ErlMessage emsg; /* Incoming message */ ETERM *fromp, *tuplep, *fnp, *argp, *resp; int res; port = atoi(argv[1] erl_init(NULL, 0 if (erl_connect_init(1, "secretcookie", 0) == -1) erl_err_quit("erl_connect_init" /* Make a listen socket */ if ((listen = my_listen(port)) <= 0) erl_err_quit("my_listen" if (erl_publish(port) == -1) erl_err_quit("erl_publish" if ((fd = erl_accept(listen, &conn)) == ERL_ERROR) erl_err_quit("erl_accept" fprintf(stderr, "Connected to %s\n\r", conn.nodename while (loop) { got = erl_receive_msg(fd, buf, BUFSIZE, &emsg if (got == ERL_TICK) { /* ignore */ } else if (got == ERL_ERROR) { loop = 0; } else { if (emsg.type == ERL_REG_SEND) { fromp = erl_element(2, emsg.msg tuplep = erl_element(3, emsg.msg fnp = erl_element(1, tuplep argp = erl_element(2, tuplep if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) { res = foo(ERL_INT_VALUE(argp) } else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) { res = bar(ERL_INT_VALUE(argp) } resp = erl_format("{cnode, ~i}", res erl_send(fd, fromp, resp erl_free_term(emsg.from erl_free_term(emsg.msg erl_free_term(fromp erl_free_term(tuplep erl_free_term(fnp erl_free_term(argp erl_free_term(resp } } } /* while */ } int my_listen(int port) { int listen_fd; struct sockaddr_in addr; int on = 1; if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return (-1 setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) memset((void*) &addr, 0, (size_t) sizeof(addr) addr.sin_family = AF_INET; addr.sin_port = htons(port addr.sin_addr.s_addr = htonl(INADDR_ANY if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) return (-1 listen(listen_fd, 5 return listen_fd; }

使用长节点名称的C节点服务器:

/* cnode_s2.c */ #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include "erl_interface.h" #include "ei.h" #define BUFSIZE 1000 int main(int argc, char **argv) { struct in_addr addr; /* 32-bit IP number of host */ int port; /* Listen port number */ int listen; /* Listen socket */ int fd; /* fd to Erlang node */ ErlConnect conn; /* Connection data */ int loop = 1; /* Loop flag */ int got; /* Result of receive */ unsigned char buf[BUFSIZE]; /* Buffer for incoming message */ ErlMessage emsg; /* Incoming message */ ETERM *fromp, *tuplep, *fnp, *argp, *resp; int res; port = atoi(argv[1] erl_init(NULL, 0 addr.s_addr = inet_addr("134.138.177.89" if (erl_connect_xinit("idril", "cnode", "cnode@idril.du.uab.ericsson.se", &addr, "secretcookie", 0) == -1) erl_err_quit("erl_connect_xinit" /* Make a listen socket */ if ((listen = my_listen(port)) <= 0) erl_err_quit("my_listen" if (erl_publish(port) == -1) erl_err_quit("erl_publish" if ((fd = erl_accept(listen, &conn)) == ERL_ERROR) erl_err_quit("erl_accept" fprintf(stderr, "Connected to %s\n\r", conn.nodename while (loop) { got = erl_receive_msg(fd, buf, BUFSIZE, &emsg if (got == ERL_TICK) { /* ignore */ } else if (got == ERL_ERROR) { loop = 0; } else { if (emsg.type == ERL_REG_SEND) { fromp = erl_element(2, emsg.msg tuplep = erl_element(3, emsg.msg fnp = erl_element(1, tuplep argp = erl_element(2, tuplep if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) { res = foo(ERL_INT_VALUE(argp) } else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) { res = bar(ERL_INT_VALUE(argp) } resp = erl_format("{cnode, ~i}", res erl_send(fd, fromp, resp erl_free_term(emsg.from erl_free_term(emsg.msg erl_free_term(fromp erl_free_term(tuplep erl_free_term(fnp erl_free_term(argp erl_free_term(resp } } } } int my_listen(int port) { int listen_fd; struct sockaddr_in addr; int on = 1; if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) return (-1 setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) memset((void*) &addr, 0, (size_t) sizeof(addr) addr.sin_family = AF_INET; addr.sin_port = htons(port addr.sin_addr.s_addr = htonl(INADDR_ANY if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) return (-1 listen(listen_fd, 5 return listen_fd; }

最后,C节点客户机的代码:

/* cnode_c.c */ #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include "erl_interface.h" #include "ei.h" #define BUFSIZE 1000 int main(int argc, char **argv) { int fd; /* fd to Erlang node */ int loop = 1; /* Loop flag */ int got; /* Result of receive */ unsigned char buf[BUFSIZE]; /* Buffer for incoming message */ ErlMessage emsg; /* Incoming message */ ETERM *fromp, *tuplep, *fnp, *argp, *resp; int res; erl_init(NULL, 0 if (erl_connect_init(1, "secretcookie", 0) == -1) erl_err_quit("erl_connect_init" if ((fd = erl_connect("e1@idril")) < 0) erl_err_quit("erl_connect" fprintf(stderr, "Connected to ei@idril\n\r" while (loop) { got = erl_receive_msg(fd, buf, BUFSIZE, &emsg if (got == ERL_TICK) { /* ignore */ } else if (got == ERL_ERROR) { loop = 0; } else { if (emsg.type == ERL_REG_SEND) { fromp = erl_element(2, emsg.msg tuplep = erl_element(3, emsg.msg fnp = erl_element(1, tuplep argp = erl_element(2, tuplep if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) { res = foo(ERL_INT_VALUE(argp) } else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) { res = bar(ERL_INT_VALUE(argp) } resp = erl_format("{cnode, ~i}", res erl_send(fd, fromp, resp erl_free_term(emsg.from erl_free_term(emsg.msg erl_free_term(fromp erl_free_term(tuplep erl_free_term(fnp erl_free_term(argp erl_free_term(resp } } } }

7.3运行示例

第1步。编译C代码。这提供给Erl_Interface路径包括文件和库,并以socketnsl库:

> gcc -o cserver \\ -I/usr/local/otp/lib/erl_interface-3.2.1/include \\ -L/usr/local/otp/lib/erl_interface-3.2.1/lib \\ complex.c cnode_s.c \\ -lerl_interface -lei -lsocket -lnsl unix> gcc -o cserver2 \\ -I/usr/local/otp/lib/erl_interface-3.2.1/include \\ -L/usr/local/otp/lib/erl_interface-3.2.1/lib \\ complex.c cnode_s2.c \\ -lerl_interface -lei -lsocket -lnsl unix> gcc -o cclient \\ -I/usr/local/otp/lib/erl_interface-3.2.1/include \\ -L/usr/local/otp/lib/erl_interface-3.2.1/lib \\ complex.c cnode_c.c \\ -lerl_interface -lei -lsocket -lnsl

二郎/ OTP R5B和OTP的更高版本中,includelib目录位于下OTPROOT/lib/erl_interface-VSN,其中OTPROOT是OTP安装的根目录(/usr/local/otp在最近的例子),并VSN是Erl_Interface应用程序的版本(3.2.1在最近的例子) 。

在R4B和更早版本的OTP中,includelib位于下OTPROOT/usr

第二步。编译Erlang代码:

unix> erl -compile complex3 complex4

第三步。使用短节点名运行C节点服务器示例。

具体如下:

  • cserver在不同的窗口中启动C程序和Erlang。

  • cserver 将端口号作为参数,并且必须在尝试调用Erlang函数之前启动。

  • Erlang节点将被赋予短名称,e1并且必须设置为使用与C节点相同的魔术cookie secretcookie

unix> cserver 3456 unix> erl -sname e1 -setcookie secretcookie Erlang (BEAM) emulator version 4.9.1.2 Eshell V4.9.1.2 (abort with ^G) (e1@idril)1> complex3:foo(3). 4 (e1@idril)2> complex3:bar(5). 10

第4步。运行C节点客户端示例。终止cserver,但不是Erlang,并开始cclient。Erlang节点必须在C节点客户端之前启动:

unix> cclient (e1@idril)3> complex3:foo(3). 4 (e1@idril)4> complex3:bar(5). 10

第五步。使用长节点名运行C节点服务器示例:

unix> cserver2 3456 unix> erl -name e1 -setcookie secretcookie Erlang (BEAM) emulator version 4.9.1.2 Eshell V4.9.1.2 (abort with ^G) (e1@idril.du.uab.ericsson.se)1> complex4:foo(3). 4 (e1@idril.du.uab.ericsson.se)2> complex4:bar(5). 10