檢視原始碼 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
回報的路徑。$EIVSN
是Erl_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.a
和 libsocket.a
或 Windows 上的 wsock32.lib
)才能使用 Erl_Interface
的通訊功能。
如果您在基於 POSIX 執行緒或 Solaris 執行緒的執行緒應用程式中使用 Erl_Interface
函式,則 Erl_Interface
需要存取執行緒套件中的某些同步功能。您必須指定額外的編譯器旗標以指示您使用的套件。定義 _REENTRANT
以及 STHREADS
或 PTHREADS
。如果指定了 _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_connect
和 ei
模組。
遠端程序呼叫
一個 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
會分配並傳回一個緩衝區,其中包含 Kernel
中 global
模組知道的所有名稱。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"
為 Kernel
中 global
模組所知,則會將 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);