热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Erlang/Elixir:外部通信之C节点

系列:ErlangElixir:外部通信之-NIFErlangElixir:外部通信之-端口驱动ErlangElixir:外部通信之-C节点C节点使用Erlang提供的Erl_In

系列:
Erlang/Elixir: 外部通信之-NIF
Erlang/Elixir: 外部通信之-端口驱动
Erlang/Elixir: 外部通信之-C节点

C节点使用Erlang提供的 Erl_Interface 与 Erlang VM进行交互, 因此需要在C文件中包含头文件:
#include "erl_interface.h"

2016-08-14 更新: C节点多线程示例 https://github.com/apofiget/c…

从Erlang的角度看, C节点就像一个普通的Erlang节点一样. 调用C节点中的foobar函数就是给C节点发送消息, 并接收执行结果. 发送消息要求指定一个接收者, 接收者是一个Erlang进程ID, 或由一个元组({RegName, Node})表示的进程.

如果不知道PID, 那么可以通过下面的方式给接收者发送消息.

{RegName, Node} ! Msg

节点名称 Node 为C节点的名称. 如果节点使用短名称, 必须遵循 cN这种命名模式, N为整数

C端

在调用 Erl_Interface接口中的其他函数前, 需要初始化内存.

erl_init(NULL, 0);

现在就可以初始化C节点了. 如果使用短节点名称, 通过调用 erl_connect_init() 完成节点的初始化:

erl_connect_init(1, "secretCOOKIE", 0);

其中:

  • 第一个参数为整数, 用于构造节点名称, 此例中节点名称为 c1

  • 第二个参数为字符串, 用于设置COOKIE的值

  • 第三个参数为一个整数, 用于标识一个C节点实例.

如果使用长名称, 需要调用 erl_connect_xinit() 进行初始化, 而不是 erl_connect_init():

erl_connect_xinit(
"idril", "cnode", "cnode@idril.du.uab.ericsson.se", &addr, "secretCOOKIE", 0
);
----------------------------------------------------------------------------------------------
主机名称 本地节点名称 全称 地址 COOKIE值 实例编号

其中:

  • 第一个参数为主机名称

  • 第二个参数为节点本地名称(不包含域名部分)

  • 第三个参数为节点的全称

  • 第四个参数为一个指针, 指向一个包含该主机IP地址的 in_addr 结构.

  • 第五个参数为COOKIE的值

  • 第六个参数为实例编号

C节点在设置Erlang和C之间的通信的时候, 既可以作为服务器端, 也可以作为客户端. 如果作为客户端, 需要通过调用 erl_connect() 连接到Erlang节点, 成功连接后, 返回一个打开的文件描述符:

fd = erl_connect("e1@localhost");

如果C端作为一个服务器运行, 它必须首先创建一个套接字(调用bind()listen())来监听特定的端口. 然后把名称和端口发布到epmd(Erlang端口映射守护进程), 详细信息请参考 手册.

erl_publish(port);

现在C节点服务器可以接受来自Erlang节点的连接了.

fd = erl_accept(listen, &conn);

erl_accept 的第二个参数为一个 ErlConnect 结构, 包含连接相关的信息. 例如, Erlang节点的名字.

收发消息

C节点可以调用 erl_receive_msg() 接收来自 Erlang节点的消息. 该函数从一个打开的文件描述符fd中读取数据, 并复制到一个缓冲区(Buffer)中, 接收的消息被存放在名为 ErlMessage 的结构 emsg 中. ErlMessage 的 type 字段表明接收的消息的类型. ERL_REG_SEND 指出, Erlang发送了一条消息到C节点中的一个已注册进程. 实际的消息是一个 ETERM, 在 ErlMessage结构的 msg 字段中.

节点事件

  • ERL_ERROR 发生了错误

  • ERL_TICK 节点心跳

  • link

  • unlink

  • exit

节点心跳事件(ERL_TICK)应该被忽略或输出到日志, 错误事件应该被处理

while (loop) {
got = erl_receive_msg(fd, buf, BUFSIZE, &emsg);
if (got == ERL_TICK) {
/* ignore */
} else if (got == ERL_ERROR) {
loop = 0; /* exit while loop */
} else {
if (emsg.type == ERL_REG_SEND) {

因为消息是一个 ETERM 结构, Erl_Interface 接口中的函数操作. 在这个例子中, 消息体为一个三元组,第二个元素为调用者的pid,第三个元素为元组 {Function,Arg}, 用于决定要调用的函数. 函数的执行结果被封装成一个 ETERM 结构并调用 erl_send() 函数返回给调用者, 它接受三个参数, 分别是: 文件描述符, Pid, 以及一个项式:

fromp = erl_element(2, emsg.msg);
tuplep = erl_element(3, emsg.msg);
fnp = erl_element(1, tuplep);
argp = erl_element(2, tuplep);
if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) {
res = foo(ERL_INT_VALUE(argp));
} else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) {
res = bar(ERL_INT_VALUE(argp));
}
resp = erl_format("{cnode, ~i}", res);
erl_send(fd, fromp, resp);

最后, 通过 ETERM 创建函数函数分配的内存必须被释放(包括通过erl_receive_msg()函数创建的)

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);

下面是一个使用短节点名称的C节点服务器实现

#include
#include
#include
#include
#include
#include
#include
#include "erl_interface.h"
#include "ei.h"
#include "complex.h"
#include "listen.h"
#define BUFSIZE 1000
int main(int argc, char **argv) {
int port; /* Listen port number */
int listen; /* Listen socket */
int fd; /* fd to Erlang node */
ErlConnect conn; /* Connection data */
int loop = 1; /* Loop flag */
int got; /* Result of receive */
unsigned char buf[BUFSIZE]; /* Buffer for incoming message */
ErlMessage emsg; /* Incoming message */
ETERM *fromp, *tuplep, *fnp, *argp, *resp;
int res;
port = atoi(argv[1]);
erl_init(NULL, 0);
if (erl_connect_init(1, "secretCOOKIE", 0) == -1)
erl_err_quit("erl_connect_init");
/* Make a listen socket */
if ((listen = my_listen(port)) <= 0)
erl_err_quit("my_listen");
// Publish to epmd
if (erl_publish(port) == -1){
erl_err_quit("erl_publish");
}
if ((fd = erl_accept(listen, &conn)) == ERL_ERROR){
erl_err_quit("erl_accept");
}
fprintf(stderr, "Connected to %s\n\r", conn.nodename);
while (loop) {
got = erl_receive_msg(fd, buf, BUFSIZE, &emsg);
if (got == ERL_TICK) {
/* ignore */
} else if (got == ERL_ERROR) {
loop = 0;
} else {
if (emsg.type == ERL_REG_SEND) {
fromp = erl_element(2, emsg.msg);
tuplep = erl_element(3, emsg.msg);
fnp = erl_element(1, tuplep);
argp = erl_element(2, tuplep);
if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) {
res = foo(ERL_INT_VALUE(argp));
} else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) {
res = bar(ERL_INT_VALUE(argp));
}
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);
}
}
} /* while */
}
int my_listen(int port) {
int listen_fd;
struct sockaddr_in addr;
int on = 1;
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) <0){
return (-1);
}
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset((void*) &addr, 0, (size_t) sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) <0){
return (-1);
}else{
printf("server is listen on: %d\n", port);
}
listen(listen_fd, 5);
return listen_fd;
}

下面是一个使用长节点名的C节点服务器实现

#include
#include
#include
#include
#include
#include
#include
#include "erl_interface.h"
#include "ei.h"
#include "complex.h"
#include "listen.h"
#define BUFSIZE 1000
int main(int argc, char **argv) {
struct in_addr addr; /* 32-bit IP number of host */
int port; /* Listen port number */
int listen; /* Listen socket */
int fd; /* fd to Erlang node */
ErlConnect conn; /* Connection data */
int loop = 1; /* Loop flag */
int got; /* Result of receive */
unsigned char buf[BUFSIZE]; /* Buffer for incoming message */
ErlMessage emsg; /* Incoming message */
ETERM *fromp, *tuplep, *fnp, *argp, *resp;
int res;
port = atoi(argv[1]);
erl_init(NULL, 0);
addr.s_addr = inet_addr("134.138.177.89");
if (erl_connect_xinit("idril", "cnode", "cnode@idril.du.uab.ericsson.se",
&addr, "secretCOOKIE", 0) == -1)
erl_err_quit("erl_connect_xinit");
/* Make a listen socket */
if ((listen = my_listen(port)) <= 0)
erl_err_quit("my_listen");
if (erl_publish(port) == -1)
erl_err_quit("erl_publish");
if ((fd = erl_accept(listen, &conn)) == ERL_ERROR)
erl_err_quit("erl_accept");
fprintf(stderr, "Connected to %s\n\r", conn.nodename);
while (loop) {
got = erl_receive_msg(fd, buf, BUFSIZE, &emsg);
if (got == ERL_TICK) {
/* ignore */
} else if (got == ERL_ERROR) {
loop = 0;
} else {
if (emsg.type == ERL_REG_SEND) {
fromp = erl_element(2, emsg.msg);
tuplep = erl_element(3, emsg.msg);
fnp = erl_element(1, tuplep);
argp = erl_element(2, tuplep);
if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) {
res = foo(ERL_INT_VALUE(argp));
} else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) {
res = bar(ERL_INT_VALUE(argp));
}
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);
}
}
}
}
int my_listen(int port) {
int listen_fd;
struct sockaddr_in addr;
int on = 1;
if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) <0)
return (-1);
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset((void*) &addr, 0, (size_t) sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) <0)
return (-1);
listen(listen_fd, 5);
return listen_fd;
}

最后是C节点客户端代码实现

#include
#include
#include
#include
#include
#include "erl_interface.h"
#include "ei.h"
#include "complex.h"
#define BUFSIZE 1000
int main(int argc, char **argv) {
int fd; /* fd to Erlang node */
int loop = 1; /* Loop flag */
int got; /* Result of receive */
unsigned char buf[BUFSIZE]; /* Buffer for incoming message */
ErlMessage emsg; /* Incoming message */
ETERM *fromp, *tuplep, *fnp, *argp, *resp;
int res;
erl_init(NULL, 0);
if (erl_connect_init(1, "secretCOOKIE", 0) == -1){
erl_err_quit("erl_connect_init");
}
if ((fd = erl_connect("e1@localhost")) <0){
erl_err_quit("erl_connect");
}
fprintf(stderr, "Connected to e1@localhost\n\r");
while (loop) {
got = erl_receive_msg(fd, buf, BUFSIZE, &emsg);
if (got == ERL_TICK) {
/* ignore */
} else if (got == ERL_ERROR) {
loop = 0;
} else {
if (emsg.type == ERL_REG_SEND) {
fromp = erl_element(2, emsg.msg);
tuplep = erl_element(3, emsg.msg);
fnp = erl_element(1, tuplep);
argp = erl_element(2, tuplep);
if (strncmp(ERL_ATOM_PTR(fnp), "foo", 3) == 0) {
res = foo(ERL_INT_VALUE(argp));
} else if (strncmp(ERL_ATOM_PTR(fnp), "bar", 3) == 0) {
res = bar(ERL_INT_VALUE(argp));
}
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);
}
}
}
}

本文中的源码经过修改, 遵循C99标准.

运行这个例子

《Erlang/Elixir: 外部通信之-C节点》

下面是两个节点启动后, EPMD的注册名称

《Erlang/Elixir: 外部通信之-C节点》

启动服务器(短名称)

./bin/c_node_server 3456

启动Erlang节点

# 进入src目录
cd src
# 编译
erlc *.erl
# 启动节点并调用C节点的函数
➜ src erl -sname e1 -setCOOKIE secretCOOKIE
Eshell V7.3 (abort with ^G)
(e1@localhost)1> c_node_short:bar(4).
Result: 8
ok
(e1@localhost)2> c_node_short:bar(5).
Result: 10
ok
(e1@localhost)3>

代码库

https://github.com/developerw&#8230;


推荐阅读
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 关于我们EMQ是一家全球领先的开源物联网基础设施软件供应商,服务新产业周期的IoT&5G、边缘计算与云计算市场,交付全球领先的开源物联网消息服务器和流处理数据 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • 在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的步骤和方法
    本文介绍了在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的详细步骤和方法。首先需要下载最新的Java SE Development Kit 9发行版,然后按照给出的Shell命令行方式进行安装。详细的步骤和方法请参考正文内容。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
author-avatar
爱辰teg_911
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有