erl_interface メモ
C と Erlang ノード上のプロセスの間で、直接メッセージを送受信できる便利なライブラリ erl_interface を試したみた。
以下、メモと言うより code snippets。
pingpong.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include "erl_interface.h" #include "ei.h" extern const char *erl_thisnodename(void); extern short erl_thiscreation(void); #define SELF(fd) erl_mk_pid(erl_thisnodename(), fd, 0, erl_thiscreation()) void init(); int connect_and_get_sockfd(); void send_ping(int); void recv_pong(int); int validate_message(ETERM*); int main() { init(); int sockfd = connect_and_get_sockfd(); printf("Connect OK\n"); send_ping(sockfd); recv_pong(sockfd); erl_close_connection(sockfd); return 0; } void init() { erl_init(NULL, 0); int creation = 1; char *cookie = "foobar"; struct in_addr addr; addr.s_addr = inet_addr("0.0.0.0"); char *host = "localhost"; char *alive = "ctest"; char node[strlen(alive) + strlen(host) + 1]; sprintf(node, "%s@%s", alive, host); if (!erl_connect_xinit(host, alive, node, &addr, cookie, creation)) erl_err_quit("erl_connect_xinit failed"); } int connect_and_get_sockfd() { int sockfd; char *alive = "test"; struct in_addr addr; addr.s_addr = inet_addr("0.0.0.0"); if ((sockfd = erl_xconnect(&addr, alive)) < 0) erl_err_quit("erl_connect failed"); return sockfd; } void send_ping(int sockfd) { ETERM *arr[3], *emsg; arr[0] = erl_mk_atom("message"); arr[1] = SELF(sockfd); arr[2] = erl_format("~s", "ping"); emsg = erl_mk_tuple(arr, 3); erl_reg_send(sockfd, "pingpong", emsg); for (int i=0; i < 3; i++) erl_free_term(arr[i]); erl_free_term(emsg); } void recv_pong(int sockfd) { int rc; int size = 1024; unsigned char *buf = malloc(size); ErlMessage emsg; if ( (rc = erl_xreceive_msg(sockfd, &buf, &size, &emsg)) == ERL_MSG ) { if (!validate_message(emsg.msg)) erl_err_quit("validate error."); printf( "Message: {%s, %d, %s}\n", ERL_ATOM_PTR(erl_element(1, emsg.msg)), ERL_PID_NUMBER(erl_element(2, emsg.msg)), erl_iolist_to_string(erl_element(3, emsg.msg)) ); erl_free_term(emsg.msg); erl_free_term(emsg.to); } free(buf); } int validate_message(ETERM *message) { if (!ERL_IS_TUPLE(message)) { puts("Message is't TUPLE."); return 0; } if (erl_size(message) != 3) { puts("TUPLE size is't 3."); return 0; } if (!ERL_IS_ATOM(erl_element(1, message))) { puts("Elem of 1st in TUPLE is't ATOM."); return 0; } if (!ERL_IS_PID(erl_element(2, message))) { puts("Elem of 2nd in TUPLE is't PID."); return 0; } if (!ERL_IS_LIST(erl_element(3, message))) { puts("Elem of 3rd in TUPLE is't LIST."); return 0; } ETERM *head, *list = erl_element(3, message); while (!ERL_IS_EMPTY_LIST(list)) { head = ERL_CONS_HEAD(list); list = ERL_CONS_TAIL(list); if (ERL_IS_INTEGER(head)) continue; puts("Elem of 3rd in TUPLE is't STRING."); return 0; } return 1; }
CC=gcc ERL=/path/to/erlang/lib/erl_interface-X.X.X.X ALL : pingpong pingpong : pingpong.c $(CC) -std=c99 -o hello hello.c \ -I$(ERL)/include \ -L$(ERL)/lib -lerl_interface -lei
pingpong.erl
-module(pingpong). -compile(export_all). start() -> register(pingpong, spawn(?MODULE, loop, [])). stop() -> pingpong ! stop. loop() -> receive stop -> io:fwrite("~w:stop.~n", [self()]), exit(ok); {message, Pid, Message} -> io:fwrite("Pid:~w~nMessage:~s~n", [Pid, Message]), Pid ! {message, self(), "pong"}; Other -> io:fwrite("Other:~w~n", [Other]) end, loop().
% erl -name test@localhost -s pingpong start
% make % ./pingpong Connect OK Message: {message, 45, pong}
Inline::C を使って perl から使える事を確認した。後でそっちの module もさらす。
さらした -> id:cooldaemon:20080214