檢視原始碼 Erl_Interface 使用者指南

簡介

Erl_Interface 函式庫包含可協助您整合以 C 和 Erlang 編寫的程式的函式。Erl_Interface 中的函式支援下列功能

  • 操作以 Erlang 資料類型表示的資料
  • 在 C 和 Erlang 格式之間轉換資料
  • 編碼和解碼 Erlang 資料類型以進行傳輸或儲存
  • C 節點和 Erlang 程序之間的通訊
  • 將 C 節點狀態備份和還原到 Mnesia

注意

預設情況下,Erl_Interface 函式庫僅保證與來自相同版本的 Erlang/OTP 元件相容。如需有關如何與早期版本的 Erlang/OTP 元件通訊的資訊,請參閱函式 ei_set_compat_rel

範圍

在下列章節中,將說明以下主題

  • 編譯您的程式碼以搭配 Erl_Interface 使用
  • 初始化 Erl_Interface
  • 編碼、解碼和傳送 Erlang 項
  • 建立項和模式
  • 模式匹配
  • 連線到分散式 Erlang 節點
  • 使用 Erlang Port Mapper Daemon (EPMD)
  • 傳送和接收 Erlang 訊息
  • 遠端程序呼叫
  • 使用全域名稱

先決條件

假設讀者熟悉 Erlang 程式語言。

編譯和連結您的程式碼

若要使用任何 Erl_Interface 函式,請在您的程式碼中加入下列程式碼行

#include "ei.h"

確定您的 OTP 安裝頂層目錄的位置。若要找出此位置,請啟動 Erlang 並在 Eshell 提示符號輸入下列命令

Eshell V4.7.4  (abort with ^G)
1> code:root_dir().
/usr/local/otp

若要編譯您的程式碼,請確保您的 C 編譯器知道在哪裡找到 ei.h,方法是在命令列中指定適當的 -I 引數,或將其新增至您的 Makefile 中的 CFLAGS 定義。此路徑的正確值為 $OTPROOT/lib/erl_interface-$EIVSN/include,其中

  • $OTPROOT 是以上範例中 code:root_dir/0 回報的路徑。
  • $EIVSNErl_Interface 應用程式的版本,例如 erl_interface-3.2.3

編譯程式碼

$ cc -c -I/usr/local/otp/lib/erl_interface-3.2.3/include myprog.c

當連結時

  • 指定 libei.a 的路徑,使用 -L$OTPROOT/lib/erl_interface-3.2.3/lib
  • 使用 -lei 指定函式庫的名稱。

在命令列上執行此操作,或將旗標新增至您的 Makefile 中的 LDFLAGS 定義。

連結程式碼

$ ld -L/usr/local/otp/lib/erl_interface-3.2.3/
                            lib myprog.o -lei -o myprog

在某些系統上,可能需要連結更多函式庫(例如,Solaris 上的 libnsl.alibsocket.a 或 Windows 上的 wsock32.lib)才能使用 Erl_Interface 的通訊功能。

如果您在基於 POSIX 執行緒或 Solaris 執行緒的執行緒應用程式中使用 Erl_Interface 函式,則 Erl_Interface 需要存取執行緒套件中的某些同步功能。您必須指定額外的編譯器旗標以指示您使用的套件。定義 _REENTRANT 以及 STHREADSPTHREADS。如果指定了 _REENTRANT,預設為使用 POSIX 執行緒。

初始化函式庫

在呼叫函式庫中的任何其他函式之前,請先呼叫 ei_init() 來初始化它,而且只能呼叫一次。

編碼、解碼和傳送 Erlang 項

在分散式 Erlang 節點之間傳送的資料是以 Erlang 外部格式編碼的。因此,如果您想要使用分散式協定在 C 程式和 Erlang 之間通訊,則必須將 Erlang 項編碼和解碼為位元組串流。

Erl_Interface 函式庫支援此活動。它具有數個 C 函式,可以建立和操作 Erlang 資料結構。以下範例顯示如何建立和編碼 Erlang 元組 {tobbe,3928}

ei_x_buff buf;
ei_x_new(&buf);
int i = 0;
ei_x_encode_tuple_header(&buf, 2);
ei_x_encode_atom(&buf, "tobbe");
ei_x_encode_long(&buf, 3928);

如需完整說明,請參閱 ei 模組。

建立項

可以使用 ei_x_format_wo_ver 函式來建立 Erlang 項,以簡化先前的範例

ei_x_buff buf;
ei_x_new(&buf);
ei_x_format_wo_ver(&buf, "{~a,~i}", "tobbe", 3928);

如需不同格式指令的完整說明,請參閱 ei_x_format_wo_ver 函式。

以下範例更為複雜

ei_x_buff buf;
int i = 0;
ei_x_new(&buf);
ei_x_format_wo_ver(&buf,
                   "[{name,~a},{age,~i},{data,[{adr,~s,~i}]}]",
                   "madonna",
                   21,
                  "E-street", 42);
ei_print_term(stdout, buf.buff, &i);
ei_x_free(&buf);

如同先前的範例,您有責任釋放為 Erlang 項配置的記憶體。在此範例中,ei_x_free() 確保釋放 buf 指向的資料。

連線到分散式 Erlang 節點

若要連線到分散式 Erlang 節點,您必須先使用其中一個 ei_connect_init_* 函式初始化連線常式,這些函式會儲存主機名稱和節點名稱等資訊以供稍後使用

int identification_number = 99;
int creation=1;
char *cookie="a secret cookie string"; /* An example */
const char* node_name = "einode@durin";
const char *cookie = NULL;
short creation = time(NULL) + 1;
ei_cnode ec;
ei_connect_init(&ec,
                node_name,
                cookie,
                creation);

如需更多資訊,請參閱 ei_connect 模組。

初始化之後,您可以設定與 Erlang 節點的連線。若要指定您要連線的 Erlang 節點,請使用 ei_connect_*() 函式系列。以下範例設定連線並產生有效的 socket 檔案描述符

int sockfd;
const char* node_name = "einode@durin"; /* An example */
if ((sockfd = ei_connect(&ec, nodename)) < 0)
  fprintf(stderr, "ERROR: ei_connect failed");

使用 EPMD

erts:epmd 是 Erlang Port Mapper Daemon。分散式 Erlang 節點會在本地主機上向 epmd 註冊,以向其他節點指示它們存在且可以接受連線。epmd 維護節點和埠號資訊的註冊表,當節點想要連線到另一個節點時,它會先聯絡 epmd 以尋找要連線的正確埠號。

當您使用 ei_connect 連線到 Erlang 節點時,會先建立與 epmd 的連線,如果該節點已知,則會建立與 Erlang 節點的連線。

C 節點也可以向 epmd 註冊自身,如果它們希望系統中的其他節點能夠找到並連線到它們。

在向 epmd 註冊之前,您必須先建立接聽 socket 並將其繫結至埠。然後

int pub = ei_publish(&ec, port);

pub 是現在已連線至 epmd 的檔案描述符。epmd 監視連線的另一端。如果它偵測到連線已關閉,節點將會取消註冊。因此,如果您明確關閉描述符,或您的節點失敗,它將會從 epmd 取消註冊。

請注意,在某些系統上,此機制不會偵測到失敗的節點,因為作業系統不會自動關閉節點失敗時保持開啟的描述符。如果節點以這種方式失敗,epmd 會阻止您使用舊名稱註冊新節點,因為它認為舊名稱仍在使用。在這種情況下,您必須明確關閉埠

傳送和接收 Erlang 訊息

使用下列兩個函式之一來傳送訊息

如同 Erlang,訊息可以傳送到 pid 或註冊的名稱。將訊息傳送到註冊的名稱會比較容易,因為這樣可以避免尋找合適 pid 的問題。

使用下列兩個函式之一來接收訊息

傳送訊息的範例

在以下範例中,{Pid, hello_world} 會傳送到已註冊的程序 my_server

ei_x_buff buf;
ei_x_new_with_version(&buf);

ei_x_encode_tuple_header(&buf, 2);
ei_x_encode_pid(&buf, ei_self(ec));
ei_x_encode_atom(&buf, "Hello world");

ei_reg_send(&ec, fd, "my_server", buf.buff, buf.index);

所傳送元組的第一個元素是您自己的 pid。這可讓 my_server 回覆。如需有關基本體的更多資訊,請參閱 ei_connect 模組。

接收訊息的範例

在此範例中,會接收 {Pid, Something}

erlang_msg msg;
int index = 0;
int version;
int arity = 0;
erlang_pid pid;
ei_x_buff buf;
ei_x_new(&buf);
for (;;) {
  int got = ei_xreceive_msg(fd, &msg, &x);
  if (got == ERL_TICK)
    continue;
  if (got == ERL_ERROR) {
    fprintf(stderr, "ei_xreceive_msg, got==%d", got);
    exit(1);
  }
  break;
}
ei_decode_version(buf.buff, &index, &version);
ei_decode_tuple_header(buf.buff, &index, &arity);
if (arity != 2) {
  fprintf(stderr, "got wrong message");
  exit(1);
}
ei_decode_pid(buf.buff, &index, &pid);

為了提供強固性,分散式 Erlang 節點會不時輪詢其所有連線的鄰居,以嘗試偵測失敗的節點或通訊連結。收到此類訊息的節點應立即回覆 ERL_TICK 訊息。ei_xreceive_msg() 會自動執行此操作。但是,當發生這種情況時,ei_xreceive_msg 會將 ERL_TICK 回傳給呼叫者,而不會將訊息儲存到 erlang_msg 結構中。

收到訊息後,呼叫者有責任釋放收到的訊息。

如需更多資訊,請參閱 ei_connectei 模組。

遠端程序呼叫

一個 Erlang 節點作為另一個 Erlang 節點的客戶端時,通常會發送請求並等待回覆。此類請求包含在遠端節點的函式呼叫中,稱為遠端程序呼叫 (remote procedure call)。

以下範例檢查特定的 Erlang 程序是否處於活動狀態

int index = 0, is_alive;
ei_x_buff args, result;

ei_x_new(&result);
ei_x_new(&args);
ei_x_encode_list_header(&args, 1);
ei_x_encode_pid(&args, &check_pid);
ei_x_encode_empty_list(&args);

if (ei_rpc(&ec, fd, "erlang", "is_process_alive",
           args.buff, args.index, &result) < 0)
    handle_error();

if (ei_decode_version(result.buff, &index) < 0
    || ei_decode_bool(result.buff, &index, &is_alive) < 0)
    handle_error();

有關 ei_rpc() 及其同伴 ei_rpc_to()ei_rpc_from() 的更多資訊,請參閱 ei_connect 模組。

使用全域名稱

C 節點可以存取透過 Kernel 中的 global 模組註冊的名稱。可以查找這些名稱,允許 C 節點向具名的 Erlang 服務發送訊息。C 節點也可以註冊全域名稱,允許它們向 Erlang 程序或其他 C 節點提供具名服務。

Erl_Interface 沒有提供全域服務的本機實作。相反地,它使用「附近」Erlang 節點提供的全域服務。要使用本節描述的服務,必須先開啟與 Erlang 節點的連線。

要查看有哪些名稱

char **names;
int count;
int i;

names = ei_global_names(&ec,fd,&count);

if (names)
  for (i=0; i<count; i++)
    printf("%s\n",names[i]);

free(names);

ei_global_names 會分配並傳回一個緩衝區,其中包含 Kernelglobal 模組知道的所有名稱。count 會被初始化以表示陣列中的名稱數量。名稱陣列中的字串會以 NULL 指標終止,因此不需要使用 count 來判斷何時到達最後一個名稱。

呼叫者有責任釋放此陣列。ei_global_names 使用單次呼叫 malloc() 來分配此陣列和所有字串,因此只需要 free(names) 即可。

要查找其中一個名稱

ETERM *pid;
char node[256];
erlang_pid the_pid;

if (ei_global_whereis(&ec,fd,"schedule",&the_pid,node) < 0)
   fprintf(stderr, "ei_global_whereis error\n");

如果 "schedule"Kernelglobal 模組所知,則會將 Erlang pid 寫入 the_pid。此 pid 可用於向排程服務發送訊息。此外,node 會被初始化以包含註冊服務的節點名稱,因此您可以通過簡單地將變數傳遞給 ei_connect 來與其建立連線。

在註冊名稱之前,您應該已經使用 epmd 註冊您的連接埠號碼。這並非絕對必要,但如果您忽略此步驟,其他想要與您的服務通信的節點將無法找到或連線到您的程序。

建立一個 Erlang 程序可以用來與您的服務通信的名稱

ei_global_register(fd,servicename,ei_self(ec));

註冊名稱後,使用 ei_accept 等待傳入的連線。

注意

請記得稍後使用 ei_x_free 釋放 pid

要取消註冊名稱

ei_global_unregister(&ec,fd,servicename);