第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接口。
《Erlang程序設計》學習筆記-第12章 接口技術
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.