Erlang/Elixir: A-C Node for External Communication

Keywords: Erlang socket github

Series:
Erlang/Elixir: External Communication-NIF
Erlang/Elixir: Port Driver for External Communication
Erlang/Elixir: A-C Node for External Communication

The C node uses Erl_Interface provided by Erlang to interact with Erlang VM, so it needs to include a header file in the C file:
#include "erl_interface.h"

Update 2016-08-14: C Node Multithread Example https://github.com/apofiget/c...

From Erlang's point of view, C node is like an ordinary Erlang node. Calling foo and bar functions in C node is to send messages to C node and receive execution results. Sending messages requires specifying a receiver, who is an Erlang process ID, or a process represented by a tuple ({RegName, Node}).

If you don't know the PID, you can send a message to the receiver in the following way.

{RegName, Node} ! Msg

Node is the name of the C node. If the node uses a short name, it must follow the naming pattern of cN, which is an integer.

C terminal

Memory needs to be initialized before calling other functions in the Erl_Interface interface.

erl_init(NULL, 0);

Now you can initialize the C node. If you use the short node name, you can initialize the node by calling erl_connect_init():

erl_connect_init(1, "secretcookie", 0);

Among them:

  • The first parameter is an integer to construct the node name, which in this case is c1

  • The second parameter is a string, which sets the value of the Cookie

  • The third parameter is an integer that identifies an instance of a C node.

If you use a long name, you need to call erl_connect_xinit() for initialization instead of erl_connect_init():

erl_connect_xinit(
    "idril", "cnode",    "cnode@idril.du.uab.ericsson.se", &addr,  "secretcookie", 0
);
----------------------------------------------------------------------------------------------
    Host Name Local Node Name Full Name Address Cookie Value Instance Number

Among them:

  • The first parameter is the host name

  • The second parameter is the local name of the node (excluding the domain name part)

  • The third parameter is the full name of the node

  • The fourth parameter is a pointer to an in_addr structure containing the host IP address.

  • The fifth parameter is the value of Cookie

  • The sixth parameter is the instance number

When setting up communication between Erlang and C, C node can act as both server and client. If it is a client, it needs to connect to Erlang node by calling erl_connect(), and after successful connection, it returns an open file descriptor:

fd = erl_connect("e1@localhost");

If the C-side runs as a server, it must first create a socket (calling bind() and listen()) to listen on a specific port. Then it publishes the name and port to epmd(Erlang Port Mapping Daemon), for more information, see manual.

erl_publish(port);

Now the C-node server can accept connections from Erlang nodes.

fd = erl_accept(listen, &conn);

The second parameter of erl_accept is an ErlConnect structure that contains connection-related information. For example, the name of the Erlang node.

Send and receive messages

C node can call erl_receive_msg() to receive messages from Erlang node. This function reads data from an open file descriptor fd and copies it to a buffer. The received messages are stored in a structured emsg named ErlMessage. The type field of ErlMessage indicates the type of message received. ERL_REG_SEND indicates that Erlang sends a message to A registered process in the C node. The actual message is an ETERM in the MSG field of the ErlMessage structure.

Node event

  • Error in ERL_ERROR

  • ERL_TICK Node Heart Rate

  • link

  • unlink

  • exit

Node heartbeat events (ERL_TICK) should be ignored or exported to the log, and error events should be handled.

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

In this example, the message body is a triple, the second element is the Pid of the caller, and the third element is the tuple {Function,Arg} to determine the function to be invoked. The execution result of the function is encapsulated in an ETERM structure and the erl_send() function is called back to the caller, which accepts three parameters. Number, respectively: file descriptor, Pid, and an item:

    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 creating function functions through ETERM must be freed (including through the erl_receive_msg() function)

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

Here is a C-node server implementation using short node names

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "erl_interface.h"
#include "ei.h"
#include "complex.h"
#include "listen.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");

  // Publish to epmd
  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);
  }else{
    printf("server is listen on: %d\n", port);
  }

  listen(listen_fd, 5);
  return listen_fd;
}

Here's a C-node server implementation using a long node name

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "erl_interface.h"
#include "ei.h"
#include "complex.h"
#include "listen.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 client code implementation of C node

#include <stdio.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "erl_interface.h"
#include "ei.h"
#include "complex.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@localhost")) < 0){
    erl_err_quit("erl_connect");
  }
  fprintf(stderr, "Connected to e1@localhost\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);
      }
    }
  }
}

The source code in this paper has been modified to comply with C99 standard.

Run this example

Following is the registration name of EPMD after two nodes start up

Start the server (short name)

./bin/c_node_server 3456

Start the Erlang node

# Enter the src directory

cd src

# Compile

erlc *.erl

# Start the node and call the function of the C node

  src erl -sname e1 -setcookie secretcookie

Eshell V7.3  (abort with ^G)
(e1@localhost)1> c_node_short:bar(4).
Result: 8
ok
(e1@localhost)2> c_node_short:bar(5).
Result: 10
ok
(e1@localhost)3>

Code base

https://github.com/developerw...

Posted by CreativityLost on Sat, 06 Apr 2019 15:30:30 -0700