行為模式
gen_server代表的就是“行為模式”的一種,行為模式的目的在於為特定類型的進程提供一套模板。
啟動服務器
用來啟動服務器的有start/3
,start/4
,start_link/3
,start_link/4
這四個函數。 使用這些start函數之后,就會產生一個新的進程,也就是一個gen_server服務器。這些 start函數的正常情況下返回值是{ok,Pid}
,Pid
就是這個新進程的進程號。 帶link與不帶的區別在於是否跟父進程建立鏈接,換種說法是,新啟動的進程死掉后,會不會通知啟動他的進程(父進程)。
start函數可以四個參數(ServerName, Module, Args, Options)
:
-
第一個參數
ServerName
是服務名, 是可以省掉的。具有相同服務名的模塊在一個節點中只能啟動一次,重復啟動會報錯,為{error, {already_started, Pid}}
。具有服務名的服務進程可以使用服務名來調用, 沒有服務名的只能通過進程號pid來調用了。通常有名字的服務進程會使用模塊名做為 服務名,即上面模板中定義的宏-define(SERVER, ?MODULE)
,然后在需要使用服務名的 地方填入?SERVER
. -
第二個參數
Module
是模塊名,一般而言API和回調函數是寫在同一個文件里的,所以就用?MODULE
,表示本模塊的模塊名。 -
第三個參數
Args
是回調函數init/1
的參數,會原封不動地傳給init/1
。 -
第四個參數
Options
是一些選項,可以設置debug、超時等東西。
init/1
,一般來說是進行服務器啟動后的一些初始化的工作, 並生成初始的狀態State,正常返回是{ok, State}。這個State是貫穿整個服務器, 並把所有六個回調函數聯系起來的紐帶。它的值最初由init/1
生成, 此后可以由三個handle函數修改,每次修改后又要放回返回值中, 供下一個被調用的handle函數使用。 如果init/1
返回ignore
或{stop, Reason}
,則會中止服務器的啟動。
start_link/0
中使用self()
的話,顯示的是調用者的進程號,而在init/1
中使用self()
的話,顯示的是服務器的進程號。
三個handle開頭的回調函數對應着三種不同的使用服務器的方式。如下:
gen_server:call ------------- handle_call/3
gen_server:cast ------------- handle_cast/2
用!向服務進程發消息 ------------- handle_info/2
gen_server:call(ServerRef, Request, Timeout) -> Reply
,
- 第一個參數
ServerRef
是被調用的服務器,可以是服務器名,或是服務器的pid。 - 第二個參數
Request
會直接傳給回調函數handle_call。
- 最后一個參數
Timeout
是超時,是可以省略的,默認值是5秒。
call是用來指揮回調函數handle_call/3
干活的。具體形式為 handle_call(Request, From, State)
。
- 第一個參數
Request
是由call傳進來的,是寫程序時關注和處理的重點。 - 第二個參數
From
是gen_server傳進來的,是調用的來源,也就是哪個進程執行了call。From
的形式是{Pid, Ref}
,Pid
是來源進程號,而Ref
是調用的標識,每一次調用 都不一樣,用以區別。有了Pid,在需要向來源進程發送消息時就可以使用,但由於call 是有返回值的,一般使用返回值傳遞數據就好。 - 第三個參數
State
是服務器的狀態,這是由init或是其他的handle函數生成的,可以根據需要進 行修改之后,再放回返回值中。
handle_call/3
在正常情況下的返回值是{reply, Reply, NewState}
, Reply
會作為call的返回值傳遞回去,NewState
則會作為服務器的狀態。 另外還可以使用{stop, Reason, State}
中止服務器運行,這比較少用。
使用call要小心的是,兩個服務器進程不能互相call,不然會死鎖。
cast
cast是沒有返回值的調用,一般把它叫做通知。它是一個“異步”的調用,調用后會直接收到 ok
,無需等待回調函數執行完畢。
它的形式是gen_server:cast(ServerRef, Request)
。參數含義 與call相同。由於不需要等待返回,所以沒必要設置超時,沒有第三個參數。
在多節點的情況下,可以用abcast
,向各節點上的具有指定名字的服務進程發通知。
cast們對應的回調函數是handle_cast/2
,具體為:handle_cast(Msg, State)
。 第一個參數是由cast傳進去的,第二個是服務器狀態,和call類似,不多說。
handel_cast/2
的返回值通常是{noreply, NewState}
,這可以用來改變服務器狀態, 或是{stop, Reason, NewState}
,這會停止服務器。通常來說,停止服務器的命令用 cast來實現比較多。
原生消息
原生消息是指不通過call或cast,直接發往服務器進程的消息,有些書上譯成“帶外消息”。 比方說網絡套接字socket發來的消息、別的進程用!發過來的消息、跟服務器建立鏈接的進程死掉了, 發來{'EXIT', Pid, Why}
等等。一般寫程序要盡量用API,不要直接用!向服務器進程發消息, 但對於socket一類的依賴於消息的應用,就不得不處理原生消息了。
handle_info/2
處理,具體為handle_info(Info, State)
。其中Info是 發過來的消息的內容。回復和handle_cast
是一樣的。
上面介紹的handle函數返回{stop,...},就會使用回調函數terminate/2
進行掃尾工作。 典型的如關閉已打開的資源、文件、網絡連接,打log做記錄,通知別的進程“我要死啦”, 或是“信春哥,滿血復活”:利用傳進來的狀態State重新啟動服務器。