Sunday, June 25, 2006

Erlang 101 ตอน 3

วันนี้จะลองเขียน Server Process ดูบ้าง
เริ่มต้นง่ายๆที่ Echo Server

เริ่มต้นด้วยการประกาศ module
-module(echo).
-export([start/0,loop/1]).

function start จะทำหน้าที่ spawn process ขึ้นมาใหม่
แล้วทำการ register process เข้ากับชื่อ "echo"
(การ register จะช่วยให้ client สามารถส่ง message มาให้ server ได้ง่ายขึ้น
แทนที่จะต้องรู้ว่า server มี process id เป็นอะไร
ก็ให้ระบุชื่อ server แทน)
start() ->
register(echo,
spawn(echo, loop, [nil])).

ในส่วนของ function loop ก็คือ loop ที่เราจะรอรับ message จาก client
ให้สังเกตว่ากรณี message ประเภท echo พอตอบ message กลับไปแล้ว
จะมีการ recursive เรียกใช้ loop อีกครั้ง
ส่วนกรณี stop จะไม่มีการเรียก loop ซ้ำ ซึ่งก็หมายความ process จะ terminate
loop(State) ->
receive
{From, {echo, Msg}} ->
From ! {ok, Msg},
loop(State);
{From, {stop}} ->
true
end.

ใน echo server เราสนใจ message อยู่ 2 แบบคือ
  • {From, {echo, Msg}}
    From ก็คือ process id ของ client
    จะได้ส่งข้อมูลกลับไปให้ client ได้
    echo ขึ้นต้นด้วยตัวเล็ก แสดงว่ามันเป็น atom หรือ Constants แบบหนึ่ง
    Msg คือ ข้อความที่ user ส่งมา
  • {From, {stop}}


เวลาใช้งาน ก็จะทำดังนี้
Erlang (BEAM) emulator version 5.4.13 [source] [hipe]

Eshell V5.4.13 (abort with ^G)
1> c("/Users/pphetra/projects/erlang/echo2/src/echo", [{outdir, "/Users/pphetra/projects/erlang/echo2/src/"}]).
/Users/pphetra/projects/erlang/echo2/src/echo.erl:13: Warning: variable 'From' is unused
{ok,echo}
2> echo:start().
true
3> echo ! {self(), {echo, "helloworld"}}.
{<0.30.0>,{echo,"helloworld"}}
4> echo ! {self(), {stop}}.
{<0.30.0>,{stop}}
5>

  • <0.30.0> คือเลข pid ของ echo server
  • self() คือ function ที่ return pid ของ process ที่เรียกคำสั่งนี้


ใน erlang มี concept ที่เรียกว่า Behavior
ซึ่งก็คือการดึงเอา pattern ที่ซ้ำๆกันออกมาเป็น code ต่างหาก
แล้วเปิดให้เราเขียน code ที่ implement behavior เหล่านี้ได้

erlang เตรียม behavior ของ server ไว้แล้ว
โดยมีชื่อว่า gen_server
ลองดูว่า echo server implement behavior นี้แล้ว code จะเปลี่ยนโฉมไปยังไงบ้าง

เริ่มต้นด้วย การประกาศ behavior(gen_server)
-module(echo).
-behavior(gen_server).
-export([handle_call/3,start/0,init/1,terminate/2]).

gen_server บังคับให้เรา implement callback function ที่ชื่อ init
ซึ่งกรณีของเรา ไม่ได้มีการ allocate resources อะไร
ก็เลยแค่ return ok พร้อม state [] ว่างๆกลับไป

start() ->
gen_server:start_link({local, echo}, echo, [], []).

init(_Arg) ->
{ok, []}.

ตัว handle_call คือ callback ที่ใช้รองรับ syncronous call (พวกต้องการรับค่ากลับ)
จะเห็นว่าเรา return กลับเป็น {reply, Msg}
ซึ่ง gen_server จะส่ง message กลับไปให้เอง
ส่วน function terminate ก็เป็น callback ที่ gen_server call
เมื่อพบ message {stop, normal, ..}
ซึ่งในการทำงานจริง ตรงนี้จะเป็นการคืนค่าทรัพยากรที่จองไว้ กลับคืนสู่ระบบ
handle_call(Msg, _From, State) ->
case Msg of
{echo, Text} ->
{reply, {ok, Text}, State};
{stop} ->
{stop, normal, State}
end.

terminate(normal, _) ->
ok.


ดูแล้ว มันเทอะทะขึ้นอีกนิด แต่ว่าผลประโยชน์ที่ได้จากการ implement gen_server behavior ก็คือ
เราสามารถใช้ pattern supervisor มาช่วย monitor server เรา
ถ้ามีการตาย หรือ terminate แบบผิดธรรมชาติ เช่นมี bug หรือ IO มีปัญหา
supervisor สามารถ restart server ให้เราอัติโนมัติได้
ซึ่งช่วยเพิ่ม reliability ให้กับระบบเรา

Related link from Roti

No comments: