On this page
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
© 2010–2017 Ericsson AB
Licensed under the Apache License, Version 2.0.