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互通
相关文章
相关标签/搜索