erlang note
没有关于erlang interface ,继续寻找吧。。。
---------------------------------------------------------------
erl -noshell -pa erlpath -s hello(hello.beam) start(in hello.erl)
这个脚本需要使用绝对路径指向包含 hello.beam
erlang shell
#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$
---------------------------------------------------------------
也转下。。。
http://segmentfault.com/blog/haitao_419/1190000000620007
1、退出,输入 haut().
2、-module
3、module_name:function_name( arguments )
例如调用tut:double(10),说明调用tut模块的double函数。
4、模块名为tut
-module(tut).
-export( [double/1, fact/1] ).
double(X)->
2 * X.
fact(1) ->
1;
fact(N) ->
N*fact(N-1).
分号表示函数还未结束。
点号表示函数已经结束。
变量必须以大写字母开头,小写开头的为字符串。
5、元组
可以返回多个值
-module(tut).
-export( [double/1] ).
double(X) ->
{X*2, X*3, X*4}.
6、列表赋值
10> [One,Two|Rest]=[1,2,3,4,5,6].
[1,2,3,4,5,6]
11> One.
1
12> Two.
2
13> Rest.
[3,4,5,6]
14>
获取列表的长度
-module(tut).
-export( [get_length/1] ).
get_length([] ) ->
0;
get_length([First|Rest]) ->
1+get_length(Rest).
字符串输出采用ASCII表示
>[97,98,99].
"abc"
7、百分号表示注释
8、格式化输出
标准函数io:format
io:format("my name is ~w, my age is ~w~n", [biao, 20]).
9、/返回浮点数
div 整数除 rem 求余
10、'a' 等同于 a
11、元组嵌套赋值
1>F = {first, guo}.
{first,guo}
2> L = {last, biao}.
{last,biao}
3> Name = {person, F, L }.
{person,{first,guo},{last,biao}}
12、元组元素提取,采用占位符(下划线)
6> Name.
{person,{first,guo},{last,biao}}
7> {_,{_,N1},{_,N2}} = Name.
{person,{first,guo},{last,biao}}
8> N1.
guo
9> N2.
biao
10>
13、获取列表的元素值 [H|T]
10> ThingstoBuy=[{apple, 5}, {pear,10}, {orange, 20} ].
[{apple,5},{pear,10},{orange,20}]
11> [Buy1|Others] = ThingstoBuy.
[{apple,5},{pear,10},{orange,20}]
12> Buy1.
{apple,5}
13> Others.
[{pear,10},{orange,20}]
14、字符串=整数列表
Hello = "hello"
整数列表中,所有的整数都是可打印字符时,才转为字符串。
15> [1,2,3].
[1,2,3]
17> [97,98,99].
"abc"
使用$来表示一个字符的ASCII值(Latin-1)
18> [$;s-32, $amp;i, $;r].
"Sir"
又例如
23> [H|T]="cat".
"cat"
24> H.
99
25> T.
"at"
18、释放所有的变量绑定。f().
19、Erlang提供的命令。
pwd()打印当前路径,
切换当前目录到C盘根目录
1> cd("e:/erlang_src").
<input type="button" e:/erlang_src
ok
20、fun定义匿名函数,并可以赋给一个变量。类似函数指针。
4> Double = fun(X)->2*X end.
AT#Fun<erl_eval.6.80247286>
15> Double(2).
4>
21、把fun作为参数传入
例如标准库中的lists:map(F,L).
把F应用到列表的每个元素,并返回新列表。
17> lists:map(Double, [1,2,3,4]).
[2,4,6,8]
标准库lists:filter(P, L ).
把列表的每个元素作为P的参数,返回为TRUE的,才被放到结果列表中。
小例子:查找奇数列表中的奇数。
是否等于的测试符号:" =:= "
25> Even = fun(X) -> X rem 2 /= 0 end.
#Fun<erl_eval.6.80247286>
26> Even(2).
false
27> lists:filter(Even,[1,2,3,4,5,6,7,8,9]).
[1,3,5,7,9]
22、比较操作符
全等于 =:= 例如 1=:=1.0 false
等于 == 例如 1==1.0 true
不等于 /=
23、返回fun的函数。
28> Fruits=[apple,orange,pear].
[apple,orange,pear]
29> Test=fun(L)->( fun(X) -> lists:member(X,L) end) end.
#Fun<erl_eval.6.80247286>
31> Isfruits = Test( Fruits ).
#Fun<erl_eval.6.80247286>
32> Isfruits( apple ).
true
23、for循环,Erlang中无循环控制,需要自定义的控制结构
-module(for).
-export([for/3]).
for(MAX,MAX,F) -> [F(MAX)];
for(I,MAX,F) -> [F(I) | for(I+1,MAX,F)].
39> c(for).
{ok,for}
40> for:for(1,10,fun(I)->I*2 end).
[2,4,6,8,10,12,14,16,18,20]
在模块中,
如果使用-import(for, [for/3] ) 在使用for函数时,就不需要指定模块名。
24、列表解析
L=[1,2,3,4,5,6].
[1,2,3,4,5,6]
42> [2*X || X <- L].
[2,4,6,8,10,12]
43>
例如
[2*X || X <- L]
由F(X)组成的列表,其中X是L的每个元素。
又例如
49> L=[{orange, 8}, {apple, 5}, {pear, 10} ].
[{orange,8},{apple,5},{pear,10}]
50> [{Name, Number*2} || {Name,Number} <- L].
[{orange,16},{apple,10},{pear,20}]
注意{Name,Number}用于和L中的元素进行匹配。
25、过滤器
51> [X || {a, X} <- [{a,1}, {b,2}, {a,3}] ].
[1,3]
过滤出为a的值。
过滤出奇数的另外一种实现。
53> L = [1,2,3,4,5,6,7,8,9].
[1,2,3,4,5,6,7,8,9]
54> [X || X <- L, X rem 2 /= 0].
[1,3,5,7,9]
55>
[X || QUALIFIER1,QUARLIFIER2...].
QUALIFIER1...如果是 X <- L形式则为生成器。
如果是布尔表达式则为过滤器。
26、快速排序算法
-module(qsort).
-export([qsort/1]).
qsort([]) -> [];
qsort([Head|L] ) ->
qsort([X || X <- L, X < Head])
++ [Head] ++
qsort([X || X <- L, X >= Head] ).
27、lists:seq(1,N) 返回1-N的整数列表。毕达哥拉斯三元组。
28、断言。可以用于简单的变量测试和比较。断言语句使用when关键字开头。
-module(maths).
-export([max/2]).
max(X, Y) when X > Y -> X;
max(X, Y) -> Y.
29、断言谓词。
is_atom(X)
is_integer(X)
is_list(X)
is_function(X,N)
is_tuple(X) 是否元组
is_record(X,Tag,N)
30、断言内建函数(BIF)
abs(X)
element(N,X) 元组X的第N个元素
hd(X) 列表X的头部
tl(X) 列表X的尾部,除第一个元素以外的剩余元素。
length(X) 列表的长度
node() 当前节点 node(X) 创建节点
round(X) 四舍五入为整数
self() 当前进程的进程标示符
size(X) 元组的大小
例如:
67> L=[1,2,3,4,5,6,7].
[1,2,3,4,5,6,7]
68> hd(L).
1
69> tl(L).
[2,3,4,5,6,7]
31、用分号分隔的断言表示 or
用逗号分隔的断言表示 and
assert(X,Y) when is_integer(X), X>5, X>Y -> [X,Y]; % and 操作
assert(X,Y) -> [Y,X].
32、andalso,orelse用于构建稍微复杂一些的断言。
33、记录的定义保存在.hrl文件中,这样可以被多个module共享,以保证是同一份定义。
在命令行中使用记录之前,先要读取记录hrl文件,命令为 rr("xxx.hrl").
34、record定义,表名字为todo,字段有status,who,memo
-record( todo, {status=ok, who = biao, memo=test}). -->保存在record.hrl中.
插入三条记录
76> X1=#todo{status=bad,who=zb,memo=test2}.
\#todo{status = bad,who = zb,memo = test2}
77> X2=#todo{}.
#todo{status = ok,who = biao,memo = test}
78> X3=X2#todo{who=biaobiao}. %复制一条记录,然后修改who的值
#todo{status = ok,who = biaobiao,memo = test}
rf(todo).
去掉记录的定义,然后打印X2,会发现其实是元组。
35、提出RECORD记录值。
第一种方法。#todo{who=W, memo=M, status=S} = X3.
第二种方法。X3#todo.W X3#toto.M
36、case..of..end分支
例子:自定义filter
filter(P, [H|T]) ->
case P(H) of
true -> [H|filter(P, T)];
false->filter(P,T)
end;
filter(P,[]) -> [].
13> IsBig = fun(X) -> X > 10 end.
#Fun<erl_eval.6.80247286>
14> maths:filter(IsBig, [1,3,4,11,20]).
[11,20]
37、条件分支if..end
iftest(X) ->
if
X == 1 ->
"return 1";
X == 2 ->
"return 2";
true ->
"return other"
end.
38、try .of. catch.. after..end 为 case的增强版,带有异常捕捉功能。
三种类型的异常exit/throw/error
异常捕捉例子:
-module(try_test).
-export([demo1/0,generate_exception/1,catcher/1]).
generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {'EXIT', a};
generate_exception(5) -> erlang:error(a).
demo1() ->
[catcher(I) || I <- [1,2,3,4,5]].
catcher(N) ->
try generate_exception(N) of
Val -> { N, normal, Val} %%如果没有异常,这个generate_exception(N)的值就和VAL进行匹配。
catch
throw:X -> {N, caught, throw, X };
exit:X -> {N, caught, exit, X };
error:X -> { N, caught, error, X }
after
io:format("test").
end.
运行结果:
6> try_test:demo1().
[{1,normal,a},
{2,caught,throw,a},
{3,caught,exit,a},
{4,normal,{'EXIT',a}},
{5,caught,error,a}]
39、erlang:error提高错误信息的质量。
sqrt(X) when X < 0 ->
erlang:error( { sqrtNagativeArgumentError, X } );
sqrt(X) ->
math:sqrt(X).
运行后,有如下提示:
7> maths:sqrt(-1).
** exception error: {sqrtNagativeArgumentError,-1}
in function maths:sqrt/1
40、函数返回值,
{ok, Val} 或者 {error, Why} 调用者模式匹配后进行处理。
例如:
41、erlang中的小于等于:" =< ", 大于等于: ">="
例如:
%%输出不同的结果
getValue(X) when X >= 1, X =< 100
-> {ok, 'less than 100'};
getValue(X)
-> {error, 'larger than 100' }.
%%调用getValue对结果输出
deal(X) ->
case getValue(X) of
{ok, Info } ->
io:format("ok print info: ~w~n", [Info]);
{error, Info} ->
io:format("error print info: ~w~n", [Info])
end.
42、捕获任何异常的写法 ( : )。
try expr of
模式匹配 -> ...
catch
_:- -> 异常处理
end.
43、使用 erlang:get_stacktrace() 打印函数调用的栈信息。
demo2() ->
try generate_exception(2)
catch
throw:X ->
{X, erlang:get_stacktrace()}
end.
44、BIF并非erlang编写,而是虚拟机上的基本操作,包含在erlang模块中,
并自动导入,所以使用时不需要用前缀,例如 erlang:tuple_to_list/1
cat 和 'cat' 是一样的,为一个常量。
元组转化为列表
3> tuple_to_list({1, "cat", "hello"}).
[1,"cat","hello"]
获取当前时间
4> time().
{11,11,2}
45、二进制数据中的整数,必须是0-255.
超出255,会从0开始,例如256输出会变成0, 257变成1.
erlang的函数注释:@spec func( arg1, arg2 ...) -> val
46、列表转成二进制数据。
@spec list_to_binary( L ) -> binary().
11> list_to_binary([1,2,3]).
<<1,2,3>>
12> list_to_binary([<<1,2,3>>, 1,2,3,<<4,5,6>>]).
<<1,2,3,1,2,3,4,5,6>>
13>
47、列表分割
@spec split_binary(BIN, POS) -> {BIN1,BIN2}
13> split_binary(<<1,2,3,4,5,6>>, 3).
{<<1,2,3>>,<<4,5,6>>}
48、@spec term_to_binary(BIN) -> Term
把任何erlang值转成二进制,把格式也存入二进制中,用于文件传输和网络传输,
可以还原的。
17> term_to_binary({"cat", "abc"}).
<<131,104,2,107,0,3,99,97,116,107,0,3,97,98,99>>
18> binary_to_term(<<131,104,2,107,0,3,99,97,116,107,0,3,97,98,99>>).
{"cat","abc"}
49、二进制的字节长度
@spec size(BIN) -> Int
19> size(<<1,2,3,4,5,6>>).
6
50、比特语法
例子,用16位存储RGB
用16位存储,Red和Blue为5为,Green 6位
20> Red = 10.
10
21> Green = 20.
20
22> Blue = 30.
30
23> Color = <<Red:5, Green:6, Blue:5>>.
<<82,158>>
采用模式匹配提出值
24> <<R:5, G:6, B:5>> = Color.
<<82,158>>
25> R.
10
26> G.
20
27> B.
30
28>
51、比特语法表达式中的元素,有一项指明计算机系统的字节序。
big/little/native. 默认为big,运行时根据CPU来确定字节序则选native
在不同机器之间进行整数、二进制之间的解包和封包,需要使用正确的字节序。
例如<<123456:32/big, 45678:16/little>>
52、全局宏定义,使用问号获取。
-define(BUFFER, 2048).
getDefine() ->
?BUFFER * 2.
测试结果:
38> maths:getDefine().
4096
53、apply可以动态调用BIF,例如apply(erlang, atom_to_list, [hello]).
模块属性
-import( lists, [map/2] ).
引入lists:map/2, 在模块中调用就不需要指明前缀lists
-export([getDefine/2]).
只有导出函数,在模块外部才能被访问到。
-compile(export_all).
如果要把模块中的函数全部导出,可以使用-compile来代替-export.
-vsn(1.0).
表示模块的版本。
54、自定义的模块属性。
例如-author( biao ).
使用maths:module_info().可以输出所有这些信息项。
50> maths:module_info(attributes).
[{vsn,[1.0]}]
51> beam_lib:chunks("maths", [attributes]). %使用系统模块来获取。
{ok,{maths,[{attributes,[{vsn,[1.0]}]}]}}
从以上结果抽取对应的值。
beam_lib:chunks用来提取模块的属性,然后用以下函数可以提出属性值。
-module(extract).
-export([extract/2]).
extract(File, Key) ->
case beam_lib:chunks(File, [attributes]) of
{ok, {File,[{attributes,L}]}} ->
case lookup( Key, L ) of
{ok, Val} -> Val;
error -> exit(notFound)
end;
_ ->
exit( badFile )
end.
lookup(Key, [{Key,Val}|_]) -> {ok,Val};
lookup(Key, [_|T]) -> lookup(Key, T);
lookup(Key, [] ) -> error.
输出:
53> extract:extract(maths, vsn).
[1.0]
55、块表达式。
begin
expr1,
expr2
end.
返回的是最后一条expr的值。
56、布尔表达式
63> not true.
false
64> true and false.
false
65> true or false.
true
66> (2>1) or (3>4).
true
erlang的预处理器是epp
57、转义符
\b 退格
\d 删除
\s 空格 \t tab \n 换行 \r回车
\^X 代表 CTRL+X ,X为A-Z或a-z
\'单引号 \" 双引号 \\反斜杠 \C字母的ascii值。
\NNN \NN \N 表示八进制数。
58、函数引用,使用fun funcname/argnum
-module(funRef).
-export([double/1,double2/1]).
square(X) -> X*X.
double(L)->lists:map(fun square/1, L). %引用本地
double2(L) -> lists:map( fun maths:sqrt/1, L ). %引用其他模块
59、包含文件,把文件引入当前模块,例如hrl文件
-include( File ).
-include_lib("kernel/include/file.hrl"). 包含lib下最新kernel下的file.hrl.
60、中缀操作符++ --
72> [1,2,3]++[4,5,6].
[1,2,3,4,5,6]
73> [1,2,3,3,3,4,5,3]--[3,3].
[1,2,3,4,5,3]
74> [1,2,3,4,3,3]--[3,3,3,3].
[1,2,4]
61、宏定义
-define( BUFFER, 2048).
-define( Test(A, B), {A,B,A,B}).
预定义宏。
print()->
io:format("~p,~p,~p~n", [?FILE,?MODULE,?LINE]).
87> funRef:print().
"./funRef.erl",funRef,7
宏的流程控制,编译开关。
-module(macro).
-export([start/0,loop/1]).
-ifdef(debug).
-define(TRACE(X), io:format("Trace ~p~p:~p~n", [?MODULE,?LINE,X])).
-else.
-define(TRACE(X), void).
-endif.
start() -> loop(5).
loop(0) -> void;
loop(N) ->
?TRACE(N),
loop(N-1).
编译运行:
107> c(macro,{d, debug}). %%引入debug定义
{ok,macro}
108> macro:start().
Trace macro14:5
Trace macro14:4
Trace macro14:3
Trace macro14:2
Trace macro14:1
void
62、在模式中使用匹配操作符。
test({name,Name}=Z|T) ->
f(Z)...
63、K进制整数的表示方法。
15#11.
15进制,其值为16.
$a,$\^c,表示ascii值。
64、进程字典,由一系列的键值对组成。
3> get().
[]
4> put(x,20). %把x的值设置为20,并把原来的值返回。
undefined
5> put(x,30).
20
6> get(x).
30
7> put(y,40).
undefined
8> get(). %返回所有的进程字典
[{y,40},{x,30}]
9> erase(x). %删除字典x
30
10> get().
[{y,40}]
11> erase().
[{y,40}]
尽量少用进程字典,一般用于只读的一些参数设置。一次性写入的变量。
65、erlang:make_ref().创建一个唯一标签。
全局唯一。匹配时使用的。
13> Data = make_ref().
#Ref<0.0.0.64>
14> Data.
#Ref<0.0.0.64>
66、短路布尔表达式。andalso orelse 而 (and,or两个都要求值)。
比较表达式:
X =< Y 小于等于
X /= Y 不等于 仅适用于整数和浮点数的比较。
X =:= Y 全等于 适用于所有的比较。和C++中的==一样。
X =/= Y 不全等于 适用于所有的比较。和C++中的==一样。
X == Y 等于 仅适用于整数和浮点数的比较。
67、下划线变量。
只使用一次的变量,例如open(File,_Mode) 等价于 open(File, _)
退出shell 输入q().等同于init:stop().
68、加载路径:
code:add_patha(Dir).加到开头。
code:add_pathz(Dir).加到结尾。
code:all_loaded(). 已加载的路径。
code:get_path().查找路径设定值。
code:clash().检查加载错误。
命令行增加加载路径:erl -pa Dir1 -pa Dir2 ....
获取erlang所需的home目录 init:get_argument(home).
69、命令行脚本。
erl -noshell -s hello start -s init stop.
用非交互式方式,调用hello:start(),然后调用init:stop().
命令行中执行任意一个函数
erl -eval 'io:format("test").' -noshell -s init stop.
70、一个相关的makefile
.SUFFIXES: .erl .beam
.erl .beam:
erlc -W $<
ERL= erl -boot start_clean
MODS = module1 module2 module3
all: compile
${ERL} -pa './dir' -s module1 start
compile:${MODS:%=%.beam}
clean:
rm -rf *.beam erl_crash.dump
erlang虚拟机的错误信息。
webtool:start().可以看到地址。
1> webtool:start().
WebTool is available at http://localhost:8888/
Or http://127.0.0.1:8888/
{ok,<0.34.0>}
71、ERLANG中进程和操作系统是不同的,ERLANG中的进程是程序语言,并不属于操作系统。
每个进程都是独立运行的ERLANG虚拟机。
Pid = spawn(Fun).产生一个新进程对Fun求值。
Pid ! M 把消息M发送给Pid进程,返回M.
Pid1 ! Pid2 ! Pid3 ! M..群发消息。
receive ... Other .. end. 接受消息。
72、创建一个求面积的服务进程。
-module(area).
-export([loop/0,rpc/2]).
%%客户端
rpc(Pid, Request) ->
Pid ! {self(), Request },
receive
{Pid, Response} ->
io:format("get response: ~p~n", [Response]);
_Other ->
_Other
end.
%%服务端
loop() ->
receive
{From, {rectangle, W, H}} ->
From ! {self(), W * H},
loop();
{From, {circle, R} } ->
From ! {self(), 3.14*R*R},
loop();
{From, _Other} ->
From ! {self(),"i don't know~~"},
loop()
end.
运行情况:
14> Pid = spawn(fun area:loop/0).
<0.62.0>
16> area:rpc(Pid, {rectangle, 4, 5}).
get response: 20
ok
17> area:rpc(Pid, {circle, 3}).
get response: 28.259999999999998
ok
注意:Pid ! M 本身是会返回消息M,所以在输出中也会体现。
self()表示当前进程的进程号
73、erlang允许的最大进程数。
18> erlang:system_info(process_limit).
32768
要增加这个上限,再启动时使用+P参数
C:\Documents and Settings\Administrator>erl +P 50000
2> erlang:system_info(process_limit).
50000
74、计算创建进程消耗的CPU时间和实际时间。
-module(elapse).
-compile(export_all).
compute(N) ->
Max = erlang:system_info( process_limit ),
io:format("max process limit is: ~p~n", [Max] ),
statistics(runtime), %%开始统计CPU消耗时间
statistics(wall_clock), %%开始统计实际消耗时间
L = for( 1, N, fun() -> spawn( fun() -> wait() end ) end ), %%启动N个进程。
io:format("in"),
{_,Time1} = statistics( runtime ), %%统计时间
{_,Time2} = statistics( wall_clock ), %%统计时间
lists:foreach( fun( Pid ) -> Pid ! die end, L ),
U1 = Time1 * 1000/N,
U2 = Time2 * 1000/N,
(pse ~p(um) CPU Time and ~p(um) Wall Time.", [U1, U2] ).
wait() ->
receive
die -> void
end.
for( N, N, F ) -> [F()];
for( I, N, F ) -> [F() | for(I+1,N,F)]. %%注意这里的for,不要写成F
75、receive 进程等待的超时时间设置。
-module(wait).
-compile( export_all ).
waiting( Time ) ->
receive
Time when Time > 100 ->
'time is larger than 100';
_ ->
'other time'
after Time ->
'no request...'
end.
等待Time毫秒后,如果没有接收到合适的请求,则执行after后面的语句。
after后面的箭头,极易遗漏!
76、只有超时的receive语句
例如让当前的进程停止T ms
-module(sleep).
-export([sleep/1]).
sleep(T) ->
receive
after
T -> void
end.
77、永远等待 infinity
sleep()->
receive
after
infinity ->
void
end.
78、注册进程:发布一个进程标示符,以便其他进程与之通信。
register(AnAtom, Pid ).
unregister(AnAtom).
whereis(AnAtom) -> Pid | undefined 判断原子AnAtom是否已被注册。
registered() -> [] 返回系统中所有已注册的名称列表。
例如:
6> Pid = spawn(fun area:loop/0).
<0.42.0>
7> register( area, Pid).
可以看做是别名,进程退出时,自动取消注册。
79、spawn( Fun) 创建一个进程、执行Fun对应的函数。
-module(clock).
-compile(export_all).
start(Time, Fun) ->
register( clock, spawn( fun() -> tick( Time, Fun) end ) ). %%创建一个进程,并给出别名,然后进程执行tick函数。
stop() ->
clock ! stop.
tick( Time, Fun ) ->
receive
stop -> void
after
Time -> %% 不断地等待,超时后打印一条记录,又接着回调tick
Fun(),
tick(Time, Fun)
end.
执行结果如下:
27>
clock:start(1000, fun()->io:format("test") end ).
true
28> test28> test28> test28> test28> test28> test28> test28
28> test28> test28> test28>
clock:stop().
teststop
80、使用MFA创建进程。
spawn( Mod, Function, Args ).
link(Pid) 把当前进程和Pid进程链接。
unlink(Pid) 取消链接
exit(Why) 退出,并广播这个Why原因内容。
exit(Pid, Why) 向Pid发送退出信号。
erlang:monitor(process, Pid) 建立一个监视器。监视器是单向的,而链接是双向。
BIF函数:process_flag( trap_exit, true).把当前进程变成系统进程。
系统进程可以捕获别的进程的退出状态。
81、
进程正常结束,发出normal信号,他的进程集合屏蔽这种信号。
进程非正常结束,他的进程集中的非系统进程都会退出,系统进程才能处理这种信号。
进程收到kill信号,不管是否是系统进程,全部退出,并且广播。
82、三种进程模式
Pid = spawn( fun() -> ... end ). 被创建的进程消亡,当前进程毫无察觉。
Pid = spawn_link( fun() -> ... end ).被创建的进程非正常消亡,当前进程也会退出。
被创建的进程退出,当前进程做错误处理。
process_flag(trap_exit, true), %先变成系统进程。
PID = spawn_link( fun() -> .. end ),
loop().
loop( State) ->
receive
{'EXIT', Pid, Reason } ->
...
loop( State1 );
...
end.
83、存活进程例子
-module(onexit).
-compile( export_all).
keep_alive( Name, Fun )-> %创建一个进程注册为Name,然后执行Fun函数。
register( Name, Pid = spawn( Fun) ),
on_exit( Pid, fun(_Why) -> keep_alive(Name, Fun) end ).
% 把当前进程和Pid链接,然后进程异常退出时,捕获并调用Fun函数处理。
on_exit(Pid, Fun) ->
process_flag( trap_exit, true),
link( Pid ),
receive
{ 'EXIT', Pid, Why } -> Fun(Why)
end.
84、创建一个名字服务。
-module(kvs).
-compile(export_all).
%启动:创建一个服务进程,循环等待处理,并给予别名kvs
start() ->
register(kvs, spawn( fun() -> loop() end )).
%向服务端发起变更请求,并接收结果
store(Key, Value) ->
kvs ! {self(), {store, Key, Value}},
receive
{kvs, Reply} -> Reply
end.
%向服务器发起查询请求,并接收结果
lookup(Key) ->
kvs ! {self(), {lookup, Key}},
receive
{kvs, Reply} -> Reply
end.
%接收客户端请求,并把结果发给客户端
loop()->
receive
{From, {store, Key,Value} } ->
put( Key, Value ),
From ! {kvs, true},
loop();
{From, {lookup, Key} } ->
From ! {kvs, get(Key) },
loop()
end.
运行结果:
18> kvs:start().
true
19> kvs:store(money, 100).
true
20> kvs:store(name, biao).
true
21> kvs:lookup(name).
biao
22> kvs:lookup(money).
100
85、启动节点时,给予名字。同一台机器。
erl -sname Name
C:\Documents and Settings\Administrator>erl -sname biao1
Eshell V5.8.4 (abort with ^G)
(biao1@biao)1> cd ("e:/erlang_src").
e:/erlang_src
ok
在第一个节点上运行服务
(biao1@biao)2> kvs:start().
true
在第二个节点上,远程发送消息来调用
(biao2@biao)3> rpc:call(biao1@biao, kvs, store, [sex, male]).
true
(biao2@biao)4> rpc:call(biao1@biao, kvs, lookup, [name]).
biao
86、在局域网内的不同机器之间。
erl -name biao1 -setcookie abc
运行在两个不同的网络上,需要用全名。需要DNS服务。
两个节点之间需要使用相同的cookie。
在/etc/hosts可以添加域名入口,内容格式为 网络IP地址 主机名或者域名 [主机名别名]。
erl -sname biao1
表示短名,在同一台机器或者在局域网内,直接用短名即可。
87、判断节点(NODE)之间是否连通。
net_adm:ping(Node).
设置erlang节点的cookie
erlang:set_cookie(node(), abc).
spawn( Node, Fun ) -> Pid
spawn( Node, Mod, Func, Arg ) -> Pid
spawn_link( Node, Fun ) -> Pid
spawn_link( Node, Mod, Func, Arg) -> Pid.
node()->Node. 返回本地节点的名字。
nodes() ->Node[] 返回与当前节点连接的所有节点。
monitor_node(Node, Flag ) 当前进程监视Node,如果Flag为true则打开,Flag为false则关闭。
当前进程会收到信号{nodeup, Node}, {nodedown, Node }
is_alive() -> bool() 本地节点状态是否正常。
{RegName,Node} ! Msg 向节点Node的RegName进程发消息。
88、在因特网上两个节点通信
A、确保4396端口是通的,Erlang的epmd会使用这个端口。
B、ERLANG端口使用。启动时erl -name .. -setcookie .. -kernel inet_dist_listen_min Min inet_dist_listen_max Max.
C、节点之间必须拥有相同的cookie,具有相同cookie的相连接的节点群称为ERLANG集群。
89、apply(M,F,A),这个可以实现,模块函数的配置化。
rpc:call(Node, Mod, Function, Args) -> Result | {badrpc, Reason}
erl -setcookie ABCEDEF
90、lib_chan模块,用来控制能够启动哪些进程。这个应该是第三方的包。
start_server()->true 启动一个服务。
start_server( Conf) -> 按照配置启动一个服务。
配置文件内容是一系列的元组,
{port, NNN } 服务器的监听端口为NNN
{service, S,password, P, mfa, SomeMod,SomeFunc,SomeArgs}
定义由密码P保护的服务S,如果服务启动,则由SomeMod:SomeFunc(MM,ArgsC,SomeArgs}创建的进程会出处理来自客户端的消息。
MM: 代理进程Pid,用于向客户端发消息
ArgC:来自客户机的调用参数。
connect(Host, port, S, P, ArgC) -> {ok, Pid} | {error, Why}
91、Socket套接字
-module(socket_test).
-compile(export_all).
get_url() ->
get_url( "www.google.com" ).
get_url( Host ) ->
%% 调用connect连接Host:80,产生一个套接字,
%% 采用二级制传输,{packet, 0}意味着原封不动地返回TCP数据。
{ok, Socket} = gen_tcp:connect( Host, 80, [binary, {packet, 0}] ),
%% 把消息发送到套接字
gen_tcp:send( Socket, "Get/HTTP/1.0\r\n\r\n"),
%% 进程接收套接字返回的消息
receive_data( Socket, [] ).
receive_data( Socket, Result ) ->
receive
%% 每次收到的Bin消息都放到列表的头部
{tcp, Socket, Bin } ->
receive_data( Socket, [Bin|Result] );
%%接收完毕,把数据反转并转化为二级制数据
{tcp_closed, Socket } ->
list_to_binary( lists:reverse(Result) )
end.
运行情况:
8> socket_test:get_url().
<<"HTTP/1.0 405 Method Not Allowed\r\nContent-Type: text/html; charset=UTF-8\r\n
Content-Length: 11815\r\nDate: Fri, 19 Aug 20"...>>
9>
92、创建一个监听8888端口的服务
服务端
-module(compute_server).
-compile(export_all).
start_server() ->
%% 监听8888端口,并返回一个监听套接字Listen
%% 字节流中的头部是4个字节
{ok,Listen} = gen_tcp:listen(8888, [binary, {packet, 4}, {reuseaddr, true}, {active, true}]),
%%开始监听8888端口,等待连接
%%有新连接进来,产生一个新的连接套接字Socket
{ok, Socket} = gen_tcp:accept(Listen),
%%连接成功后,关掉监听器,新建的Socket不会受到影响。
gen_tcp:close( Listen ),
loop( Socket ).
loop( Socket ) ->
receive
{tcp, Socket, Bin } ->
io:format("receive binary: ~p~n", [Bin]),
Str = binary_to_term( Bin ),
io:format("receive value: ~p~n", [Str]),
Reply = Str,
io:format("server reply: ~p~n", [Reply]),
gen_tcp:send(Socket, term_to_binary(Reply) ),
loop( Socket );
{tcp_closed, Socket } ->
io:format("server socket closed.~n")
end.
客户端:
-module(compute_client).
-compile(export_all).
send( Str ) ->
{ok, Socket} = gen_tcp:connect("localhost", 8888, [binary, {packet, 4}]),
ok = gen_tcp:send(Socket, term_to_binary( Str) ),
receive
{tcp, Socket, Bin } ->
io:format("client receive binary: ~p~n", [Bin] ),
Val = binary_to_term( Bin ),
io:format("client receive value: ~p~n", [Val] ),
gen_tcp:close( Socket )
end.
运行情况
客户端
2> compute_client:send("abc").
client receive binary: <<131,107,0,3,97,98,99>>
client receive value: "abc"
ok
服务端
7> compute_server:start_server().
receive binary: <<131,107,0,3,97,98,99>>
receive value: "abc"
server reply: "abc"
server socket closed.
ok
93、主动套接字
创建时设置{active, true}
数据到达时,系统向控制进程发送{ tcp, Socket, Data }
控制进程无法控制流量。
用于异步服务器,客户端不会被阻塞。
94、被动套接字
{actvie, false}
gen_tcp:recv(Socket, N) 接收来自套接字的数据。N为接收的字节数,如果N=0,则接收所有的
调用recv时,客户端会被阻塞。
只能等待一个套接字的消息。
95、混合型Socket
{active, once }
主动接收一条消息,然后系统处于阻塞状态,
必须调用inet:setopts(Socket, [{active, once}] )
96、inet:peername(Socket) ->{ok, {Ip, Port}} | {error, Why} 查看连接的来源。
97、ets 表类型
set 键不能相同。
order set 排序的set
bag 可以有相同的键,但不能有两个相同的元组。
duplicate bag 可以有相同的键值,也可以有两个相同的元组。
98、set ordered_set,bag,duplicat_bag 操作
-module(ets_test).
-export([start/0]).
start() ->
%%注意第一个参数为 fun test/1,即调用test函数
lists:foreach( fun test/1, [set, ordered_set, bag, duplicate_bag]).
test( Mode ) ->
%%创建一个tab表,返回表ID
TabID = ets:new( tab, [Mode] ),
ets:insert(TabID, {a, 1}),
ets:insert(TabID, {b, 2}),
ets:insert(TabID, {a, 1}),
ets:insert(TabID, {a, 3}),
ets:insert(TabID, {a, 2}),
%% 把表数据转成列表。
List = ets:tab2list(TabID),
%% 13位宽度显示。
io:format("~13w => ~p~n", [Mode, List]),
%%删除表,释放空间
ets:delete( TabID ).
运行:
4> ets_test:start().
set => [{b,2},{a,2}]
ordered_set => [{a,2},{b,2}]
bag => [{b,2},{a,1},{a,3},{a,2}]
duplicate_bag => [{b,2},{a,1},{a,1},{a,3},{a,2}]
ok
99、@spec ets:new(Name, [Opt]) -> TabID
Name 一个原子
Opt取值有
1) set | ordered_set | bag | duplicate_bag
2) private 私有表,只有所有者进程才能读写。
3) public 公开表
4) protected 只有所有者进程能写,其余知道表名字的进程可以读。
5) named_table 命名表,后续可以使用Name来操作这个表
6) {keypos,1} 设置键的位置。在record中使用。
ets:new的默认设置为
[set,protected, {keypos, 1}]
100、把服务器的公共部分、不怎么变化的部分放在一个文件中,
然后把变化的部分(业务逻辑)放在另外一个文件中,
通过回调实现解耦。
服务端例子:
-module(server1).
-export([start/2,rpc/2]).
%% 创建一个进程,并且进程注册名是Name,传入一个Mod模块名。
start(Name, Mod ) ->
register( Name, spawn( fun() -> loop(Name, Mod, Mod:init() ) end ) ).
%% 进程Name, 循环接收消息,并调用Mod:handle(..,State)
loop( Name, Mod, State ) ->
receive
{ From, Request } ->
{Response, State1} = Mod:handle(Request, State),
From ! {Name, Response},
loop( Name, Mod, State1)
end.
%% 客户端向Name进程发送Request消息
rpc( Name, Request ) ->
Name ! {self(), Request},
receive
{Name, Response} -> Response
end.
被回调的Mod模块
-module(name_server).
-compile(export_all).
-import(server1, [rpc/2]).
%%被服务器回调的程序代码,可以把服务器代码当做一个模板
init() -> dict:new().
handle( { add, Name, Place }, Dict ) ->
{ok, dict:store( Name, Place,Dict) };
handle( { whereis, Name }, Dict ) ->
{dict:find(Name, Dict), Dict}.
被回调的部分可以看做是接口。
运行情况如下:
10> server1:start(myserver, name_server).
true
11> server1:rpc(myserver, {add, biao, 'at home'}).
ok
12> server1:rpc(myserver, {whereis, biao}).
{ok,'at home'}
101、支持事务的服务端程序
在handle处理异常时,给客户端发退出指令。
-module(server2).
-compile(export_all).
%% 创建一个进程,并且进程注册名是Name,传入一个Mod模块名。
start( Name, Mod ) ->
register( Name, spawn( fun() -> loop(Name, Mod, Mod:init())end )).
%% 进程Name, 循环接收消息,并调用Mod:handle(..,State)
loop( Name, Mod, OldState ) ->
receive
{From, Request } ->
try Mod:handle( Request, OldState ) of
{Response, NewState} ->
From ! {Name, ok, Response },
loop( Name, Mod, NewState )
catch
_:Why ->
io:format("Server ~p request ~p~n"
"caused exception ~p~n",
[Name, Request, Why] ),
From ! {Name, crash },
loop( Name, Mod, OldState )
end
end.
%% 客户端向Name进程发送Request消息
rpc( Name, Request ) ->
Name ! {self(), Request},
receive
{Name, crash} -> exit( rpc );
{Name, ok, Response} -> Response
end.
102、一个空服务器,然后根据传入的指令进行操作。
服务端
-module(server5).
-compile(export_all).
start() ->
spawn( fun() -> wait() end ).
wait() ->
receive
{become, F} -> F()
end.
rpc( Pid, Q) ->
Pid ! { self(), Q },
receive
{Pid, Reply} -> Reply
end.
具体的服务操作,把功能部分解耦出来。
-module(fac_server5).
-compile(export_all).
loop() ->
receive
{From, {fac, N} } ->
From ! {self(), fac(N)},
loop();
{become, Something} ->
Something()
end.
fac(0) -> 1;
fac(N) -> N * fac(N-1).
运行情况:
2> Pid = server5:start().
<0.33.0>
3> Pid ! {become, fun fac_server5:loop/0 }.
{become,#Fun<fac_server5.loop.0>}
4> server5:rpc(Pid, {fac, 3}).
6
5> server5:rpc(Pid, {fac, 5}).
120
103、服务器模板gen_server.
需要在开头使用 -behaviour( gen_server).
回调函数有:
init/1 初始化,返回一个状态,作为handle_call的输入
handle_call/3 回调主体,模式匹配。
handle_cast/2
handle_info/2
terminate/2
code_change/3
写个服务器程序,需要以下三步:
1、确定回调模块的名称。这里为my_bank.
2、接口说明
start() 打开银行
stop() 关闭银行
new_account(Who) 开户
deposit(Who, Amount) 存钱
withdraw(Who,Amount) 取钱
3、编写回调函数
init([]) -> {ok, State } %这里的State生成后,将传给后面的函数使用,是一个全局的对象
handle_call(_Request, _From, State) -> {reply, Reply, State}
handle_cast(_Msg, State) -> {noreply, State }
handle_info(_Info, State) -> {noreply, State}
terminate(_Reason, _State) -> ok
code_change(_OldVsn, State, Extra ) -> {ok, State }
例子:
%%第一步,确定回调模块名称
-module(my_bank).
%-behaviour(gen_server).
-compile(export_all).
%%第二步,接口实现
%% start_link启动一个本地的服务器{local,Name},第二个参数为回调模块。.
%% 首先会调用MOD:init/1
start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], [] ).
%% gen_server:call(?MODULE,Term) 发起对Name服务器的远程调用。
stop() -> gen_server:call(?MODULE, stop ).
new_account(Who) -> gen_server:call(?MODULE,{new,Who}).
deposit(Who, Amount) -> gen_server:call(?MODULE, {add, Who, Amount}).
withdraw(Who,Amount) -> gen_server:call(?MODULE,{remove, Who, Amount}).
%%第三步 回调函数实现
%%6个回调函数
init([]) ->
{ok, ets:new(?MODULE, [set]) }.
%银行开户
handle_call({new, Who}, _From, Tab) ->
Reply = case ets:lookup(Tab, Who) of
[] -> ets:insert(Tab, {Who, 0}),
{ welcome, Who };
[_] -> {Who, you_already_in_bank}
end,
{reply, Reply, Tab};
%银行存钱
handle_call({add, Who, X}, _From, Tab) ->
Reply = case ets:lookup( Tab, Who ) of
[] -> you_are_not_in_bank;
[{Who, Balance}]->
NewBalance = Balance + X,
ets:insert(Tab, {Who, NewBalance}),
{thanks, Who, your_balance_now_is, NewBalance}
end,
{reply, Reply, Tab};
%银行取钱
handle_call( {remove, Who, X}, _From, Tab ) ->
Reply = case ets:lookup( Tab, Who) of
[] -> you_are_not_in_bank;
[{Who, Balance}] when X =< Balance ->
NewBalance = Balance - X,
ets:insert(Tab, {Who, NewBalance} ),
{thanks, Who, your_balance_now_is, NewBalance};
[{Who, _Balance}] ->
{sorry, you_havnot_enough_money}
end,
{reply, Reply, Tab };
%终止服务器程序的方法 handle_call(Stop, From, State) -> {stop,Reason,Reply,Tab}
handle_call(stop, _From, Tab ) ->
{stop, normal, stopped, Tab }.
handle_cast(_Msg, State) -> {noreply, State }.
handle_info(_Info, State) -> {noreply, State }.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State }.
104、gen_server:start_link(Name,Mod,InitArgs,Opts)
创建Name服务,调用Mod:init(InitArgs)启动服务。
105、gen_server:call(Name,Request).调用服务器程序,发起Request请求,会回调handle_call
106、gen_server:cast(Name,Name) 回调 hanle_cast(_Msg,State)
handle_cast(_Msg,State) -> {noreply, NewState}
107、handle_info() 用来处理服务器收到的原生消息,例如收到其他进程的{'EXIT',PID,WHAT}.
handle_info(_Info,State) -> {noreply,State}.
108、在handle_XXX()函数返回{stop, Reason, State}
-----------------------------------------------------------------
http://wgcode.iteye.com/blog/1018614
第二章 入门
1.所有的变量都必须以大写字母开头,如果要查看某个变量,只要输入变量的名字即可,如果一个变量被赋予值,就称为绑定变量,否则被称为自由变量,一开始所有变量都是自由的。 有一点像Java中的常量,这就是为什么用大写字母的原因。
2. “=” 近似于一个赋值操作符,是一个模式匹配运算符,当X是自由变量未被赋值时“=”是赋值运算符,否则是模式匹配运算符。
3. “/”除号永远返回浮点数。
4. 原子用来表示不同的非数字常量值,是一串以小写字母开头,后跟数字字母 或 下划线 或 邮件符号@的字符,如:red等。
使用单引号引起来的字符也是原子,这种形式,我们就能使用大写字母做为原子,如:'Monday'。一个原子的值就是原子自身,如果输入的命令就是一个原子,那么Erlang会打印那个原子的值。
例如: 1> hello.
hello
5. 若干个以逗号分隔的值用一对花括号括起来,就开成了一个元组,元组没有名字,通常使用一个原子作为元组的第一个元素来标明这个元组所代表的含义如:{point, 20, 45},元组可以嵌套,元组中的元素可以是另一个元组。提取元组中的值,
Point = {point, 20, 45}, {point , X, Y} = Point , 可以得出X = 20, Y = 45。
6. ”_“是一个占位符,称为匿名变量,表示那些我们不关心的变量,与常规变量不同的是,在同一个模式中的不同地方,各个_所绑定的值不必相同。
7. 若干个以逗号分隔的值用一对方括号括起来,就形成了一个列表,[point, 72, {jjj, 20, 30}]。列表的第一个元素称为列表头,剩下的元素就称为列表尾,列表的尾通常还是一个列表,访问列表头的操作非常高效,实际上所有的列表处理函数都是从提取列表头开始,往后处理。[H | T], H是列表头,T是列表尾,还是一个列表。
用模式匹配方式从一个列表中提取元素,[Buy1 | Thing2ToBuy2] = [{o,4}, {n, 1}, {a, 2}], Buy1 -> {o,4},
Thing2ToBuy2 -> [{n, 1}, {a, 2}]。[X]就是[ X | [] ]的缩写
8.字符串是用双引号(”“)将一串字符括起来就是一个字符串,不是使用单引号,单引号是原子,比如:”hello“,Erlang中其实并没有字符串,实际上就是一个整数列表,可以使用$来表示字符的整数值,$a实际上就是一个整数,表示字符a。
9. % 和 %% Erlang注释
10. 在Erlang中,任何可以被求出值的东西都被称作表达式。这意味着catch,if,try...catch等都是表达式,而类型记录和模块属性等,这些不能被求值的都不是表达式。
11. help().查看Erlang编译器的输出功能列表。
第三章 顺序型编程
模块是Erlang的最基本单元,我们编写的所有函数都存在于模块之中,模块文件通常存放在以*.erl的文件中,编译成功后为*.beam,
1. 函数,多个作用的同名函数由多个子句组成,子句间以分号分隔,最后一个以句点结束,每个子句都是一个函数,通过调用参数从上到下进行匹配,如果参数为空列表的,只能匹配空列表函数,有些首参数直接就以原子作为标识。函数调用匹配失败直接抛出一个错误。
2. pwd()可以打印当前的工作目录,cd("c:/work")可以将当前的目录切换到指定的目录
3. 分号小结:
”,“ :用来分隔函数调用、数据构造器以及模式中的参数。
”. “ :后跟一个空白符号,用于分隔完整的函数和表示达。
”; “ :用于分隔子句,几种情况下会用到子句:分段函数的子句,case子句,if子句,try...catch子句,receive子句。
4. fun是一个匿名函数,可以赋给变量,同样支持传递参数,以end作为结尾,fun也可以有若干个不同的子句:
TT = fun({c, C}) -> {f, 32 + C * 9 / 5};
({f, F}) -> {c, (F - 32) * 5 / 9}
end.
fun可以作为函数参数,也可以作为函数的结果,这些 能够返回fun 或 接受fun作为参数的函数,称谓 高阶函数
(1). Even = fun(X) -> ( X rem 2) =:= 0 end.
list:map( Even, [1,2,3,4,5,8]).
(2).Mult = fun(Times) -> ( fun(X) -> X * Times end ) end.
TT = Mult(3). 返回TT = fun(x) -> X * 3 end.
TT(5). 输出15
5. List的两个重点函数:
list:map( Even, [1,2,3,4,5,8]). 将Even应用到列表中的每一个元素中,完成后返回一个新的列表保存了每个列 表元素执行Even返回的值。
list:filter(Even, [1,2,3,4,5,8]). 返回一个新的列表,新列表由列表参数中每一个可能满足Event的元素组成。
list:seq(1, N)返回一个由1到N整数组成的列表
6. for循环
for(Max, Max, F) -> [F(Max)];
for(I, Max, F) -> [F(I) | for(I + 1, Max,F)].
7. sum求和
sum([H | T]) -> H + sum(T);
sum([]) -> 0.
8. map遍历
map(_, []) -> [];
map(F, [H | T]) -> [F(H) | map(F, T)].
9. -import(lists, [map/2, sum/1]) 引用其它模块的函数,这样就减少模块引用名如:list:map, 引用后直接用map
-export([total/1]) 输出函数,让其它模块可以调用当前函数
10. 列表解析 [ F(X) || X <- L ] 代表由F(X)组成的列表,其中X是取值于列表L。如:[ 2 * X || X <- L ].意味着列表L中 的每一个元素X乘以2后的列表。
[ {Name, 2 * Number} || {Name, Number} <- Buy], 记号(||)右边的元组{Name,Number}是用于匹配 列表Buy中的每个元素,左边的元组{Name,2 * Number}则是一个构造器。
11. [1,2,3] ++ [4] ++ [5,6] -> [1,2,3,4,5,6]. "++"是一个中缀添加操作符, ++ 和 --是对列表进行添加和删除的中缀 操作符,两边操作的均为列表,
用于模式匹配时,f("begin" ++ T) -> ...., 被扩展为[$b, $e, $g, $i, $n | T].
12. X rem Y ,X除Y取余数,6 rem 2 =:= 0
13. When
max(X, Y) when X > Y -> X; When和判断条件是为函数头
max(X, Y) -> Y.
max(X, Y) when is_integer(X), X > Y, Y < 6 -> X. When的多条件判断,用逗号隔开,表示逻辑与&&操作
max(X, Y) when X =:= dog; X =:=cat -> X. When的多条件判断,用分号隔开,表示逻辑或||操作
14. and(&)【逻辑与】,
andalso(&&) 与
or(|) 【逻辑或】 ,
orelse(||),
not B1 【逻辑非】,
B1 xor B2【逻辑异或】
15. case表达式
filter(P, [H | T]) ->
case P(H) of
true -> true;
false-> false;
filter(P, [H | T]) ->
case P(H) of
1-> true;
0-> false;
16. if表达式
if
X > Y -> X;
X < Y -> Y;
end.
17. 记录 记录就是元组,只是元组的伪装,
-record(todo, {status=reminder,who=joe,text}).
创建或更新:X1 = #todo{}. 或者 X1 = #todo{status=urgent, text="test"}.
提取: #todo{who = W, text = Txt} = X1. 模式匹配可取得 W 和 Txt
释放: rf(todo).
第四章 异常
1. demo1() -> [ catcher(I) || I <- [1,2,3,4,5]].
catcher(N) ->
try generate_exception(N) of
0 -> false;
1 -> true;
catch
throw:X -> {N, caught, thrown, X};
exit :X -> {N, caught, exited, X};
error :X -> {N, caught, error, X}
end.
after
io:format("after").
end.
2. erlang:error的另一个用处是提高错误信息的质量,抛出一个自定义错误,等于Java的throws
3.捕获所有异常:
try generate_exception(N)
catch
_:_ -> {erlang:get_stackstrace()}
end.
第五章 顺序型编程进阶
1. BIF 内建函数,是Erlang内建的函数,启动Erlang时自动导入的函数,通常用来完成那些无法用Erlang完成的任务。如:tuple_to_list/1能将元组转换为列表, time/0返回当前时间的时、分、秒。
2. 二进制binary,以一个整数或者字符序列的形式出现,用<<>>括起来,如:<<5,10,20>>. 和 <<"hello">>.
对于字符串来说,如果其二进制数据是可打印的字符串,那么在显示 时,会将其显示为字符串的形式,否则 ,就会显示成一串整数。<<"cat">>等同于<<99,97.116>>.
3. 操作二进制数据的BIF:@spec类型文档标记
@spec list_ to_binary(IoList) -> binary() 将列表中的所有数据转换成二进制 数据 。
@spec split_binary(Bin, Pos) -> {Bin1, Bin2}. 在Pos指定的位置将二进制数据Bin分割为两个部分。
@spec term_to_binary(Term) -> Bin. 可以将任何Erlang值转化为二进制 数据 。
@spec binary_to_term(Bin) -> Term. 这个是term_to_binary的逆函数
4. Byte比特语法,是模式匹配的一种扩展,Erlang的看家本领。M = <<X:3, Y:7, Z:6>>.表示X占3Bit, Y占7Bit, Z占6Bit。
操作:Red=2. Green = 61. Blue = 20. Mem = <<Red:5, Green:6, Blue:5>>. 输入<<23,180>>.
Red占5位, Green占6位, Blue占5位,其它Red和Green、Blue分别也代表某数据
提取:<<R1:5, G1:6, B1:5>> = Mem. 可得出R1为2, G1为61, B1为20
比特语法的表达式:无论采用哪个形式,总比特数必须恰好能被8整除,因为二进制数据每个字节都是8Bit
Ei = Value |
Value:Size |
Value / TypeSpecifierList |
Value:Size / TypeSpecifierList
TypeSpecifierList 列表项有以下值 @type End = big(默认) | little | native(运行时决定的字节序)
5. BIF的apply(Mod, Func, [Arg1, Arg2,......, ArgN]) 将Mod模块中的Func函数应用到参数Arg1, Arg2. . . ArgN上 去。等同于Mod:Func(Arg1, Arg2, ... , ArgN). 尽量少用。
6. - module(modname). 预定义模块属性,必须放在所有函数之前,模块名称必须是一个原子。
- import (Mod, [Func1/1, Func2/2,......]). 从Mod模块中导入指定参数和函数名的函数。
- export ([Func/1, Func2/2,......]). 从当前模块中导出Func指定函数,只有被导出的函数才能在模块外部调用。
- compile(Options). 向编译器选项列表中添加Options,Options是一个编译器选项或者一个编译器选项列表。
- vsn(Version). 指定一个模块的版本。
- sometag(Value). 用户自定义属性,sometag必须为一个原子,Value必须是一个文字项,可在运行时提取
- include(FileName). 包含文件如:-include("jj/file.hrl").
7. Mod:module_info() 可打印输出当前模块的信息。
8. 块表达式, 可以把一串表达式组织成一个类似子句的实体,当某处只允许使用单个表达式而实际需要使用多个表达式时, 可以使用块表达式。
begin
expr1,
expr2,
.......
end
9. Erlang没有独立的布尔类型,原子true 和 false取而代之被赋予了特殊的布尔语义,作为符号使用
10. epp, 在Erlang模块被编译之前,会被epp的预处理器进行自动处理,并且插入任何必须的包含文件,保存预处理器的 输出,用命令compile:file(M, ['P']),编译M.erl文件的所有代码,在M.P中产生一个列表,存放包含文件。
11.转义字符:同Java一样
io:format("~w~n", ["\b\n"]). ~w的意思是对一个列表不做任何修饰处理,直接打印原始结果,无转义。~p是完 整打印的缩写。
12. 宏定义
-undef(Macro) 取消此宏定义,这个语句后不能再调用这个宏。
-ifdef (Macro) 只有Macro被定义后,才对该行以下的代码进行运算。
-ifndef(Macro) 只有Macro未定义后,才对该行以下的代码进行运算。
-else . 只能在ifdef或ifndef之后出现,如果条件为false, 就会执行该语句后的代码
-endif. 标记ifdef 和 ifndef语句的结束。
例如:
-export([start/0)].
-ifdef(debug).
-define(TRACE(X), io:format("TRACE ~P:~P ~P~N",[?MODULE, ?LINE, X])).
-else
-define(TRACE(X),void).
-endif.
start() -> loop(5).
loop(0) -> void;
loop(N) -> ?TRACE(N), loop(N - 1).
13.在模式中使用匹配操作符
func1([{tag1, A, B} = Z] | T) ->
........
f(..., Z, ...). Z在函数中重复使用,可以把{tag1, A, B}模式写成一个Z,再重复使用
14.数值类型
K进制整数:不以10进制的整数可以用语法K#Digits来表示,如:16#af6bfa23, 这种语法所能表示的最高进制为 36进制。
$语法 :语法$表示ASCII字符的整数值,如:$a是97.
15.进程字典
Erlang的每个进程都有自己的私有数据存储,叫做进程字典,是一系列的键值对应的关联数组,一个键对应一个值
@spec put(Key, Value) -> OldValue. 返回值为前一个关联值,如果之前没有则返回undefined.
@spec get(Key) -> Value. 查找Key对应的值,没有时返回undefined.
@spec get() -> [{Key, Value}].以{Key,Value}元组列表的形式返回整个字典。
@spec get_keys(Value)->[Key]. 返回字典中值为Value的键的列表。
@spec erase(Key) -> Value. 存在Key对应的值,返回再删除Key的值,否则返回undefined.
@spec arase() -> [{Key, Value}]. 返回全部列表元组{Key,Value},同时删除全部列表。
16.引用 erlang:make_ref()用于创建引用。
17.比较表达式
/= 不等于
=:= 全等于
=/= 不全等于
=< 小于等于
>= 大于等于
_VarName 以下划线开始的变量,编译器不会产生警告信息。open(_Mode)比open(_)更具有可读性。
第八章 并发编程
1. 创建新的并发进程:Pid = spawn(Fun) 如:Pid = spawn(fun area_server0:loop/0). 函数内有表达式 receive,当向这个进程发送数据时,执行当前的注册函数,并找到receive进行处理。
向进程发送消息 :Pid !Message , Pid1 ! Pid2 !Pid3 !M,把消息M发送到Pid1 和 Pid2 和 Pid3各个进 程,“!”是一个发送符号,表示把右边的数据发送到左边的Pid去。
接收一个发给当前进程的消息:
(1)receive
pattern1 [when Guard1] -> expressions1;
pattern2 [when Guard2] -> expressions2;
.......
end.
(2)receive 带超时的
pattern1 [when Guard1] -> expressions1;
pattern2 [when Guard2] -> expressions2;
.......
after Time ->
Expressions
end.
(3)sleep(T) -> 带延时的
receive
pattern1 [when Guard1] -> expressions1;
pattern2 [when Guard2] -> expressions2;
.......
after T -> true
end.
(4)receive 一个越时时间为0的语句会立即触发一个超时。只有在所有消息都进行过匹配之后才会检查after段是否 需要进行运算。
pattern1 [when Guard1] -> expressions1;
pattern2 [when Guard2] -> expressions2;
.......
after 0 ->
receive
Any ->
Any
end
end.
(5)无限等待超时进行接收,人为控制超时
如果receive的超时值被设定为原子,那么系统永远都不会触发超时,
(6)可以使用接收超时来实现一个简单的计时器, 在指定Time时间后对Fun进行求值
receive 带超时的
pattern1 [when Guard1] -> expressions1;
pattern2 [when Guard2] -> expressions2;
.......
after Time ->
Fun()
end.
(7)选择性接收
Erlang的每一个进程都有与之对应的邮箱,当向进程改送消息时,消息就被送入邮箱之中,当系统对receive 语句进行求值时,就是对进程邮箱进行检查的唯一机会。
receive
pattern1 [when Guard1] -> expressions1;
pattern2 [when Guard2] -> expressions2;
.......
after Time ->
ExpressionTimeOut
end.
2. Send 和 receive的工作原理:
Erlang的每一个进程都有与之对应的邮箱,当向进程改送消息时,消息就被送入邮箱之中,当系统对receive 语句进行求值时,就是对进程邮箱进行检查的唯一机会。
(1). 当进入一个receive语句时,我们启动一个计时器(只有在表达式中存在after段时才会计时)。
(2). 从邮箱中取出第一个消息,然后尝试对Pattern1、Pattern2等进行模式匹配。如果匹配成功,消息就从邮箱中 删除,对应的模式之后的表达式就会被求值。
(3). 如果邮箱中的第一个消息不能匹配receive语句的任何一个模式,那么就会将第一个消息从邮箱中删除并送往一 个“保存队列”,然后继续尝试邮箱中的第二个消息。这个过程会不断重复直到找到匹配的消息,或者邮箱中所 有的消息全都检查完毕。
(4). 如果邮箱中的所有的消息都不能匹配,那么就挂起进程,直到下一次又有新的消息进入邮箱时再对该进程进行重 新调度执行。注意,当一个新消息到达时,只对新消息进行匹配而不会对保存队列中的消息进行再次匹配。
(5). 一个消息如果被匹配,那么存入保存队列中的所有消息就会按照它们到达进程的时间先后顺序重新放回到邮箱 中。这时,如果开启了一个计时器,那么这个计时器就会被清空。
(6). 如果在我们等待一个消息时触发了计时器,那么对表达式ExpressionsTimeOut求值然后把存入保存队列中的 所有消息按照它们到达进程的时间先后顺序重新放回到邮箱中。
3. self(). 返回当前进程的Pid.
4. erlang:system_info(process_limit) 用来查找系统允许的进程数上限,要修改Erlang虚拟机的进程数必须在启动Erlang虚拟机时带参数+P, 如:erl +P 500000, 程序内控制processes:max(20000). 但不能超出虚拟机的最大数。
5. 注册进程,Erlang中有4个BIF用于管理注册进程。
register( AnAtom, Pid ) 将一个进程Pid注册一个名为AnAtom的原子
unregister( AnAtom ) 移除与AnAtom相对应进程的所有注册信息。
whereis( AnAtom ) -> Pid【存在时】 | undefined【不存在时】
registered() -> [ AnAtom :: atom() ] 返回一个系统中所有已经注册的名称列表。
1>Pid = spawn(fun area_server0:loop/0).
2>register(area, Pid).
3>area ! { rectangle, 4, 5 }.
6. 尾递归技术 在receive里面不断调用当前的函数,这个过程叫做尾递归
7. 使用MFA启动进程
我们想要编写那些可以运行时更新的代码,如果想要确保代码可以被很好地进行动态更新,那么就必须使用不同形式的spawn, spawn(Mod, FuncName, Args). MFA分别代表三个参数,Args是一个参数列表,可以进行热更新。
第九章 并发编译错误处理
1.当一个进程退出时想执行某些动作。可以编写一个函数如:on_exit(Pid, Fun), 它会创建一个指向Pid进程的链接。如果Pid由于消亡(其原因在变量Why中),那么这个函数就会执行函数Fun(Why)。这样可以在一个与当前进程无关的进程中进行这些处理,这些进程甚至不需要在同一台机器上,分布式的Erlang会用到。
on_exit(Pid, Fun) ->
spawn(fun() ->
process_flag(trap_exit, true), 把创建的进程变为一个系统进程
link(Pid), 把新建的进程与Pid进程相链接(监视)
receive 当Pid消亡时,收到信号,执行处理
{'EXIT', Pid, Why} -> Fun(Why)
end
end).
link:是一种在两个进程之间的传播路径,如果两个进程被链接在一起,若其中一个进程消亡,那么系统就会向另一 个进程改送一个退出信号,一群与某个给定的进程进行链接的进程集合称为该进程的链接集。
exit signal:当一个进程消亡时,它会产生一个叫做退出信号的东西,系统会向这个濒死进程的链接集的所有进程发 送退出信号。
system process:当进程接收到一个非正常的退出信号时它自己也会消亡,除非它是特殊类型的进程即系统进程。 我们可以用BIF process_flag(trap_exit, true)来把一个普通进程转换为一个可以捕获退出信号的系统进程。
2. 捕获退出的编程模式
(1). 不管创建的进程是否崩溃 Pid = spawn(fun() -> ... end).
(2). 新创建的进程崩溃我也跟着自行消亡 Pid = spawn_link(fun() -> ... end). 并行进程
(3). 新创建的进程崩溃我需要处理错误。 这种情况就要使用spawn_link 和 trap_exits
process_flag(trap_exit, true).
Pid = spawn_link(fun() -> ... end),
......
loop(...).
loop(State) ->
receive
{'EXIT', SomePid, Reason} ->
%% do something with the error
end
3. 错误处理原语
@spec spawn_link(Fun) -> Pid 是一个原子操作,它除了创建进程外,还会在这两个进程之间创建链接。
@spec process_flag(trap_exit, true) 这个把当前进程转换为系统进程,系统进程可以接收和处理退出信号。
@spec link(Pid) -> true 如果Pid所指定的进程还没有链接,那么给这个进程创建一个链接,链接是双向的,如 果一个进程A运行了Link(B), 那么它自身也会被链接到B,等同于B运行了A。
@spec unlink(Pid) -> true 这会移除当前进程和Pid所指进程间的链接
@spec exit(Why) -> none() 这会导致当前进程终止,并把终止原因设为Why,向链接集中广播。
4. 存活进程 消亡的时候,立刻重启
keep_alive(Name, Fun) ->
register(Name, Pid = spawn(Fun)),
on_exit(Pid, fun(_Why) -> keep_alive(Name, Fun) end).
第十章 分布式编程
1. 在终端上启动一个名为TT的Erlang节点,在这个节点上启动服务:erl -sname TT , 节点显示为:TT@ZT-PC。
2. rpc:call ( Node, Mod, Func, [ Arg1, Arg2, ... , ArgN ] ) , 在Node上执行一个远程调用,被调用的函数是
Mod:Func(Arg1, Arg2, ... , ArgN ).
3. 运行于不同节点的客户端和服务器架构
在bilbo节点上
erl -sname bilbo.
rpc:call ( gandalf@localhost, kvs, store, [weather, fine] ).
在gandalf节点上
kvs.lookup ( weather ) . 输入 {ok, fine}
4. 运行于同一局域网内的不同机器上, 确保再点之间使用相同的Cookie
在gandalf 上启动一个Erlang节点,以提供其它服务器调用
erl -name gandalf -setcookie abc
标记符号变为:(gandalf@doris. myerl. example. com)
在bilbo上启动一个Erlang节点并向gandalf发送一些命令
erl -name bilbo -setcookie abc
(bilbo@george. myerl. example. com) 1>
rpc:call ( gandalf@doris.myerl.example.com, kvs, store,[weather, cold]).
返回true.
5. 分布式的原语
@spec spawn ( Node, Fun ) -> Pid 这个原语和spawn(Fun)没有多大的区别,但新创建的进程位于Node节 点上。
@spec spawn ( Node, Mod, Func, ArgList) -> Pid 新创建的节点MFA进程位于Node节点上。
@spec spawn_link ( Node, Fun ) -> Pid 和spawn_link(Fun)没大区别,新创建的进程会Node节点上。
@spec spawn_link ( Node, Mod, Func, ArgList ) -> Pid 和spawn(Node, Mod, Func, ArgList)没多大的 区别,只是新创建的进程与当前进程互相链接。
@spec disconnect_node(Node) -> bool() | ignored 强制断开一个节点的链接。
@spec monitor_node(Node, Flag) -> true Flag为true就会打开监视,Flag为false就会关闭监视。
@spec node() -> Node 返回本地节点的名称。
@spec node(Arg) -> Node 这个原语会返回Arg所指定的节点,Arg可以是Pid、一个引用 或者一个端口,本地节点不是分布式返回nonode@nonode
@spec nodes() -> [Node] 返回网络上与当前节点连接的所有其它节点列表。
@spec is_alive() -> bool() 本地节点状态正常而且是分布式的系统,则返回true.
6. 分布式库
rpc模块提供了一系列远程调用服务。
global模块提供了名称注册函数和分布式系统中的锁定功能,完整的网络连接维护函数。
7. cookie设置
(1). 把cookie存放在$HOME/.erlang.cookie文件中。这个文件包含了一个随机的字符串,在你每一次运行该机器 Erlang时自动生成的。
(2). 启动时设置Cookie, erl -setcookie ASDFSDFSADFASDFSREWRWER....
(3). 使用BIF erlang:set_cookie(node(), C)把本地节点的cookie设置为原子C .
第十二章 接口技术
1. 创建端口:Port = open_port ( PortName, PortSettings) // 多种参数方式
发送数据:Port !{PidC, {command, Data}}
接收数据:receive {Port, {data, Data}} -> ...... Data comes from the external process ....
关闭端口:Port !{PidC, close}
第十三章 文件编程
1. Erlang把操作文件的函数编排在4个不同的模块中
(1). file。 这个模块包含了用于文件打开、关闭、读取、写入和目录列表等功能的函数。
(2). filename:一套操作文件名的函数。
(3). filelib:file模块的扩展,提供了一套辅助函数用于生成文件列表、检验文件类型等操作。
(4). io:提供了一系列对已打开的文件进行操作的函数。
2.读取文件
(1). file:consult ( "data1.dat" ). 全部读取,读取成功返回{ok, [Term]}, 否则,就会返回{error, Reason}。
(2). 顺序一次读一项
{ok, S} = file:open ( "data1.dat", read ).
io:read(S, '' ). 读出一项,结束时返回eof
file:close(S).
(3). 从文件中一次读取一行数据
{ok, S} = file:open ( "data1.dat", read ).
io:get_line(S, ''). 结束时返回eof。
file:close(S).
(4). 将整个文件读入到一个二进制数据中
file:read_file ( "data1.dat" ).
把新的数据存回到文件中去file:write_file ().
成功返回{ok, Bin}, 失败返回{error, Why}.
(5). 随机读取一个文件
{ok , S} = file:open("data1.dat", [read, binary, raw]).
file:pread(S, 22, 46). 从22个字节开始,读取长度为46字节的数据
file:pread(S, 1, 10).
file:close(S).
3. 写入文件的方法
(1). 向一个文件中写入一串Erlang数据项 @spec io:format(IoDevice, Format, Args) -> ok.
{ok, S} = file:open(File, write),
lists:foreach ( fun(X) -> io:format(S, "~p.~n", [X]) end, L ) ,
file:close(S).
(2). 向文件中写入一行
{ok, S} = file:open("test2.dat", write).
io:format(S, "~s~n", ["Hello readers"]).
file:close(S).
(3). 一步写入整个文件
writefiles(File, Data) -> file:write_file(File, Data).
(4). 在随机访问模式下写入文件
{ok, S} = file:open("...", [raw, write, binary]).
file:pwrite(S, 10, <<"new">>).
file:close(S).
4.目录操作
(1). list_dir(Dir) 用于生成Dir目录下的文件列表。
(2). make_dir(Dir) 用来创建一个新的目录
(3). del_dir(Dir) 用来删除目录。
5. 查询文件属性 file:read_file_info(F). F是一个合法的文件名或者目录名,那么这个函数会返回{ok, Info},Info是一个 类型为#file_info的记录。
6.复制和删除文件
file:copy(Source, Des) 将源文件Source复制到目标文件Des
file:delete(File). 删除文件
第十四章 Socket编程
1. 打开:{ok, Socket} = gen_tcp:connect ( Address, Port, Options ), 或 gen_tcp:listen ( Port, Options ) Options是模式 如:[ binary, {packet, 0} ], 以binary的模式打开,打开二进制传输格式。
发送: ok = gen_tcp:send ( Socket , "asdfasdfasdf"), receive_data(Socket, []). 把消息发送到Socket上, 然后等待回应,回应消息通常 不会在一个数据包内全部返回,而是会一帧一帧地返回,每帧一部分数 据。程序以顺序型消息的方式收到这些数据帧并把它转发给打开Socket的进程。
回应: {tcp, Socket, Bin} 第三个参数为二进制类型,这是我们为何以二进制模型打开套接字的原因所在,这个消息 只是Web服务器发送过来的数据帧之一,程序会把它添加到一个列表中,然后继续接收下一帧。
关闭:{tcp_closed, Socket}, 这表明Web服务器已经停止向程序发送数据
nano_get_url() ->
{ok, Socket} = gen_tcp:connect ( "www.google.com", 80, [binary, {packet, 0}]),
ok = gen_tcp:send ( Socket, "sdfsdfsdf"),
receive_data ( Socket, [] ).
receive_data ( Socket, SoFar ) ->
receive
{tcp, Socket, Bin } ->
receive_data( Socket, [Bin | SoFar] );
{tcp_closed, Socket} ->
list_to_binary ( reverse(SoFar))
end.
2. 一个二进制数据如果太长会被截断,可以使用io:format 或 string:tokens 来将它们切成小的片段
3. 一个完整的TCP服务器
start_nano_server() ->
{ok, Listen} = gen_tcp:accept(2345,
[binary, {packet, 4}, {reuseaddr, true}, {active, true}]).
{ok, Socket} = gen_tcp:accept(Listen),
gen_tcp:close(Listen),
loop(Socket).
loop(Socket) ->
receive
{tcp, Socket, Bin} ->
str = binary_to_temp(Bin),
gen_tcp:send(Socket, term_to_binary ( str )),
loop(Socket);
{tcp_closed, Socket} ->
io:format ( "Server socket closed~n" )
end.
4. 顺序型服务器
start_seq_server() ->
{ok, Listen} = gen_tcp:listen( ... ),
seq_loop(Listen).
seq_loop(Listen) ->
{ok, Socket} = gen_tcp:accept ( Listen ),
loop ( Socket ),
seq_loop(Listen).
loop(...) -> %% as before
5.并发服务器 当接收到一个新的连接的时候启动一个新进程处理。
start_parallel_server() ->
{ok, Listen} = gen_tcp:listen( ... ),
spawn ( fun() -> par_connect(Listen) end ).
par_connect(Listen) ->
{ok, Socket} = gen_tcp:accept(Listen),
spawn( fun() -> par_connect(Listen) end).
loop(Socket).
loop(...) -> %% as before
6.Erlang的套接字打开模式
(1). acitve 主动型消息接收(非阻塞) {active, true}
(2). active once 被动型消息接收(阻塞) {active, false}
(3). passive 混合型消息接收(半阻塞) {active, once}, 需要调用inet:setopts重新激活下一个消息。
7. Socket的错误处理
每个Socket都有一个控制进程,如果控制进程消亡,那么Socket会自动关闭。
8.UDP的服务器和客户机
server(Port) ->
{ok, Socket} -> gen_udp:open(Port, [ binary ] ),
loop(Socket).
loop(Socket) ->
receive
{udp, Socket, Host, Port, Bin} ->
BinReply = ... ,
gen_udp:send( Socket, Host, Port, BinReply ),
loop(Socket)
end.
client(Request) ->
{ok, Socket} = gen_udp:open(0, [binary]),
ok = gen_udp:send(Socket, "localhost", 4000, Request),
Value = receive
{udp, Socket, _, _, Bin } -> {ok, Bin}
after 2000 -> error
end.
gen_udp:close(Socket),
Value
UDP的协议传输是不可靠的,有可能会收不到来自服务器的回应。
9. 一个Udp的数据报可能被传输再次,加其加入一个唯一的引用,然后在服务器返回时去校验这个引用。Erlang的BIF make_ref可以保证引用的唯一性,这个保证会返回全球唯一的引用。
client(Request) ->
{ok, Scoket} = gen_udp:open(0, [binary] ),
Ref = make_ref(),
B1 = term_to_binary({Ref, Request}),
ok = gen_udp:send(Socket, "localhost", 4000, B1),
wait_for_ref(Socket, Ref).
wait_for_ref(Socket, Ref) ->
receive
{udp, Socket, _, _, Bin} ->
case binary_to_term(Bin) of
{Ref, Val} -> Val;
{_SomeOtherRef, _} -> wait_for_ref(Socket, Ref)
end;
after 1000 -> .......
end.
10. 向多台机器广播消息
send(IoList) ->
case inet:ifget("eth0", [broadaddr]) of
{ok, [{broadaddr, Ip}]} ->
{ok, S} = gen_udp:open(5010, [{broadcast, true}]),
gen_udp:send(S, Ip, 6000, IoList),
gen_udp:close(S);
_ ->
io:format("Bad interface name, or\nbroadcasting not supported\n")
end.
listen() ->
{ok, S} = gen_udp:open(6000),
loop(S).
loop(S) ->
receive
Any ->
io:format("receive:~p~n", [Any]),
loop(S)
end.
第十五章 ETS 和 DETS 大量数据的存储机制
ETS 和 DETS是Erlang用于高效存储大量Erlang数据条目的系统模块,ETS是Erlang Term Storage 的缩写,而
DETS是Disk Ets的缩写,它们提供大型的“键 - 值” 搜索表,ETS所不同的是留在内存,DETS驻留在磁盘上。
1. 创建:ets : new(name, [Opt]). name是一个原子, Opts是表的类型
插入:insert(Tablename, X) X可以是一个元组或者元组的列表。在ETS和DETS中,insert的参数和行为都一样
查找:lookup(Tablename, Key), 其返回的结果是匹配Key的元组列表。返回元组是因为可以有多个元组使用相同的 键,如果没有匹配,则返回一个空列表。
释放:用完一个表,可以调用dets:close(TableId) 或 ets:delete(TableId)来告诉系统释放这个表。
2.表的类型,总共有4种:set 、ordered set、bag、duplicate bag 和 private 、public 、protected、 named_table、{keypos, K}
set: 表中每一个元组的键值都不能相同。
ordered set: 在Set的基础上,元组会进行排序。
bag: 多个元组可以用相同的键值,但不能有两个完全相同的元组。
duplicate bag:不仅多个元组可以有相同的键值,同一个元组也可以在表中出现多次。
3. ETS小程序
start() ->
lists:foreach( fun test_ets/1, [set, ordered_set, bag, duplicate_bag]).
test_ets(Mode) ->
TableId = ets:new(test, [Mode]),
ets:insert(TableId, {a, 1}),
List = ets:tab2list(TableId),
io:format("~-13W => ~p~n", [Mode, List]),
ets:delete(TableId).
第十六章 OTP概述 【框架】 —Erlang的进化
OTP即Open Telecom Platform 电信开放平台, 它是一个应用程序操作系统,包括了大量库和程序用来构建大规模的分布式容错系统。OTP的设计目标是用来构造容错系统。
OTP最核心的概念是行为,一个行为封装某种常见的行为模式,这些行为可以理解为某种应用程序框架,可以通过回调模块来定制这些框架。
首先来仔细研究行为中的一种—gen_server。
(1)用Erlang写一个小的客户/服务器程序。
(2)慢慢抽离这个程序的通用部分,并增加一些特性。