Erlang Nodes and C Nodes

A step by step guide for integrating C into your Erlang project

Yusuf Kasım Temel
6 min readMar 5, 2021
Image Credit: Cara Fuller

There are limited educational material about C Nodes and they are not up-to-date. So, I think this story will make your integration easier. Erlang is not a language which is created to make two or more dimensional array manipulation. On the other hand, sometimes you need efficient two or more dimensional array manipulation. It is possible to make two dimensional array manipulation by using Map data type but it is not efficient enough. Instead of that, using C nodes for two dimensional array manipulation is much more efficient and it was 7 times faster in our project. So, if you need efficiency, you have to use C. Luckily, integration is very easy using C Nodes.

Erlang Nodes

On the other hand, to understand C Nodes, we need to understand Erlang Nodes. Erlang Nodes are the runtime systems. When you call erl in terminal you create an Erlang node. Erlang Nodes can communicate through send and receive constructors. So you can communicate between nodes using pid like two process communicating in the same node. But if you want to use registered names, you have to indicate the node beside the registered process name because registered names are local to each node. You can name your nodes by -name or -sname(stands for short name) flags after erl command on terminal.

Node name with the host name can be seen in erlang shell at the beginning of every line and can be showed when you type node(). Nodes created with short name cannot communicate with the nodes created with long name. You can easily see the nodes that are currently open and connected by typing nodes() . Connection can be established by net_adm:ping(<Node@Hostname>) or by sending a message to that node. net_adm:ping function will return pong if the connection is successfully established and it will return pang if it fails.

On the other hand, you can directly send message to an Erlang node via registered name and node name together and the connection will be established automatically.

C Nodes

We can move on to C nodes because we have covered Erlang nodes at basic level. A C node is established using Erlang Interface Library in c. First, you have to initialize Erlang interface.

ei_init();

Then you can initialize your c node. If you use short names as node names, you have to name your c nodes c<n>@hostname (c1@yusufpro) where n is the number of your c node. When using long names, there is no such restriction.(http://erlang.org/documentation/doc-5.5.1/doc/tutorial/cnode.html)

ei_connect_init automatically adds hostname at the end. You can check the nodename you created with ei_thishostname function.

const char *nodename = ei_thishostname(&ec);

I really struggled here at my first try, because my C node`s hostname did not have capital cases. But my Erlang node`s hostname part did have capital cases. If you had this problem you need to change your hostname with scutil — set HostName <new hostname> in your bash shell.

After C node initialization, there are two types of connection approach you can take. The first one is using C node as client and Erlang node as server. In this case, Erlang node needs to standby while you try to connect from C node.

char *erlang_node_name = "node1@yusufpro";
int fd = ei_connect(&ec, erlang_node_name);

fd (file descriptor) value returned from the ei_connect function is reserved to be used in receiving and sending messages. If fd value is a negative integer, it means that connection attempt failed.

The second approach is using Erlang node as client and C node as server. In this approach, you should set up listen socket, publish it and accept incoming connection.

ei_accept function also returns fd. If any of the these function`s return values are less than 0, it means that those functions failed to perform. So you have to understand and correct the error before moving on.

To receive or send data, ei_x_buff data type is used. For more information about ei_x_buff data type: http://erlang.org/doc/man/ei.html#data-types. This data type consists of two fields. The first one is buff which is a char array that holds the data in external term format. For further information about external term format, visit https://erlang.org/doc/apps/erts/erl_ext_dist.html. The second one is index which is an integer that holds the length of the buff that is pointed by the buff pointer.

ei_x_buff &buff_object;
ei_x_new_with_version(&buff_object);

After creating buff object, it can be filled with encode functions in Erlang Interface Library (http://erlang.org/doc/man/ei.html#ei_encode_atom). Encoding functions returns negative integer if it can not perform the action properly. In the following example we fill buff object with

#{first=>[1,3], second=>{1,2}} this map.

To send this message to Erlang node’s registered process “worker ”, we are going to use ei_reg_send function.

ei_reg_send(&ec, fd, <process_name> , buff_object.buff, buff_object.index);

Here ec is initialized c node, fd is file descriptor you got before from the connection functions, <process_name > is the atom of registered process name, buff is the message and index is the length of the message.

As you can see, Erlang automatically decodes external term.

Normally, there should be process name and node name together to send a message. However, since normal C node does not have more than one process unlike an Erlang node, you can put any atom instead of process name.

(node1@YusufPro)1> {any,c1@YusufPro} ! #{first => [1,-1],second => {1,2}}.

To receive a message from Erlang node, we need to have an erlang_msg object that holds information about the message such as communicating pids or cookie, file description we got from connection, and ei_x_buff object we created to hold the data.

erlang_msg msg;
ei_xreceive_msg(fd, &msg, &buff_object);

ei_xreceive_msg will return a negative integer if it fails. Otherwise, it will fill buff_object’s inside.

To decode an external term in C, we need to have a decode index that holds where we are at decoding. Decode functions change decode index to the end of decoded data. To decode a buff_object that contains

#{first=>[1,-1], second=>{1,2}} this map`s external term format, we need to decode each data in the term respectively.

After we run the code and send map to the C node directly from Erlang node, we can decode. Then, to see it clearly we can print the values we got from the decoding process.

Conclusion

Erlang is very good for learning purely functional programming. However, highly complex array manipulation can be a big deal. C nodes overcome this obstacle and make the language better.

I hope this guide helps you to integrate C into your Erlang program.

Feel free to contact me if you think that i skipped something or made a mistake. Thank you for your time.

--

--