zoukankan      html  css  js  c++  java
  • erlang与c之间的连接

    http://blog.chinaunix.net/uid-22566367-id-382012.html

    erlang与c之间的连接
    参考资料:网络资料
    作者:Sunny
        在Programming Erlang书上的第十二章中实现了elang与c语言之间的连接,本人觉得那个例子还是有点复杂,在此,本人举一个更简单的例子。而且在那本书上的Page 161第7行,有一个错误。
    书上写的是:
        Port ! {PidC, {connect, Pid1}}
        把端口连接进程PID从Pid1改为PidC
    他把这句话写反了(也许是翻译的问题),应为:把端口连接进程PID从PidC改为Pid1。

    下面开始我们的程序。
    预备知识:
    (一)、一般而言,A语言要和B语言进行连接的话,有两种方式。
    1.把B语言的可执行二进制代码拷贝到A程序当中,定义标准的参数调用接口,从而B程序执行的结果返回到A程序当中。
    2.启用两个独立的进程,A进程可以和B进程之间通信,以此来实现A和B的连接。
    当然,我们使用的Eralng语言是采用第二中方式进行的。这样做主要有这方面的考虑:我们不可能保证在erlang之外的程序正常运行,一旦外部程序崩溃,那么端口连接进程就会收到一个退出信号,相应的,如果端口连接进程消亡,那么外部程序也会崩溃的。除此之外,erlang外部程序是操作系统来运行的,不会导致erlang系统的崩溃。
    (二)、要创建一个端口,我们可以用这个命令。
        Port = open_port(PortName, PortSettings).
    每个端口必须绑定到一个 Erlang 进程中,一般来讲,创建 Erlang
    端口的进程即为端口连接进程。创建号端口后,你就可以向该端口发送数据,或者从端口读取数据。
    open_port()使用详解:
    @spec open_port(PortName, [Opt]) -> Port
    PortName是如下之一:
    {spawn,Command}
    启动一个扩展程序。Command是扩展程序的名字。Command会在Erlang的工作空间以外工作,除非找到了叫做Command的内联驱动。
    {fd,In,Out}
    允许Erlang进程存取一个已经打开的文件描述符。文件描述符 “In” 用作stdin,而文件描述符 “Out” 用作stdout。
    Opt是如下之一:
    {packet,N}
    包前面加上N(1,2,4)字节长度的长度包头。
    stream
    消息不是按照包长度发送的。应用自己知道如何处理这些包。
    {line,Max}
    以行为单位传递消息。如果一行大于Max字节,则会切割为只有Max字节。
    {cd,Dir}
    仅用于 {spawn,Command} 选项,扩展程序的初始目录为Dir。
    {env,Env}
    仅用于 {spawn,Command} 选项。指定扩展程序可用的环境变量为Env。Env是一个列表的 {VarName,Value} 对。两个变量都是字符串。
    这并不是完整的 open_port 参数列表。我们可以在参考手册的erlang模块里找到详细的描述。、
    这里举一个例子来说明一下这个函数的使用:
    eg:     open_port({spawn, "./c_fun2.exe"}, [{packet, 2}]).
    解释:spawn 基本上是固定的关键字,第二项是对应的 exe 文件的路径,最后一项[{packet, 2}]是告诉 erlang端口驱动,发送数据时,给数据头部增加一个长度标记,长度用两个字节表示。
    (三)、向端口进程发送消息。
        Port ! {PidC, {command, Data}}
    (四)、改变端口绑定的进程(就是我在前面提到的书中的那个错误)。
        Port ! {PidC, {connect, Pid1}}
        说明:把端口连接进程的PID从PidC改为Pid1.
    (五)、关闭端口。
        Port ! {PidC, close}
    注意:在上面这些使用当中注意列表和元组的区别。列表适用于参数个数不变的情况,而元组的参数个数是可以变化的。

    预备知识到此结束。不过,还有一个问题,open_port()函数将进程和端口已经绑定了,为什么在向端口发送消息的时候(    Port ! {PidC, {connect, Pid1}}),还使用了进程的PidC?我是这么考虑的,也许是为了方便端口切换绑定的进程(Port ! {PidC, {connect, Pid1}})。
    不知道这样对不对?

    注:这个测试程序不是本人所写,是来自网络的。
    程序开始:
    //echo.c
    #include <stdio.h>
    #include <unistd.h>
    typedef unsigned char byte;
    typedef char int8;
    int8 read_exact(byte* buf, int8 len);
    int8 write_exact(byte* buf, int8 len);
    int main() {
        FILE * fp;
        fp = fopen("ports.log", "w");
        fprintf(fp, "start... ");
        fflush(fp);
        byte buf[256]={0};
        while(read_exact(buf, 1)==1)
        {
            int8 len = buf[0];
            if(read_exact(buf, len)<=0) return -1;
            fprintf(fp, "buf:[%s],len:[%d] ", buf, len);
            fflush(fp);
            if(write_exact(&len, 1)<=0) return -1;
            if(write_exact(buf, len)<=0) return -1;
        }
        fprintf(fp, "end... ");
        fclose(fp);
        return 0;
    }
    int8 read_exact(byte* buf, int8 len)
    {
        int i, got=0;
        do {
            if ((i=read(0, buf+got, len-got)) <= 0)
                return (i);
            got += i;
        }while (got < len);
        return (len);
    }
    int8 write_exact(byte* buf, int8 len)
    {
        int i, wrote=0;
        do {
            if ((i= write(1, buf+wrote, len-wrote)) <= 0)
                return (i);
            wrote += i;
        }while (wrote < len);
        return (len);
    }
    %%echo.erl
    %% echo.erl
    -module(echo).
    -export([start/0, stop/0, echo/1]).

    start() ->
        spawn(fun() ->
                register(echo, self()),
                process_flag(trap_exit, true),
                Port = open_port({spawn, "./sun.sh"}, [{packet, 1}]),
                loop(Port)
            end).

    stop() ->
        echo ! stop.

    echo(Msg) ->
        echo ! {call, self(), Msg},    %% Msg必须是一个List
        receive
            Result -> Result
        after 1000 -> io:format("time out~n"), true
        end.

    loop(Port) ->
        receive
            {call, Caller, Msg} ->
                Port ! {self(), {command, Msg}},    %% Msg必须是一个List
                receive
                    {Port, {data, Data}} ->     %% 返回的Data也是一个List
                        Caller ! Data
                end,
                loop(Port);
            stop ->
                Port ! {self(), close},
                receive
                    {Port, closed} -> exit(normal)
                end;
            {'EXIT', Port, Reason} ->
                io:format("port terminated!~n"),
                exit({port_terminated, Reason})
    end.
    下面是我的测试过程:
    ^_^[sunny@localhost ~/erl/io]86$ gcc echo.c -o echo
    ^_^[sunny@localhost ~/erl/io]87$ erl
    Erlang R13B04 (erts-5.7.5) [source] [smp:2:2] [rq:2] [async-threads:0] [kernel-poll:false]

    Eshell V5.7.5  (abort with ^G)
    1> c(echo).
    {ok,echo}
    2> echo:start().
    <0.39.0>
    3> echo:echo(["I_love_linux"]). 
    "I_love_linux"
    4> echo:stop().
    stop
    5> halt().
    ^_^[sunny@localhost ~/erl/io]88$ cat ports.log 
    start...
    buf:[I_love_linux],len:[12]
    end...
    ^_^[sunny@localhost ~/erl/io]89$ 

    由此表明,Erlang和C语言连接成功。

    拓展:Erlang和Shell脚本的交互。
        相信实现了Erlang和C的交互后,Erlang和shell的交互就是“A piece of cake”。

    需要两个文件:ls.sh  echo.erl
    #ls.sh
    #!/bin/bash
    ls > sun
    exit 0

    %%echo.erl
    -module(echo).
    -export([start/0, stop/0]).

    start() ->
        spawn(fun() ->
                register(echo, self()),
                process_flag(trap_exit, true),
                Port = open_port({spawn, "./ls.sh"}, [stream]),
                loop(Port)
            end).
    loop(Port) ->
        io:format("Execute successfully!The port is~p~n", [Port]).

    stop() ->
        echo ! stop.
    注意:ls.sh必须要有可执行的权限。

    ^_^[sunny@localhost ~/erl/io1]16$ erl
    Erlang R13B04 (erts-5.7.5) [source] [smp:2:2] [rq:2] [async-threads:0] [kernel-poll:false]

    Eshell V5.7.5  (abort with ^G)
    1> c(echo).
    {ok,echo}
    2> echo:start().
    <0.39.0>
    Execute successfully!The port is#Port<0.1972>
    3> halt().
    ^_^[sunny@localhost ~/erl/io1]17$ cat sun
    echo.beam
    echo.erl
    ls.sh
    sun
    ^_^[sunny@localhost ~/erl/io1]18$ ls
    echo.beam  echo.erl  ls.sh  sun
    ^_^[sunny@localhost ~/erl/io1]19$ 

    到此,我们实现虚拟机启动的技术问题,全部攻克了。

  • 相关阅读:
    ORA-01045: user XXZY lacks CREATE SESSION privilege; logon denied
    ORA-31626:作业不存在 ORA-31633:无法创建主表"XXX.SYS_IMPORT_FULL_05"
    HTTP 错误 401.3
    mysql 简单游标
    mysql 多重游标嵌套
    表单校验 “灰白字提示”
    Eclipse连接mysql数据库出现问题
    虚拟机无法使用桥接,没有未桥接网络适配器解决办法
    每周进度条(16)
    人月神话阅读笔记06
  • 原文地址:https://www.cnblogs.com/fvsfvs123/p/4244781.html
Copyright © 2011-2022 走看看