Erlang -- C Nodes

Interoperability Tutorial

User's Guide

Version 10.7

- User's Guide
- PDF
- Top

- Expand All
- Contract All

### Chapters

- Introduction
  - Top of chapter
  - Purpose
  - Prerequisites
- Overview
  - Top of chapter
  - Built-In Mechanisms
  - C and Java Libraries
  - Standard Protocols
  - IC and CORBA
  - Old Applications
- Problem Example
  - Top of chapter
  - Description
- Ports
  - Top of chapter
  - Erlang Program
  - C Program
  - Running the Example
- Erl_Interface
  - Top of chapter
  - Erlang Program
  - C Program
  - Running the Example
- Port Drivers
  - Top of chapter
  - Erlang Program
  - C Driver
  - Running the Example
- C Nodes
  - Top of chapter
  - Erlang Program
  - C Program
  - Running the Example
- NIFs
  - Top of chapter
  - Erlang Program
  - NIF Library Code
  - Running the Example

# 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 cnode@idril.ericsson.se.

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, 'cnode@idril.du.uab.ericsson.se'} ! {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", "cnode@idril.ericsson.se",
                      &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", "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;
    }

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)
    (e1@idril.du.uab.ericsson.se)1> complex4:foo(3).
    4
    (e1@idril.du.uab.ericsson.se)2> complex4:bar(5).
    10