• 第十二章 接口技术


    Erlang运行第三方代码时需要一个与Erlang运行时系统相互独立的外部程序, 两者通过二进制通道进行通信。在Erlang中是通过端口连接进程来作为中间人管理两者之间的通信。

    12.1 端口

    创建端口

    Port = open_port(PortName, PortSettings)
    

    发送数据

    Port ! {PidC, {command, Data}}
    

    改变连接进程的PID

    Port ! {PidC, connect, Pid1}
    

    关闭端口

    Port ! {PidC, close}
    

    为一个外部C程序添加接口
    为Erlang与C的程序调用实现一个通信协议:

    1. 数据包前两个字节表示数据的长度Len, 之后跟上长度为Len的数据
    2. 调用第一个函数twice(需要一个参数x), 则数据格式为[1, N];
    3. 调用第二个函数sum(需要两个参数x, y),则数据格式为[2, M, N]
    4. 返回值规定为一个字节

    12.2.1 C程序

    1. example1.c
      包含具体调用的函数
    int twice(int x) {
        return 2*x;
    }
    int sum(int x, int y) {
        return x+y;
    }
    
    1. example_driver.c
      实现字节流协议并调用函数
    #include <stdio.h>
    typedef unsigned char byte;
    
    int read_cmd(byte *buff);
    int write_cmd(byte *buff, int len);
    
    int main() {
        int fn, arg1, arg2, result;
        byte buff[100];
    
        /* 在无限循环中读取输入, 按照通信协议进行解析处理 */ 
        while (read_cmd(buff) > 0) {
            fn = buff[0];
    
            if (fn == 1) {
                arg1 = buff[1];
                result = twice(arg1);
            } else if (fn == 2) {
                arg1 = buff[1];
                arg2 = buff[2];
                result = sum(arg1, arg2);
            }
    
            buff[0] = result;
            write_cmd(buff, 1);
        }
    }
    
    1. erl_comm.c
      读写内存缓冲中的数据
    #include <unistd.h>
    typedef unsigned char byte;
    
    int read_cmd(byte *buff);
    int write_cmd(byte *buff, int len);
    int read_exact(byte *buff, int len);
    int write_exact(byte *buff, int len);
    
    /* 首先读取两个字节的包头, 然后根据其值(数据包长度)继续读取 */
    int read_cmd(byte *buff) {
        int len;
        if (read_exact(buff, 2) != 2) {
            return -1;
        }
        len = (buff[0] << 8) | buff[1];
        return read_exact(buff, len);
    }
    
    /* 依次写入运行结果:两字节的包头+一字节计算结果 */
    int write_cmd(byte *buff, int len) {
        byte li;
        li = (len >> 8) & 0xff;
        write_exact(&li, 1);
        li = len & 0xff;
        write_exact(&li, 1);
    
        return write_exact(buff, len);
    }
    
    /* 从标准输入中读取指定长度的数据 */
    int read_exact(byte *buff, int len) {
        int i, got = 0;
        do {
            if ((i == read(0, buff+got, len-got)) <= 0) {
                return i;
            }
            got += i;
        } while (got < len);
        return len;
    }
    
    /* 向标准输出中写入指定长度的数据 */
    int write_exact(byte *buff, int len) {
        int i, wrote = 0;
        do {
            if ((i == write(1, buff+wrote, len-wrote)) <= 0) {
                return i;
            }
            wrote += 1;
        } while (wrote < len);
        return len;
    }
    

    12.2.2 Erlang程序

    -module(example1).
    -export([start/0, stop/0]).
    -export([twice/1, sum/2]).
    
    %% 创建一个注册名为example1的进程
    %% 使用open_port创建端口, 指明数据包需要两字节长的包头 
    %% 使用loop等待消息处理 
    start() ->
        spawn(fun() ->
                      register(example1, self()),
                      process_flag(trap_exit, true),
                      Port = open_port({spawn, "./example1"}, [{packet, 2}]),
                      loop(Port)
              end).
    stop() ->
        example1 ! stop.
    
    twice(X)  ->call_port({twice, X}).
    sum(X, Y) ->call_port({sum, X, Y}).
    
    call_port(Msg) ->
        example1 ! {call, self(), Msg},
        receive
            {example1, Result} ->
                Result
        end.
    
    loop(Port) ->
        receive
            {call, Caller, Msg} ->
                Port ! {self(), {command, encode(Msg)}},
                receive
                    {Port, {data, Data}} ->
                        Caller ! {example1, decode(Data)}
                end,
                loop(Port);
            stop ->
                Port ! {self(), close},
                receive
                    {Port, closed} ->
                        exit(normal)
                end;
            {'EXIT', Port, Reason} ->
                exit({port_terminated, Reason})
        end.
    
    encode({twice, X})  ->{1, X};
    encode({sum, X, Y}) ->{2, X, Y}.
    
    decode([Int]) ->Int.
    

    运行结果:

    1> example1:start().
    <0.34.0>
    2> example1:sum(45, 32).
    77
    3> example1:twice(10).  
    20
    4> example1:stop().
    stop
    

    12.3 open_port

    open_port函数的标准形式:

    open_port(PortName, [Opt]) -> Port
    

    其中
    PortName
    可以是如下形式:

    1. {spawn, Command}
    2. {fd, In, Out}
      Opt
    3. {packet, N}
      以N字节长度的数据作为包的长度计数
    4. stream
      不包含数据包长度
    5. {line, Max}
      基于行发送消息, 超过Max字节则折行
    6. {cd, Dir}
      只针对当PortName为{spawn, Command}方式时有效, 即使外部程序在Dir目录中启动
    7. {env, Env}
      只针对当PortName为{spawn, Command}方式时有效, 即为外部程序配置运行参数

    12.4 内联驱动

    把第三方程序编译成共享库, 然后动态的内联到Erlang的运行时系统中称为内联驱动方式。创建内联驱动程序是Erlang与其它语言对接的最有效方式, 但因为内联驱动程序的任何错误都会导致Erlang系统崩溃, 因此这种方式比较危险。

    1. example1_lid.erl
    %% 相比于example1.erl只是多了加载共享库的过程
    start() ->
        start("example1_drv").
    
    start(SharedLib) ->
        case erl_ddll:load_driver(".", SharedLib) of
        ok ->ok;
        {error, already_loaded} ->ok;
        _ ->exit({error, could_not_load_driver})
        end,
        spawn(fun() ->init(SharedLib) end).
    
    init(SharedLib) ->
        register(example1_lid, self()),
        Port = open_port({spawn, SharedLib}, []),
        loop(Port).
    
    1. example1_lid.c
    /* example1_lid.c */
    
    #include <stdio.h>
    #include <erl_driver.h>
    
    typedef struct {
        ErlDrvPort port;
    } example_data;
    
    static ErlDrvData example_drv_start(ErlDrvPort port, char *buff)
    {
        example_data* d = (example_data*)driver_alloc(sizeof(example_data));
        d->port = port;
        return (ErlDrvData)d;
    }
    
    static void example_drv_stop(ErlDrvData handle)
    {
        driver_free((char*)handle);
    }
    
    static void example_drv_output(ErlDrvData handle, char *buff, int bufflen)
    {
        example_data* d = (example_data*)handle;
        char fn = buff[0], arg = buff[1], res;
        if (fn == 1) {
          res = twice(arg);
        } else if (fn == 2) {
          res = sum(buff[1], buff[2]);
        }
        driver_output(d->port, &res, 1);
    }
    
    ErlDrvEntry example_driver_entry = {
        NULL,               /* F_PTR init, N/A */
        example_drv_start,  /* L_PTR start, 端口打开时调用 */
        example_drv_stop,   /* F_PTR stop, 端口关闭时调用 */
        example_drv_output, /* F_PTR output, 发送数据时调用 */
        NULL,               /* F_PTR ready_input, 
                               输入设备就绪时调用 */
        NULL,               /* F_PTR ready_output, 
                               输出设备就绪时调用 */
        "example1_drv",     /* char *driver_name,  驱动名称, 调用open_port函数时使用 */
        NULL,               /* F_PTR finish, 卸载驱动时调用 */
        NULL,               /* F_PTR control, 端口回调 */
        NULL,               /* F_PTR timeout, 保留 */
        NULL                /* F_PTR outputv, 保留 */
    };
    
    DRIVER_INIT(example_drv) /* must match name in driver_entry */
    {
        return &example_driver_entry;
    }
    

    运行结果:

    5> c(example1_lid).
    {ok,example1_lid}
    6> example1_lid:start().
    <0.35.0>
    7> example1_lid:sum(45, 32).
    77
    8> example1_lid:twice(10).  
    20
    9> example1_lid:stop().
    stop
    

    12.5 注意
    Erlang提供了几个与第三方语言通信的库。
    Erl接口(ei), 针对C, 文档链接更新至:
    http://www.erlang.org/doc/apps/erl_interface/erl_interface.pdf
    Jinteface, 针对Java, 文档链接更新至:
    http://www.erlang.org/doc/apps/jinterface/jinterface.pdf

  • 相关阅读:
    html例题——简历
    求值
    c#语句实例(排大小)
    3.6语言基础笔记
    2016.3.5进制间的转换
    3.26-1
    3.23(网页)
    3.23
    3.22
    3.20
  • 原文地址:https://www.cnblogs.com/KylinBlog/p/13536519.html
Copyright © 2020-2023  润新知