架構概覽
- CNode:用 C 實現的 Erlang 节點,通過
erl_interface 庫與 Erlang VM 通訊。
- 通訊協議:基於 TCP 的 Erlang Distribution Protocol。
- 優勢:隔離崩潰風險(獨立進程),適合高效能/舊系統整合。
Windows 環境建置步驟
- 安裝 Erlang (例 R16B02),取得
include、lib 目錄:
erl5.10.3\lib\erl_interface-3.7.14\include
erl5.10.3\lib\erl_interface-3.7.14\lib (需 ei_md.lib、erl_interface_md.lib)
- VS 建立 C 專案,設定:
Include 目錄加入 include
Library 目錄加入 lib
連結器輸入加入 ws2_32.lib ei_md.lib erl_interface_md.lib
字元集設為 Unicode
- 編譯生成
cnode.exe
CNode Server 端核心代碼
#include
#include
#include "erl_interface.h"
#include "ei.h"
#define BUFSIZE 1000
#define PORT 8088
int foo(int x) { return x + 1; }
int bar(int y) { return y * 2; }
int main() {
WSADATA wsa; WSAStartup(MAKEWORD(2,2), &wsa);
erl_init(NULL, 0);
struct in_addr addr; addr.s_addr = inet_addr("127.0.0.1");
if (erl_connect_xinit("idril", "cnode", "cnode@127.0.0.1", &addr, "secretcookie", 0) == -1)
erl_err_quit("erl_connect_xinit");
int listen = my_listen(PORT);
erl_publish(PORT);
int fd = erl_accept(listen, &conn);
fprintf(stderr, "Connected to %s\n", conn.nodename);
while (1) {
ErlMessage emsg; unsigned char buf[BUFSIZE];
int got = erl_receive_msg(fd, buf, BUFSIZE, &emsg);
if (got == ERL_TICK) continue;
if (got == ERL_ERROR) break;
if (emsg.type == ERL_REG_SEND) {
ETERM *fromp = erl_element(2, emsg.msg);
ETERM *tuplep = erl_element(3, emsg.msg);
ETERM *fnp = erl_element(1, tuplep);
ETERM *argp = erl_element(2, tuplep);
int res = (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) ? foo(ERL_INT_VALUE(argp))
: bar(ERL_INT_VALUE(argp));
ETERM *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);
}
}
return 0;
}
int my_listen(int port) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY;
bind(fd, (struct sockaddr*)&addr, sizeof(addr));
listen(fd, 5); return fd;
}
Erlang 客戶端測試
% 啟動 Erlang 節點
erl -name e1@127.0.0.1 -setcookie secretcookie
% 連接 CNode
net_kernel:connect('cnode@127.0.0.1'). % -> true
% 發送異步訊息
{any, 'cnode@127.0.0.1'} ! {call, self(), {bar, 3}}.
flush(). % 收到 {cnode,6}
{any, 'cnode@127.0.0.1'} ! {call, self(), {foo, 3}}.
flush(). % 收到 {cnode,4}
常見問題排查
| 現象 | 原因 | 解決 |
| 連接失敗 | epmd 未啟動 | 先啟動一個 Erlang 節點或執行 epmd.exe |
| 編譯錯誤 | Erlang 版本不匹配 | 嚴格使用構建時的 Erlang 版本 (例 R16B02) |
| Cookie 不合 | 雙方 cookie 不同 | 雙方統一 -setcookie secretcookie |
| 節點名稱衝突 | 名稱重複 | 確保 cnode@127.0.0.1 唯一 |
CNode Client 端關鍵差異
erl_connect_init(1, "secretcookie", 0);
int fd = erl_connect("e1@idril"); // 主動連接已知節點
// 後續收發迴圈與 Server 相同
參考資源