Erlang学习笔记

目录

示例程序

阶乘

-module(fac).
-export([fac/1]).

fac(0)->
    1;
fac(N) ->
    N*fac(N-1).


on\_exit处理程序

on_exit(Pid,Fun)->
       spawn(fun() ->
                   process_flag(trap_exit,true),
                   link(Pid),
                   receive
                          {'EXIT',Pid,Why}->
                              Fun(Why)
                    end
              end).


socket\_emample

-module(socket_example).
%-import(lists,[reverse/1]).
-export([nano_get_url/0]).

nano_get_url()->
  nano_get_url("www.google.com").

nano_get_url(Host)->
  {ok,Socket}=gen_tcp:connect(Host,80,[binary,{packet,0}]),
  ok=gen_tcp:send(Socket,"GET / HTTP/1.0\r\n\r\n"),
  receive_data(Socket,[]).

receive_data(Socket,SoFar)->
  receive
    {tcp,Socket,Bin}->
      receive_data(Socket,[Bin|SoFar]);
    {tcp_closed,Socket} ->
      list_to_binary(lists:reverse(SoFar))
  end.



一个计算阶乘的UDP服务器

基本语法   语法

开始

  • ErLang的注释用%开头
  • ErLang用下划线“\_”表示任意变量
  • 整个函数定义结束用一个句号“.”;同一个函数中,并列的逻辑分支之间,用分号“;”分界;顺序语句之间,用逗号“,”分隔
  • 在erlang中模块名和方法名都是原子

if…end

%最后一个断言是true如果其他都没有匹配就执行最后一个
if
  G1 -> E1;
  G2 -> E2;
  ...
  true ->E;
end

case…of…end…

case E of
    P1 [when G1] ->E1;
    P2 [when G2] ->E2;
    ...
end

try…catch

try FOE of
    P1->E1;
    P2->E2;
catch
    EP1->EE1;
    EP2->EE2;
after
    E
end
%FOE如果正确执行会把结果匹配P1,P2..,如果错误为把结果匹配EP1,EP2..
%E无论什么情况都要执行

Erlang中任何东西都是表达式,所有表达式都有值

begin…end


%begin...end 块的值是表达式的最后一个值
%当代码的某处只允许单表达式时,可以用begin
begin
    E1,
    ...
    En
end



receive … end

receive
       P1 -> E1;
       P2 -> E2;
end
%带超时的receive
receive
       P1 -> E1;
       P2 -> E2;
after Time ->
       E
end

record

%Name 必须是原子
-record(Name, {
                 key1=Default1,
                 key2=Default2,
                 key3,
              }
       ).
%创建记录
-record(todo,{status=reminder,who=joe,text}).
X=#todo{}.
X1=#todo{status=urgent,text="Fix"}.

%从记录中提取字段
#todo{who=W,text=Txt}=X1.
W.
Txt.


define

-define(macro1(X,Y),{a,X,Y}).
foo(A)->
    ?macro1(A+10,b)

%预定义模块
%扩展为当前文件名
?FILE
%扩展为当前模块名
?MODULE
%扩展为当前行号
?LINE

-undef(M)
-ifdef(M)
-ifndef(M)
-else
-endif


BIF

关闭系统

%关闭系统
erlang:halt()
init:stop()

%一般使用init:stop()

文件加载

%获取文件的加载路径
code:get_path()

%加载路径到开始
code:add_patha(Dir)

%加载路径到末尾
code:add_pathz(Dir)

%或者可以把路径写入.erlang
%或者可以在启动是写加载路径
>erl -pa Dir1 -pa Dir2 -pz Dir3

%得到home路径
init:get_argument(home).



运行程序

erl -noshell -s hello start -s init stop

并发

Pid = spawn(Fun)
Pid ! Message

receive
       P1 -> E1;
       P2 -> E2;
end




注册进程

%将Pid注册为Name的原子
register(Name,Pid)
%
unregister(Name)

%判断原子Name是否被注册
whereis(Name)

%返回已经注册的Name
registered()

捕获进程退出

  1. 我不在乎创建的进程是否崩溃

    Pid=spawn(...)
    
    
  2. 如果我创建的进程崩溃,那我也崩溃

    Pid=spawn_link(...)
    
  3. 如果我创建的进程崩溃,我需要处理错误

    process_flag(trap_exit,true)
    Pid=spawn_link(...)
    

spawn

 Pid = spawn(Fun) 
创建并发进程对Fun求值,新创建的进程和调用者所在进程并发运行。
可以向新创建的进程Pid发送消息  Pid!M.  或者想多个进程发送消息 Pid1 ! Pid2 !...Pidn ! M .  !号为发送消息运算符

link

%A进程使用link(B)则A B 就相互监视一方消亡会向另一方发出退出信号
link(Pid)

process\_flag

%process_flag会把创建的进程变为系统进程


list\_to\_binary

split\_binary

  • 将二进制数据在指定的位置分为两个部分

term\_to\_binary

binary\_to\_term

size

make\_ref

list\_to\_atom

> L2=list_to_atom("list" ++"s").
lists
> L2:seq(1,5).
[1,2,3,4,5]

list\_to\_tuple

返回一个与列表 List 一致对应的元组,列表 List 可以包含任意的 Erlang 项(terms)。

record\_info

获取记录的字段信息
record_info(fields, wm_reqdata).
获取记录的长度信息
record_info(size, wm_reqdata).

atom\_to\_list

node

self

self() 返回当前进程的pid

nodes

is\_alive

is\_port

proc\_lib

用proc_lib创建的进程有什么与众不同
1. 会多一些信息
    与直接使用spawn创建进程(后面我们称之为"普通erlang进程")相比,使用proc_lib初始化进程会多一些信息,比如注册名,进程的父进程信息,初始化调用的函数等等
2.进程退出时的不同处理
    普通Erlang进程只有退出原因是normal的时候才会被认为是正常退出,使用proc_lib启动的进程退出原因是shutdown或者{shutdown,Term}的时候也被认为是正常退出.因为应用程序(监控树)停止而导致的进程终止,进程退出的原因会标记为shutdown.使用proc_lib创建的进程退出的原因不是normal也不是shutdown的时候,就会创建一个进程崩溃报告,这个会写入默认的SASL的事件handler,错误报告会在只有在启动了SASL的时候才能看到

模块

lists

  • all
    %如果List中的每个元素作为Pred函数的参数执行,结果都返回true,那么all函数返回true,
    %否则返回false
    all(Pred, List) -> boolean()
    
    
  • duplicate
    duplicate(N, Elem) -> List
    %返回一个由N个Elem组成的列表。
    %例子
    lists:duplicate(5,"test").
    %结果
    ["test","test","test","test","test"]
    
    
  • map
    %把L中的每一项作用于F,并返回一个新列表
    lists:map(F,L)
    %将List1中的每个元素去在Fun中执行,然后返回一个元素,最后返回的这些元素组成一个列表,
    map(Fun, List1) -> List2
    
    返回给List2
    
    
  • member
    %如果Elem和List中的某个元素匹配(相同),那么返回true,否则返回false
    ember(Elem, List) -> boolean()
    
    
    
  • filter
    %返回一个列表,这个列表是由List1中执行Pred函数返回true的元素组成。
    filter(Pred, List1) -> List2
    
    
  • foldl
  • foreach
    %以List中的每个元素为参数执行Fun函数,执行顺序按照List中元素的顺序,这个函数最后返回ok。是单边的
    foreach(Fun, List) -> ok
    
    
    
  • seq
    1> lists:seq(1,5).
    [1,2,3,4,5]
    
    

lib\_chan

  • start\_server

    在本机上启动一个服务器

gen\_tcp

  • connect
    %打开一个套节字
    {ok,Socket}=gen_tcp:connect(Host,80,[binary,{packet,0}])
    
    
  • send
    %发送套节字,等待回应,回应的消息会转发给打开套节字的进程
    %收到的消息会有很多
    %所以下面一般是receive
    ok=gen_tcp:send(Socket,"GET / HTTP/1.0\r\n\r\n")
    
    
  • listen
    %监听来自2345端口的连接
    %{packet,4}的意思是每个应用程序消息都是重一个4字节长的头部开始的
    {ok,Listen} = gen_tcp:listen(2345,[binary,{packet,4},
                       {reuseaddr,true},
                       {active,true}]),
    
    
    
  • accept
    %程序会在这里暂停并等待一个连接
    %当一个新的连接建立时,会返回变量Socket,该变量绑定到新建的套节字上
    %通过这个套节字服务器和客户机就可以进行通信了
    {ok,Socket} = gen_tcp:accept(Listen),
    
    
    
  • close
    %关闭监听套节字
    %这个操作不影响已经建立起来的连接
    gen_tcp:close(Listen),
    
    
    
  • controlling\_process
    %如果控制进程消亡,那套节字也会消亡
    %把套节字的控制进程改为新的控制进程
    gen_tcp:controlling_process(Socket,NewPid)
    
    
  • recv

inet

  • peername
    %返回连接另外一端的IP地址和端口号
    @spec inet:peername(Socket) -> {ok,{IP_Address,Port}} | {error,Why}
    
    

gen\_server


因此接口部分定义在behavior,接口的实现部分放在回调模块实现
进程如何启动
如何处理同步请求 Synchronous Requests - Call
如何处理异步请求 Asynchronous Requests - Cast
通用消息处理 handle_info
如何处理进程终止
如何进行代码版本替换

gen_server module            Callback module
-----------------                        ---------------
gen_server:start_link -----> Module:init/1 %%调用start_link会调用init
gen_server:call
gen_server:multi_call -----> Module:handle_call/3
gen_server:cast
gen_server:abcast     -----> Module:handle_cast/2
-                     -----> Module:handle_info/2
-                     -----> Module:terminate/2
-                     -----> Module:code_change/3   


handle_cast(_Msg, State) ->
    {noreply, State}.

ets

T=ets:new(mytable,[]),
ets:insert(T,{17,hello}),
ets:insert(T,{42,goodbye})
ets:lookup(T,17)
%返回 [{17,hello}]

ets:fun2ms()
ets:select()



OTP

行为模式

  • 行为模式接口特定函数和调用规范
  • 行为模式实现导出了接口所需的全部函数的回调模块,要说明行为模式的名称
  • 行为模式容器容器是一个进程,执行某个模块中的代码,调用和行为模式实现相对应的回调模块来处理逻辑

    回调函数在容器中执行

  • 行为模式典型代码布局
    1. 首部
    2. API
    3. 行为模式接口
    4. 内部函数
  • 调用过程
    1. 容器启动,传入模块名
    2. 容器通过接口反向调用实现
  • 用户调用
    1. 用户启动服务进程
    2. 向进程发消息

应用

  • 可以安应用名启动或停止
  • 可以知道安装了哪些应用
  • 每个主动应用都有一个监督者
  • 元数据

     {aplication, 应用名称
     [{descriiption, 描述},
      {vsn,版本},
      {modules,[应用中模块的列表,顺序不重要]},
      {registered,[注册进程名称]},
      {applications,[需要在改应用启动前启动的应用]},
      {mod,{如何启动应用程序的名称,[启动参数]}
     ]
    }
    
  • 应用行为模式

     实现模块命名一般为application-name_app
    -behaviour(applications) 
    
  • 监督者

    根监督者的行为模式实现模块一般为applicaton-name_sub
    本质上应用就是监督者和工作进程构建的树,树根是监督者 
    -behaviour(supervisor)
    

技术概念

基础

模块包含函数,函数执行产生进程,进程之间可以发送消息进行通信

  • 模块
  • 函数
  • 进程
  • 消息

变量

  • 所有变量有必须以大写字母开头

原子

  • 原子是以小写字母开头的,red,cat
  • 也可以使用单引号的字符(可以用大写开头),'Monday','and you'
  • 一个原子的值就是自身

元组

二进制数据

  1. 二进制数据更加节省内存
  2. 用到的整数必须在0-255之间
  3. 字符是ASCII码
  4. term_to_binary 和 binary_to_term 是逆函数

列表

列表解析

断言

字符串

  1. 双引号的字符串是列表

匿名函数

  • 定义匿名函数
Z=fun(X) -> 2*X end.

  • 匿名函数作为参数
  • 匿名函数作为返回值

记录

  • 把一个名称与元组中的一个元素对应起来

进程

  • 进程的共同点
    1. 注册别名
    2. 新创建的进程首先会初始化进程的循环状态数据(loop data).循环状态数据存放在变量中,这个变量被成为进程状态(process state)
    3. 进程状态会传递给loop方法.进程在运行状态通过loop方法来实现状态循环,loop方法的职责就是receive-evaluate,接受到消息,处理,更新进程状态,并把进程状态作为尾递归的参数传递回去.如果进程接收到stop消息,进程就进行清理并终止.

进程标识符

端口连接进程

%创建端口
Port=open_port(PortName,PortSettings)


端口标识符

引用

进程字典

  • 每一个进程都有自己私有的数据存储
%进程字典是键值对
put(Key,Value)->OldValue

get(Key)->Value
get_keys(Value)->[Key]

%删除key的字典
erase(Key)->Value
%删除整个进程字典
erase()



尾递归

链接进程

退出信号

系统进程

  • 系统进程在收到非正常退出信号时不会退出

ETS和DETS

基本操作

  1. 创建或打开
  2. 将一个或多个元组插入表
  3. 在表中查找元素
  4. 释放一个表

TCP/IP编程

需要注意的问题

  1. 如何组织数据,如何知道请求或响应含有多少数据
  2. 请求或响应过程中数据如何编码解码

打开套节字的3种模式

  1. active
  2. active once
  3. passive

主动模式和被动模式的区别

  1. 主动模式没法控制消息流

参数

  1. {active, false} 方式通过 gen_tcp:recv(Socket, Length) -> {ok, Data} | {error, Reason} 来接收。
  2. {active, true} 方式以消息形式{tcp, Socket, Data} | {tcp_closed, Socket} 主动投递给线程
  3. 对于第一种方式 gen_tcp:recv/2,3,如果封包的类型是{packet, raw}或者{packet,0},就需要显式的指定长度,否则封包的长度是对端决定的,长度只能设置为0
  4. 对于第二种方式,缓存区有多少数据,都会全部以消息{tcp, Socket, Data} 投递给线程。

其他

安装man手册