7 C Nodes

This section outlines an example of how to solve the example problem in Problem Example by using a C node. Notice that a C node is not typically used for solving simple problems like this, a port is sufficient.

7.1  Erlang Program

From Erlang's point of view, the C node is treated like a normal Erlang node. Thus, calling the functions foo and bar only involves sending a message to the C node asking for the function to be called, and receiving the result. Sending a message requires a recipient, that is, a process that can be defined using either a pid or a tuple, consisting of a registered name and a node name. In this case, a tuple is the only alternative as no pid is known:

{RegName, Node} ! Msg

The node name Node is to be the name of the C node. If short node names are used, the plain name of the node is cN, where N is an integer. If long node names are used, there is no such restriction. An example of a C node name using short node names is thus c1@idril, an example using long node names is [email protected].

The registered name, RegName, can be any atom. The name can be ignored by the C code, or, for example, be used to distinguish between different types of messages. An example of Erlang code using short node names follows:


-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.

When using long node names, the code is slightly different as shown in the following example:


-module(complex4).
-export([foo/1, bar/1]).

foo(X) ->
    call_cnode({foo, X}).
bar(Y) ->
    call_cnode({bar, Y}).

call_cnode(Msg) ->
    {any, '[email protected]'} ! {call, self(), Msg},
    receive
	{cnode, Result} ->
	    Result
    end.

7.2  C Program

Setting Up Communication

Before calling any other function in Erl_Interface, the memory handling must be initiated:

erl_init(NULL, 0);

Now the C node can be initiated. If short node names are used, this is done by calling erl_connect_init():

erl_connect_init(1, "secretcookie", 0);

Here:

  • The first argument is the integer used to construct the node name.

    In the example, the plain node name is c1.

  • The second argument is a string defining the magic cookie.
  • The third argument is an integer that is used to identify a particular instance of a C node.

If long node node names are used, initiation is done by calling erl_connect_xinit():

erl_connect_xinit("idril", "cnode", "[email protected]",
                  &addr, "secretcookie", 0);

Here:

  • The first argument is the host name.
  • The second argument is the plain node name.
  • The third argument is the full node name.
  • The fourth argument is a pointer to an in_addr struct with the IP address of the host.
  • The fifth argument is the magic cookie.
  • The sixth argument is the instance number.

The C node can act as a server or a client when setting up the Erlang-C communication. If it acts as a client, it connects to an Erlang node by calling erl_connect(), which returns an open file descriptor at success:

fd = erl_connect("e1@idril");

If the C node acts as a server, it must first create a socket (call bind() and listen()) listening to a certain port number port. It then publishes its name and port number with epmd, the Erlang port mapper daemon. For details, see the epmd manual page in ERTS:

erl_publish(port);

Now the C node server can accept connections from Erlang nodes:

fd = erl_accept(listen, &conn);

The second argument to erl_accept is a struct ErlConnect which contains useful information when a connection has been established, for example, the name of the Erlang node.

Sending and Receiving Messages

The C node can receive a message from Erlang by calling erl_receive msg(). This function reads data from the open file descriptor fd into a buffer and puts the result in an ErlMessage struct emsg. ErlMessage has a field type defining what kind of data is received. In this case, the type of interest is ERL_REG_SEND which indicates that Erlang sent a message to a registered process at the C node. The actual message, an ETERM, is in the msg field.

It is also necessary to take care of the types ERL_ERROR (an error occurred) and ERL_TICK (alive check from other node, is to be ignored). Other possible types indicate process events such as link, unlink, and exit:

  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) {

As the message is an ETERM struct, Erl_Interface functions can be used to manipulate it. In this case, the message becomes a 3-tuple, because that is how the Erlang code is written. The second element will be the pid of the caller and the third element will be the tuple {Function,Arg} determining which function to call, and with which argument. The result of calling the function is made into an ETERM struct as well and sent back to Erlang using erl_send(), which takes the open file descriptor, a pid, and a term as arguments:

        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);

Finally, the memory allocated by the ETERM creating functions (including erl_receive_msg() must be freed:

        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);

The following examples show the resulting C programs. First a C node server using short node names:


/* 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;
}

A C node server using long node names:


/* 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", "[email protected]",
			&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;
}

Finally, the code for the C node client:


/* 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  Running the Example

Step 1. Compile the C code. This provides the paths to the Erl_Interface include files and libraries, and to the socket and nsl libraries:

>  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

In Erlang/OTP R5B and later versions of OTP, the include and lib directories are situated under OTPROOT/lib/erl_interface-VSN, where OTPROOT is the root directory of the OTP installation (/usr/local/otp in the recent example) and VSN is the version of the Erl_Interface application (3.2.1 in the recent example).

In R4B and earlier versions of OTP, include and lib are situated under OTPROOT/usr.

Step 2. Compile the Erlang code:

unix> erl -compile complex3 complex4

Step 3. Run the C node server example with short node names.

Do as follows:

  • Start the C program cserver and Erlang in different windows.
  • cserver takes a port number as argument and must be started before trying to call the Erlang functions.
  • The Erlang node is to be given the short name e1 and must be set to use the same magic cookie as the C node, 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

Step 4. Run the C node client example. Terminate cserver, but not Erlang, and start cclient. The Erlang node must be started before the C node client:

unix> cclient

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

Step 5. Run the C node server example with long node names:

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)
([email protected])1> complex4:foo(3).
4
([email protected])2> complex4:bar(5).
10