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