非常教程

Erlang 20参考手册

指南:教程 | Guide: Tutorial

7. C节点 | 7. C Nodes

本节概述了如何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
Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1