7. C节点 | 7. C Nodes
本节概述了如何Problem Example
使用C节点解决示例问题的示例。注意C节点通常不用于解决像这样的简单问题,一个端口就足够了。
7.1 Erlang程序
从Erlang的角度来看,C节点被视为正常的Erlang节点。因此,调用的功能foo
和bar
仅涉及发送一个消息到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端口映射器守护进程。有关详细信息,请参阅epmd
ERTS中的手册页面:
erl_publish(port);
现在C节点服务器可以接受来自Erlang节点的连接:
fd = erl_accept(listen, &conn);
第二个参数erl_accept
是一个结构ErlConnect
,它包含建立连接时的有用信息,例如Erlang节点的名称。
发送和接收消息
C节点可以通过调用从Erlang接收消息erl_receive msg()
。该函数从打开的文件描述符中读取数据fd
到缓冲区中,并将结果放入ErlMessage
结构中emsg
。ErlMessage
有一个字段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路径包括文件和库,并以socket
和nsl
库:
> 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的更高版本中,include
和lib
目录位于下OTPROOT/lib/erl_interface-VSN
,其中OTPROOT
是OTP安装的根目录(/usr/local/otp
在最近的例子),并VSN
是Erl_Interface应用程序的版本(3.2.1在最近的例子) 。
在R4B和更早版本的OTP中,include
并lib
位于下OTPROOT/usr
。
第二步。编译Erlang代码:
unix> erl -compile complex3 complex4
第三步。使用短节点名运行C节点服务器示例。
具体如下:
cserver
在不同的窗口中启动C程序和Erlang。
cserver
将端口号作为参数,并且必须在尝试调用Erlang函数之前启动。
- Erlang节点将被赋予短名称,
e1
并且必须设置为使用与C节点相同的魔术cookiesecretcookie
:
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
本文档系腾讯云开发者社区成员共同维护,如有问题请联系 cloudcommunity@tencent.com