Erlang的C Node
| 翻译: | gashero |
|---|---|
| 日期: | 2009-10-16 |
| 地址: | http://www.erlang.org/doc/tutorial/cnode.html |
目录
这个例子是用于解决 http://www.erlang.org/doc/tutorial/example.html 问题的。注意C node不适用于解决简单问题,port会更合适。
1 Erlang程序
从Erlang的观点来看,C node只是一个普通的Erlang结点。因此调用函数foo和bar只是发送消息到C node请求调用函数,和接受结果。发送消息需要接受者;进程可以定义为pid或包含注册进程名和结点名的元组。这种情况下元组只是唯一选择,因为根本就 没有pid。
{RegName,Node} ! Msg
结点名Node是C node的名字。如果使用短结点名,则名字形如 cN 其中N是整数。如果使用长结点名,就没有这么严格了。一个使用短结点名的例子是 c1@idril ,一个使用长结点名的例子是 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.
当使用长结点名时,也只有略微不同,如下只列出 call_cnode() 函数:
call_cnode(Msg) ->
{any,'cnode@idril.du.uab.ericsson.se'} ! {call,self(),Msg},
receive
{cnode,Result}
Result
end.
2 C程序
2.1 设置通信
在开始调用 Erl_interface 函数之前,内存处理需要初始化:
erl_init(NULL,0);
现在C结点已经初始化了。如果使用了短结点名,则使用 erl_connect_init()
erl_connect_init(1,"secretcookie",0);
第一个参数是整数用于构造结点名。这个例子中纯结点名是"c1"。第二个参数是字符串描述的magic cookie,用于识别结点所在的群组。第三个参数是整数,用于标识一个C node的实例。
如果使用长结点名,初始化是如下调用:
erl_connect_xinit("idril","cnode","cnode@idril.ericsson.se",&addr,"secretcookie",0);
前面的3个参数是主机名、纯结点名和完整结点名。第四个参数是指向 in_addr 结构体的指针,标识主机的IP地址,第五个和第六个参数是magic cookie和实例号。
C node可以作为服务器也可以作为客户端与Erlang-C通信。如果作为客户端,它使用 erl_connect() 连接到一个Erlang结点,然后成功返回打开的文件描述符:
fd=erl_connect("e1@idril");
如果C node作为服务器,他必须首先创建socket(调用bind()和listen())和监听合适的端口。然后发布自己的名字和端口号到 epmd (erlang端口映射守护进程,查看epmd的手册):
erl_publish(port);
现在C node服务器可以接受Erlang结点的连接了:
fd=erl_accept(listen,&conn);
第二个参数是 ErlConnect 结构体,包含了连接的有用信息;例如Erlang结点名。
2.2 发送和接受消息
C node可以用 erl_receive_msg() 接收消息。这个函数从打开的文件描述符fd读取数据到缓冲区,然后将结果存入 ErlMessage 类型结构体 emsg 。 ErlMessage 结构体拥有一个字段 type 定义了接受到的消息类型。这种情况下 ERL_REG_SEND 标识接受到了注册进程的消息。手机的消息,一个 ETERM 存放在 msg 字段中。
需要注意的是 ERL_ERROR 类型和 ERL_TICK 类型,分别对应错误和存活检查。其他类型大概是link/unlink和exit之类的。
while(loop) {
got=erl_receive_msg(fd,buf,BUFSIZE,&emsg);
if (got=ERL_TICK) { //忽略即可
} else if(got==ERL_ERROR) {
loop=0;
} else {
if (emsg.type==ERL_REG_SEND) {
消息是一个 ETERM 结构体, erl_interface 函数可以用于操作它。这种情况下,消息是一个三元组(与Erlang代码有关,看看上面)。第二个元素是调用者的pid,第三个元素是元组 {Function,Arg} 决定使用何种参数调用哪个函数。函数调用的结果放入 ETERM 结构体,然后用 erl_send() 发送回去,附带上打开的文件描述符、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 node服务器使用短结点名:
/* 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; //监听端口号
int listen; //监听socket
int fd; //Erlang结点的文件句柄
ErlConnect conn; //连接数据
int loop=1;
int got;
unsigned char buf[BUFSIZE];
ErlMessage emsg;
ETERM *fromp, *tuplep, *fnp, *argp, *resp;
int res;
port=atoi(argv[1]);
erl_init(NULL,0); //初始化c node
if (erl_connect_init(1,"secretcookie",0)==-1)
erl_err_quit("erl_connect_init");
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) {
//忽略
}else if (got==ERL_ERROR) {
loop=0;
}else {
if (emsg.type==ERL_REG_SEND) {
fropm=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;
}
下面的例子是长结点名模式的,区别只是在于 erl_connect_xinit() 。
下面的例子是C node客户端:
/* 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;
int loop=1;
int got;
unsigned char buf[BUFSIZE];
ErlMessage emsg;
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) {
//忽略
}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);
}
}
}
}
3 运行例子
3.1 编译C代码
编译C代码,提供Erl_interface的头文件和库文件路径,还有socket和nsl库。
在R5B或更新版本的OTP中,头文件和库路径在 OTPROOT/lib/erl_interface-VSN ,其中OTPROOT是你的OTP安装路径,VSN是 erl_interface 的应用版本(上面例子是3.2.1。
在R4B或更早版本的OTP中,头文件和库路径在 OTPROOT/usr 。
编译选项:
- -I/usr/local/lib/otp/lib/erl_interface-3.2.1/include
- -L/usr/local/lib/otp/lib/erl_interface-3.2.1/lib
- -lerl_interface
- -lei
- -lsocket
- -lnsl
3.2 编译erlang代码
unix> erl -compile complex3 complex4
3.3 运行C node服务器的例子,短结点名
启动C程序 cserver 和Erlang。cserver以端口号作为参数,且在调用Erlang函数前启动。Erlang结点需要给出短结点名 "e1" 并设置与C node相同的magic cookie。
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
3.4 运行C node客户端例子
停止cserver但是不停止Erlang,启动cclient。Erlang结点必须在C node客户端之前启动:
unix> cclient (e1@idril)3> complex3:foo(3). 4 (e1@idril)4> complex3:bar(5). 10
3.5 运行C node服务器,长结点名的例子
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
本文介绍如何通过Erlang的CNode实现Erlang与C程序间的通信。包括设置通信、消息发送与接收的过程,并提供了服务器端及客户端的示例代码。
759

被折叠的 条评论
为什么被折叠?



