《Erlang程序設計》學習筆記-第12章 接口技術

第12章 接口技術

1. @spec open_port(PortName, [Opt]) -> Port
PortName是下列內容之一
    {spawn, Command} 啓動一個外部程序,Command是外部程序的名字。除非它是一個Erlang運行時可以找到的內聯驅動程序,否則Command所指定的程序會運行在Erlang運行時系統這外的系統進程裏。
    {fd, In, Out} 允許Erlang進程去訪問任何一個已經由Erlang打開的文件描述符。In可以用來發送數據到標準輸入,Out可以用來接收通過標準輸出發送的數據。
Opt是下列內容之一
    {packet, N}數據兇會以N(N應該是1,2,4之一,分別代表8位,16位,32位的整數)字節長度的數據作爲包的長度計數。
    {steam}發送的消息不含數據包的長度數據。使用這個參數的應用程序必須知道如何去處理這些包。
    {line, Max}發送的消息基於行。如果一行數據超過Max指定的字節數,那麼這一行就會在Max指定的位置分拆爲兩行。
    {cd, Dir}只對{spawn, Command}參數有效。它表明外部程序會在Dir指定的目錄中啓動。
    {env, Env}只對{spawn, Command}參數有效。外部程序的環境可以通過列表Env中的環境變量來擴展。Env是一個由形如{VarName, Value}的數據構成的列表,其中的VarName和Value都是字符串。
2. 創建端口的那個進程被稱爲端口連接進程。
3. 一個端口的通信由:端口連接進程,端口和外部程序組成。
4. 在創建Port時,erlang會自動啓動外部程序並與之連接,外部程序的標準輸出和標準輸入會指向erlang。
5. 一個端口其實就是一個Erlang進程。可以向它發送下列命令(PidC是端口連接進程的PID)
    Port!{PidC, {command, Data}} 向端口發送數據, Data是一個要發送的數據的列表。
    Port!{PidC, {connect, Pid1}} 用Pid1代替PidC來接收從Port發來的數據。
    Port!{PidC, close}    關閉端口。
6. 端口連接進程可以通過下面的方法接收來來自外部程序的消息(其實是外部程序發來數據,收端口轉換爲消息)
receive
    {Port, {data, Data}} ->  %% Data是接收到的數據的列表
7. 如果打開Port時應用了{packet,N}選項,則在發送數據時,erlang會爲數據自動加上一個長度頭,而在收到數據時,erlang也自動會爲數據去掉長度頭。
8. 自己寫了一個port的測試程序,比書上例子簡單,完成了簡單的echo功能,分別有兩個文件,一個echo.c,一個echo.erl:
%% 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.../n");
    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]/n", buf, len);
        fflush(fp);

        if(write_exact(&len, 1)<=0) return -1;
        if(write_exact(buf, len)<=0) return -1;
    }

    fprintf(fp, "end.../n");
    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
-module(echo).
-export([start/0, stop/0, echo/1]).

start() ->
    spawn(fun() ->
            register(echo, self()),
            process_flag(trap_exit, true),
            Port = open_port({spawn, "./echo"}, [{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.
   
9. 發現如果模塊名,函數名和註冊的進程名這三者重名的話,沒有問題。

10. 內聯驅動程序:創建一個內聯驅動程序是erlang與第三方語言對接的最有效方式,但它同時也是最爲危險一種的方式。內聯驅動程序的任何錯誤都會導致erlang系統崩潰,進而影響系統中的其他進程。正因爲如此,不推薦使用內聯驅動程序,除非別無它法。
11. 寫內聯的C程序時,需要用時erl_driver.h,erlang在安裝時會有這個文件,不同版本的erlang可能會有所不同。
12. 內聯驅動的C程序很簡單,定義幾個回調函數:主要是~start, ~stop, ~output。其中output函數會從erlang進程發送消息時調用。
13. 分析一下例子程序
start() ->
    start("example1_drv").

start(SharedLib) ->
    case erl_ddll:load_driver(".", SharedLib) of    %% 加載動態連接庫
    ok -> ok;
    {error, already_loaded} -> ok;
    Any -> io:format("any:~w~n", [Any]), exit({error, could_not_load_driver})
    end,
    spawn(fun() -> init(SharedLib) end).

init(SharedLib) ->
    register(example1_lid, self()),
    %% 這裏少了一個process_flag(trap_exit, true), 我想是因爲動態連接庫與這個進程在一個進程裏,所以,如果動態鏈接庫出問題(也就是這個進程出了問題了)也不會給這個進程報告,所以沒有必要,但有一個疑問就是如果沒有這個這個進程就不會收到exit消息,但在例子中卻有處理這個的匹配。
    Port = open_port({spawn, SharedLib}, []),    %% 打開端口,沒有選項
    loop(Port).

其中的與啓動外部系統進程一樣處理。
14. 有幾個有用的庫erl_interface(ei)是erlang與C的接口庫,Jinterface是erlang與Java的接口庫,ci是erlang的IDL編譯器,可能可以與com接口。

發佈了46 篇原創文章 · 獲贊 4 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章