erlang與c

概述

  • 當前erlangVM與外部世界互通三種模型(NIF、端口驅動、C節點)
    • NIF與端口驅動相似優勢效率高,缺點一旦崩潰整個erlangVM也就崩潰
    • C節點效率略低(基於rpc機制),但獨立,可與erlang節點實現無縫rpc:call,rpc:async_call調用互通
  • c\c++做爲大多數的語言宿主環境及其自身優點使C節點模型成爲某些場景下不錯的混搭選擇

rpc

  • async_call/4其實是調用call/4,本質上它們相同
  • call/4流程與數據封裝結構:
    • rpc:call/4封裝傳遞 do_call(N, {call,M,F,A,group_leader()}, infinity)
    • rpc:do_call/3透傳 gen_server:call({?NAME,Node}, {call,M,F,A,group_leader()}, infinity)
    • gen_server:call/3透傳 gen:call(Name, '$gen_call', {call,M,F,A,group_leader()}, Timeout)
    • gen:call最終到gen:do_call/4
  • gen:do_call/4分析:
    • 根據流程最終進行遠程rpc調用都是經過VM的erlang:send方法
    • 發送數據結構封裝成三元組 {'$gen_call', {pid(), reference()}, {call,M,F,A,pid()}}
    • 上面group_leader()實際上是一個pid()
    • erlang:send以後就阻塞等待,這裏須要erl_interface配合返回
    receive
    	{Mref, Reply} ->		
    		    erlang:demonitor(Mref, [flush]),
    		    {ok, Reply};

epmd

  • epmd是(Erlang Port Mapper Daemon)的縮寫,每臺物理機上啓動惟一一個該進程用於記錄/交換cluster上的每一個進程的節點/IP/端口映射信息而且local epmd老是監聽4369端口
  • 啓動beam.smp都會監聽某個端口而且向local epmd註冊自身映射信息,當rpc遠程時會查詢beam.smp中是否已經鏈接若是已經存在則複用,否者向目標的epmd查詢目標端口映射信息,而後beam.smp直接經過tcp/ip與目標創建socket(全雙工)連接
  • 注意local epmd是相對的,更多詳情可閱讀net_kernel模塊

erl_interface

  • 以C形式提供的整套模擬erlang節點間數據交互、解析、構建以及收發方法集詳情
  • ErlMessage,ETERM兩個重要結構體:
    •  
    typedef struct { int type; 消息類型'ERL_REG_SEND' ETERM *msg; 此消息數據地址指針 ETERM *from; 此消息來自節點名指針 ETERM *to; 此字段接受時候無效NULL char to_name[];
    } ErlMessage
    * ```
    typedef struct {
    		union {
      	   Erl_Integer    ival;
      		Erl_Uinteger   uival; 
      		Erl_LLInteger  llval;
      		Erl_ULLInteger ullval;
      		Erl_Float      fval;
      		Erl_Atom       aval;
      		Erl_Pid        pidval;     
      		Erl_Port       portval;    
      		Erl_Ref        refval;   
      		Erl_List       lval;
      		Erl_EmptyList  nval;
      		Erl_Tuple      tval;
      		Erl_Binary     bval;
      		Erl_Variable   vval;
      		Erl_Function   funcval;
      		Erl_Big        bigval;
    		} uval;

} ETERMhtml

* **`erl_interface.h`,`ei.h`幾個重要函數:**
	* ```ErlMessage emsg = {}```  
	```erl_receive_msg(fd, 0, 0, &emsg)```   
	emsg.msg裏面存放的結構就是上面{'$gen_call', {pid(), reference()}, {call,M,F,A,pid()}}三元組,若是這裏是進行響應遠程節點的rpc:call的話返回須要構建{reference(), data}使用erl_send進行反饋給遠程節點
	* ```erl_element(2, emsg.msg)```   
	取元組emsg.msg中第2個位置上的ETERM*對象地址,注意查看C\C++源碼發現使用此方法以後就從元組中抽象移除了這個地址的遍歷指引,因此只要使用erl_element函數取出來的臨時指針對象都必須經過erl_free_term釋放,未取的不須要顯示調用刪除由於刪除元組會遍歷它全部子元素。這種相似於淺拷貝應用範疇     	
	* ```erl_free_term(emsg.from) ```   
	```erl_free_term(emsg.msg)```  
	這兩行釋放必須存在  
	* rpc互通的數據最好是經過binary或者json可進行序列化,反序列化封裝與解析
* **** 	
## C節點(client)
* ```
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "erl_interface.h"
#include "ei.h"
int main(int argc, char **argv) {
  int fd;
  int loop = 1;
  int got;
  ErlMessage emsg;
  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, "be3", 0) == -1){
      erl_err_quit("erl_connect_xinit");
  }
  if ((fd = erl_connect("temp@10.0.1.85")) < 0){
      erl_err_quit("temp@10.0.1.85");
  }
  fprintf(stderr, "Connected to temp@10.0.1.85\n\r");
  while (loop) {
      got = erl_receive_msg(fd, 0, 0, &emsg);
      if (got == ERL_TICK) {
      } else if (got == ERL_ERROR) {
          loop = 0;
      } else {
          if (emsg.type == ERL_REG_SEND) {
              ETERM * fromp  = erl_element(2, emsg.msg);
              ETERM * tuplep = erl_element(3, emsg.msg);
              ETERM * call = erl_element(1, tuplep);
              ETERM * mod    = erl_element(2, tuplep);
              ETERM * func   = erl_element(3, tuplep);
              ETERM * arg = erl_element(4, tuplep);
              
              /* print */
              printf("遠程rpc:%s %s:%s\r\n", ERL_ATOM_PTR(call), ERL_ATOM_PTR(mod), ERL_ATOM_PTR(func));
              
              /* 消息發送着進程pid(),與接收引用ref() */
              ETERM* from = erl_element(1, fromp);
              ETERM* mref = erl_element(2, fromp);
              ETERM* resp = erl_format("{~w, ~i}", mref, 50001);
              erl_send(fd, from, resp);
              
              erl_free_term(emsg.from);
              erl_free_term(emsg.msg);
              
              erl_free_term(fromp);
              erl_free_term(tuplep);
              erl_free_term(call);
              erl_free_term(mod);
              erl_free_term(func);
              erl_free_term(arg);
              erl_free_term(from);
              erl_free_term(mref);
              erl_free_term(resp);
          }
      }
  }
}
  • 編譯須要設置本身的include和lib路徑
    /usr/local/Cellar/erlang/18.3/lib/erlang/lib/erl_interface-3.8.2/include
    /usr/local/Cellar/erlang/18.3/lib/erlang/lib/erl_interface-3.8.2/lib
    依賴libei.a,liberl_interface.a
  • 測試時候須要保證物理機epmd和須要鏈接的erlang節點處於開啓狀態 /usr/local/Cellar/erlang/18.3/bin/erl -setcookie be3 -name temp@10.0.1.85
    rpc:call('cnode@127.0.0.1', mod, func, [0]).
    	50001
    	(temp@10.0.1.85)3>

總結

  • 上面只是簡單演示C\C++節點工做原理,應用級別還須要使用線程或者協程來來進行rpc接收以及多節點rpc管理封裝等等
  • 可嘗試使用golang+cgo實現erlang節點與golang系統級別進程rpc互通
相關文章
相關標籤/搜索