Erlang学习总结
安装,启动
基本语法
变量
保存命令的结果 X=1234565, Y = X*X*X,
erlang变量不变,单一赋值,一个变量已经赋值,叫绑定变量,否则 ,称自由变量。
整数
浮点数
必须含有小数点,且小数点后至少有一位十进制数。“/”永远返回浮点数,N
div M,用于整数除,N rem M,取余数
原子
用来表示不同的非数字常量值。是一串以小写字母开头,后跟数字字母或下划线或@的字符。例如:red,cat,joe@something,a_long_name.使用单引号引起来的也是原子,如”helloworld”
一个原子的值就是自身。
元组
将若干以逗号分割的值用一对花括号括起来。如{joe,1.82}
从元组提取字段值:
Point = {point, 10, 45}.
{point, X, Y} = Point.
X:10,Y,45
_做为匹配的占位符
列表
以逗号分隔的值,用方括号括起来。如:[1,2,3,4,5]。
列表中第一个元素为列表头,剩下的为列表的尾,,访问列表的头是一个非常高效的操作。
如果T是一个列表,则[H|T]也是一个列表。|是将列表的头和尾分隔开。
向列表增加元素[E1,E2,E3 |T]
从列表中提取元素L = [1,2,3,4,5],
[X|Y] = L,
X:1
Y:[2,3,4,5]
字符串
Name = “hello”.
记录
记录可以理解成就是元组,
当我们使用元组时,一旦元组的数量变得非常庞大,我们会很难记住元组中每一个元素的确切含义,记录就是提供了一种方法把一个名字与元组中的一个元素对应起来,从而解决这一烦恼。
模式匹配
=是一个模式匹配运算符。Lhs = Rhs,表示对右端求值,将结果与左端进行模式匹配。
X=123,将值123绑定到X.
L = [1,2,3].
[X,Y,Z] = L.
X的值1
控制语句
If
V1-> do(1);
V2-> do(2)
end
case XXX of
V1-> do(1);
V2-> do(2).
end
无循环控制语句,替代方案:尾递归
loop() ->
receive
{From, {store, Key, Value}} ->
put(Key, Value),
io:format("store:~p,~p~n",[Key,Value]),
From ! {kvs, true},
loop();
{From, {lookup, Key}} ->
From ! {kvs, get(Key)},
io:format("lookup:~p,~n",[Key]),
loop()
end.
模块
-module(modulename).
函数
同名不同目的函数,两个函数完全不同
无return 语句
异常
系统遇到错误时,就会自动地抛出异常,常见的错误:
1. 模式匹配失败
2. 调用BIF时,传入错误的类型参数
3. 调用内建的异常函数显示抛出异常:exit(Why), throw(Why), erlang:error(Why)
try generate_exception() of
Val-> do some thing
catch
throw:X->…..
exit:X->….
error:X->…
after
Afterxpressions
end.
After后面的代码用于执行完generate_exception表达式后的清理工作,无论是否抛出异常,这段代码都必须执行。after中的返回值被自动舍弃。
并发编程
进程
使用erlang编写的应用运行时通常时成千上万个轻量级进程组成,并通过消息传递互相通讯。进程间上下文切换对于erlang来说,比C、java程序切换要高效得多。
创建进程,
spawn(fun()-> loop() end).
进程间收发消息
注册进程
register(AnAtom, Pid)
移除
unregister(AnAtom)
进程间通讯
Pid ! “Message”
链接进程
link(Pid)
unlink(Pid)
如果一个进程消亡,系统会向另一个进程发送退出信号。
监督进程
分布式编程
分布机制是透明的,对于程序来说,并不知道自己是在分布运行。利用结节的概念,整个系统的各种服务可以放在1 台机器上,也可以分布在多台机器,程序不需要改动,唯一改动的是节点名。
启动节点
erl –snamenode_a –setcookie 123
调用另一节点上的方法
rpc:call(节点名, 模块名,函数, [参数])
在远程结点启动进程
spawn(Node, fun() ->loop() end).需要把程序复制到所有节点上。dist_demo
向其他结点进程发送消息
{Node, Pid} ! “Message”.
ETS内存表,Mnesia数据库
Dist, ETS, Mnesia关系:
速度Dist > ETS >Mnesia
Dist只能在当前进程使用
ETS可以在多个进程间调用。
Mnesia可以跨越节点,分布式调用
ETS一般用于保存在线列表,缓存数据,Key->Value结果
Mnesia适合用于数据库存储,可直接存储Erlang数据结构,可建多个索引,并支持分片技术,可让数据均匀地分布在多个节点上。
复制是使Mnesia成为一个容错数据库管理系统的机制之一。数据表可以被复制到混合网络中的多个节点,而对应用来说却是透明的。被复制的数据表是对等的,也就是说没有主-从结构。
JAVA接口
启动节点
OtpNodenode = newOtpNode("test","123",8888);
OtpMboxmbox = node.createMbox();
mbox.registerName("java");
接收erlang节点发送的消息
OtpErlangObjectobject = mbox.receive();
回复erlang节点的消息
完整代码
publicstaticvoid main(String[] args) throws IOException,OtpErlangExit, OtpErlangDecodeException {
OtpNodenode = newOtpNode("test","123",8888);
OtpMboxmbox = node.createMbox();
mbox.registerName("java");
System.out.println("start to Listen......");
while(true) {
OtpErlangObjectobject = mbox.receive();
if(object instanceof OtpErlangTuple){
OtpErlangTupletuple = (OtpErlangTuple) object;
int arity =tuple.arity();
OtpErlangPidpid = (OtpErlangPid) tuple.elementAt(0);
System.out.println(pid);
mbox.send(pid,newOtpErlangString("I'm node-java"));
}
System.out.println(object);
}
}
集群节点管理
节点特性:
全连接
Mnesia数据库
启动节点
启动一个Erlang 节点并指定Mnesia数据库目录
erl -sname c -mnesia dir'"c:/Users/Administrator"'
初始化数据库模式(schema)
模式是一种特殊的表,它包含了诸如表名、每个表的存储类型(例如,表应该存储到RAM、硬盘或者可能是两者以及表的位置)等信息。
Ø mnesia:create_schema(NodeList). 该函数用来初始化一个新的空模式,在Mnesia启动之前这是一个强制性的必要步骤。Mnesia是一个真正分布式的数据库管理系统,而模式是一个系统表,它被复制到Mnesia系统的所有节点上。如果NodeList中某一个节点已经有模式,则该函数会失败。该函数需要NodeList中所有节点上的Mnesia都停止之后才执行。应用程序只需调用该函数一次,因为通常只需要初始化数据库模式一次。
Ø mnesia:delete_schema(DiscNodeList). 该函数在DiscNodeList节点上删除旧的模式,它也删除所有旧的表和数据。该函数需要在所有数据库节点(db_nodes)上的Mnesia都停止后才能执行。
启动Mnesia
mnesia:start().
创建表
Ø mnesia:create_table(Name, ArgList).Name表名
Ø mnesia:delete_table(Tab). 该函数永久删除表Tab的所有副本。
Ø mnesia:clear_table(Tab). 该函数永久删除表Tab的全部记录。
Ø mnesia:move_table_copy(Tab, From, To). 该函数将表Tab的拷贝从From节点移动到To节点。表的存储类型{type}被保留,这样当移动一个RAM表到另一个节点时,在新节点上也维持一个RAM表。在表移动的过程中仍然可以有事务执行读和写操作。
Ø mnesia:add_table_copy(Tab, Node, Type). 该函数在Node节点上创建Tab表的备份。Type参数必须是ram_copies、 disc_copies或者是disc_only_copies。如果我们加一个系统表schema的拷贝到某个节点上,这意味着我们要Mnesia模式也驻留在那里。这个动作扩展了组成特定Mnesia系统节点的集合。
Ø mnesia:del_table_copy(Tab, Node). 该函数在Node节点上删除Tab表的备份,当最后一个备份被删除后,表本身也被删除。
Ø change_table_copy_type(Tab, Node, ToType). 该函数改变表的存储类型。例如,将在Node节点上指定的内存类型的表Tab改为磁盘类型的表.
用远程的数据模式替换本地数据库模式
mnesia:change_config(extra_db_nodes,[a@windows7]).
查看数据库的状态信息
mnesia:info()
Mnesia特性
Ø 事务属性:原子性,一致性,隔离性,持久性
Ø 索引
Ø 分片
Ø 无盘节点
Erlang ODBC
在系统中配置ODBC
ODBC常用操作
Ø odbc:start().
{ok, Ref} = odbc:connect("DSN=name;UID=user;PWD=pwd", []).
Ø odbc:sql_query(Ref, "CREATE TABLE EMPLOYEE (NR integer, FIRSTNAME char varying(20), LASTNAME char varying(20), GENDER char(1), PRIMARY KEY(NR))").
Ø odbc:sql_query(Ref, "INSERT INTO EMPLOYEE VALUES(1, 'Jane', 'Doe', 'F')").
Ø odbc:sql_query(Ref, "SELECT * FROM EMPLOYEE").
{selected,["NR","FIRSTNAME","LASTNAME","GENDER"],
[{1,"Jane","Doe","F"},
{2,"John","Doe","M"},
{3,"Monica","Geller","F"},
{4,"Ross","Geller","M"},
{5,"Rachel","Green","F"},
{6,"Piper","Halliwell","F"},
{7,"Prue","Halliwell","F"}, {8,"Louise","Lane","F"}]]}
Ø 关闭连接
odbc:disconnect(Ref).
Ø 关闭应用
odbc:stop().
Socket
打开Socket的进程被称为控制进程,socket收到的消息都会发给控制进程,如果控制进程死掉了,那这个socket也会被关掉,可以通过 gen_tcp:controlling_process(Socket, NewPid).来修改一个socket的控制进程
Ø R11B-3 版本之后,erlang允许多个进程同时监听同一个Listen的socket,这样就可以实现创建一个pool来处理连接了
Ø erlang 的socket可以以三种模式打开active,active once,passive,可以在gen_tcp:listen,或者gen_tcp:connect的时候设置参数{active, true | false | once}
Ø active socket,当数据来的时候,控制进程会收到{tcp, Socket, Data}消息,控制进程无法控制什么时候收到这些消息,完全被动的,nonblocking
Ø passive socket,控制进程必须使用gen_tcp:recv(Socket, N)明确的指定从Socket中读取N个字节,(如果N为0的话,所有可用的数据都会读出来),读取的时候是需要等待的,这样,服务器就可以完全控制什么时候读取数据,blocking
Ø 如果客户端不停的发送消息,同时服务端处理的速度跟不上的话,服务器端将会被撑死,这就是flood攻击
• 因为服务器端无法block住客户端,因此称之为nonblocking server
• 只有在确信能支撑起客户端的需要的时候才使用这种方式
Ø 服务器端可以自由的控制在需要的时候才调用recv,因此在此之前客户端将被block住(操作系统的buffer除外)
• 因此服务器端很安全,至少程序不至于被客户端疯狂的发信息而撑死(操作系统撑死除外)
• 这种阻塞模式服务器也只能等待一个socket的数据
Ø 我们使用{active,once},这样收到消息之后,socket变成passive模式,等我们处理完消息之后,再设置socket为{active,once}
• 这样,服务端不会被客户端的数据撑死了,同时也能处理很多连接的请求了
Ø @spec inet:peername(Socket) -> {ok, {IP_Address, Port}} | {error,Why}
• 可以查看Socket的另一边是谁
• IP_Address的格式{N1,N2,N3,N4},{K1,K2,K3...K8}分别支持IPV4,IPV6,这里的N和K都是0-255的整数
Ø Socket编程的错误处理
• 不需要错误处理!
• 如果控制进程死掉了,相应的socket就会断开,另一端会收到消息{tcp_closed,Socket}
部分例子:
start() ->
{ok,LS}= gen_tcp:listen(8888,[{reuseaddr, true}, {packet, 0},
{active, false}]),
{ok, NS} = gen_tcp:accept(LS),
%%inet:setopts(NS, [{active, true}]),
%%gen_tcp:controlling_process(NS, self()),
Pid = spawn(fun() -> loop(NS) end).
%%gen_tcp:controlling_process(NS, Pid).
%%spawn(?MODULE, loop, [NS]).
%%loop(NS).
loop(Socket) ->
io:format("loop~n"),
case gen_tcp:recv(Socket, 0, 5000) of
%%receive
{tcp,Socket,Data} ->
io:format("data:~p~n",[Data]),
gen_tcp:send(Socket, Data),
loop(Socket);
{tcp_closed,Socket}->
io:format("closed~n"),
gen_tcp:close(Socket);
{ok,Data}->
io:format("data:~p~n",[Data]),
gen_tcp:send(Socket, Data),
loop(Socket);
{error,closed}->
io:format("closed~n"),
gen_tcp:close(Socket);
Data->
io:format("data:~p~n",[Data])
end.