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>