Erlang --- gen_server


行為模式

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、超時等東西。
start對應的回調函數是init/1,一般來說是進行服務器啟動后的一些初始化的工作, 並生成初始的狀態State,正常返回是{ok, State}。這個State是貫穿整個服務器, 並把所有六個回調函數聯系起來的紐帶。它的值最初由init/1生成, 此后可以由三個handle函數修改,每次修改后又要放回返回值中, 供下一個被調用的handle函數使用。 如果init/1返回ignore{stop, Reason},則會中止服務器的啟動。
有一點細節要注意的是,API函數和回調函數雖然習慣上是寫在同一個文件中,但執行函數 的進程卻通常是不一樣的。在上面的模板中,start_link/0中使用self()的話,顯示的是調用者的進程號,而在init/1中使用self()的話,顯示的是服務器的進程號。
使用服務器

三個handle開頭的回調函數對應着三種不同的使用服務器的方式。如下:

gen_server:call     -------------   handle_call/3
gen_server:cast     -------------   handle_cast/2
用!向服務進程發消息   -------------   handle_info/2
call是有返回值的調用;cast是無返回值的調用,即通知;而直接向服務器進程發的 消息則由handle_info處理。
call是有返回值的調用,也是所謂的同步調用,進程會在調用后一直等待直到回調函數返回為止。 它的函數形式是 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函數生成的,可以根據需要進 行修改之后,再放回返回值中。
call
call對應的回調函數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重新啟動服務器。

最簡單的就是啥都不干,返回ok就好了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM