Chinaunix首页 | 论坛 | 博客
  • 博客访问: 207956
  • 博文数量: 39
  • 博客积分: 1057
  • 博客等级: 准尉
  • 技术积分: 926
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-27 20:13
文章分类

全部博文(39)

文章存档

2012年(24)

2011年(15)

分类: Python/Ruby

2012-06-12 09:31:57

只是为了更好的理解通用服务器和 Erlang/OTP,故而在此抄写一遍原文....Take a deep breath...

16.1 The Road to the Generic Server

     We're going to write four little server called server1,server2...,each slightly
     different from the last.The goal is to totally separate the non-functional parts
     of the problem from the functional parts of the problem .

     Server 1:The Basic Server
     Here's our first attempt.It's a little server that we can parameterize with a callback
     module:  

点击(此处)折叠或打开

  1. -module(server1)
  2. -export([start/2,rpc/2])

  3. start(Name,Mod) ->
  4.     register(Name,spawn(fun() -> loop(Name,Mod,Mod:init()) end)).

  5. rpc(Name,Request) ->
  6.     Name ! {self(),Request},
  7.     receive
  8.         {Name,Response} -> Response
  9.     end.

  10. loop(Name,Mod,State) ->
  11.     receive
  12.         {From,Request} ->
  13.             {Response,State1} = Mod:hanle(Request,State),
  14.             From ! {Name,Response},
  15.             loop(Name,Mod,State1)
  16.     end.
    This very small amount of code capture the quintessential nature of a server.Let's write
    a callback for server1.Here's a name server callback:

点击(此处)折叠或打开

  1. -module(name_server)
  2. -export([init/0,add/2,whereis/1,hanle/2]).
  3. -import(server1,[rpc/2]).

  4. %%client routines
  5. add(Name,Place) -> rpc(name_server,{add,Name,Place}).
  6. whereis(Name) -> rpc(name_server,{whereis,Name}).

  7. %llback routines
  8. init() -> dict:new().

  9. handle({add,Name,Place},Dict) -> {ok,dict:store(Name,Place,Dict)};
  10. handle({whereis,Name},Dict) -> {dict:find(Name,Dict),Dict}.
    This code actually performs two tasks.It servers as a callback module that is called from
    the server framework code,ant at the same time,it contains the interfaceing routines that
    will be called by the client.The usual OTP convention is to combine both functions in the
    same module.
    Just to prove that it works,do this:
   

点击(此处)折叠或打开

  1. 1> server1:start(name_server,name_server).
  2. true
  3. 2>name_server:add(joe,"at home").
  4. ok
  5. 3>name_server:whereis(joe).
  6. {ok,"at home"}
    Now stop and think.The callback had no code for concurrency,no spawn,no send
    ,no receive,no register.It is pure sequential code-nothing else.What dose this
    mean?
    This means we can write client-server modules without understanding anything about
    the underlying concurrency modules.
    This is the basic pattern for all servers.Once you understand the basic structure,
    it's easy to "roll your own."

    Server 2:A Server with Transactions
    Here's a server that crashes the client if the query in the server results in an
    exception:

点击(此处)折叠或打开

  1. -module(server2)
  2. -export([start/2,rpc/2]).

  3. start(Name,Mod) ->
  4.     register(Name,spawn(fun() -> loop(Name,Mod,Mod:init()) end)).

  5. rpc(Name,Request) ->
  6.     Name ! {self(),Request},
  7.     receive
  8.         {Name,crash} -> exit(rpc);
  9.         {Name,ok,Response} -> Response
  10.     end.

  11. loop(Name,Mod,OldState) ->
  12.     receive
  13.         {From,Request} ->
  14.             try Mod:handle(Request,OldState) of
  15.                 {Response,NewState} ->
  16.                      From ! {Name,ok,Response},
  17.                      loop(Name,Mod,NewState)
  18.             catch
  19.                 _:Why ->
  20.                     log_the_error(Name,Request,Why),
  21.                     From ! {Name,crash},
  22.                     loop(Name,Mod,OldState)
  23.              end
  24.     end.

  25. log_the_error(Name,Request,Why) ->
  26.     io:format("Server ~p request ~p~n"
  27.               "caused exception ~p~n",
  28.               [Name,Request,Why]).
    This one gives you "tracsaction semantics" in the server--it loops with the original value
    of State if an exception was raised in the handler function.But if the handler function
    succeeded,then it loops with the value of newState provide by handler fucntion.
   
    Why does it retain the original state? When the handler function fails,the client that
    sent the message that caused the failure is sent a message thate causes it to crash.
    The client cannot proceed,because the request it sent to the server caused the handler
    function to crash.But any other client that wants to use the server will not be affected.
    Moreover,the state of server is not changed when an error occurs in the handler.

    Note that the callback module for this server is exactly the same as the callback module
    we used for server1.By changing the server and keeping the callback module constant,we can
    change the nonfunctional behavior of the callback module.

    Note: The last statement wasn't strictly true.We have to make a very small change to the
    callback module when we go from server1 to server2,and that is to change the name in the
    -import declaration from server1 to server2.Otherwise,there are no changes.

    Server 3: A Server with Hot Code Swapping
    Now we'll add hot code swapping:

点击(此处)折叠或打开

  1. -module(server3).
  2. -export([start/2,rpc/2,swap_code/2]).

  3. start(Name,Mod) ->
  4.     register(Name,spawn(fun() -> loop(Name,Mod,Mod:init()) end)).

  5. swap_code(Name,Mod) -> rpc(Name,{swap_code,Mod}).

  6. rpc(Name,Request) ->
  7.     Name ! {self(),Request},
  8.     receive
  9.         {Name,Response} -> Response
  10.     end.

  11. loop(Name,Mod,OldState) ->
  12.     receive
  13.         {From,{swap_code,NewCallBackMod}} ->
  14.             From ! {From,ack},
  15.             loop(Name,NewCallBackMod,OldState);
  16.          {From,Request} ->
  17.              {Response,NewState} = Mod:handle(Request,OldState),
  18.              From ! {Name,Response},
  19.              loop(Name,Mod,NewState)
  20.     end.
How does this work?

If we send the server a swap code message,then it will change the callback module to the new
module contained in the message.

We can demonstrate this by starting server3 with a callback module and then dynamically swapping the callback module.We can't use name_server as the callback module because we hard-compiled the name of the server into the module.So,we make a copy of this,calling it name_server1 where we change the name of the server:

点击(此处)折叠或打开

  1. -module(name_server1)
  2.     -export([init/0,add/2,whereis/1,hanle/2]).
  3.     -import(server3,[rpc/2]).
  4.     %%client routines
  5.     add(Name,Place) -> rpc(name_server,{add,Name,Place}).
  6.     whereis(Name) -> rpc(name_server,{whereis,Name}).
  7.     %llback routines
  8.     init() -> dict:new().
  9.     handle({add,Name,Place},Dict) -> {ok,dict:store(Name,Place,Dict)};
  10.     handle({whereis,Name},Dict) -> {dict:find(Name,Dict),Dict}.
First we'll start server3 with the name_server1 callback module:

点击(此处)折叠或打开

  1. 1> server3:start(name_server,name_server1).
  2. true
  3. 2> name_server:add(joe,"at home").
  4. ok
  5. 3> name_server:add(helen,"at job").
  6. ok
Now suppose we want to find all the named that are served by the name server.There is no function in the API that can do this--the module name_server has only and lookup access routine.

With lightning speed,we fire up our text editor and write a new callback module:

点击(此处)折叠或打开

  1. -module(new_name_server)
  2.     -export([init/0,add/2,whereis/1,all_names/0,delete/1,hanle/2]).
  3.     -import(server3,[rpc/2]).
  4.     %%client routines
  5.     all_names() -> rpc(name_server,allNames).
  6.     delete(Name) -> rpc(name_server,{delete,Name}).
  7.     add(Name,Place) -> rpc(name_server,{add,Name,Place}).
  8.     whereis(Name) -> rpc(name_server,{whereis,Name}).
  9.     %llback routines
  10.     init() -> dict:new().
  11.     handle({add,Name,Place},Dict) -> {ok,dict:store(Name,Place,Dict)};
  12.     handle(allNames,Dict) -> {dict:fetch_keys(Dict),Dict};
  13.     handle({delete,Name},Dict) -> {ok,dict:erase(Name,Dict)};
  14.     handle({whereis,Name},Dict) -> {dict:find(Name,Dict),Dict}.
We compile this and tell the server to swap its callback module:

点击(此处)折叠或打开

  1. 4> c(new_name_server).
  2. {ok,new_name_server}
  3. 5> server3:swap_code(name_server,new_name_server).
  4. ok
Now we can run the new functions in the server:

点击(此处)折叠或打开

  1. 6> new_name_server:all_names().
  2. [joe,helen]
Here we changed the callback module on the fly--this is dynamic code upgrade,in action before your eyes,with no black magic.

Now stop and think again.The last two tasks we have done are generally considered to by pretty difficult,in fact,very difficult.Servers with "transactions semantics" are difficult to write;servers with dynamic code upgrade are very difficult to write.

This technique is extremely powerful.Traditionally we think of servers as programing with state that change state when we send them message.The code in the servers is fixed the first time it is called,and if we want to change the code in the server,we have to stop the server and change the code,and then we can restart the server.In the examples we have given,the code in the server can be changed just as easily as we can change the state of server.

Server 4: Transactions and Hot Code Swapping
In the last two servers,code upgrade and transaction semantics were separate.Let's combine them into single server.Hold onto you hats....

点击(此处)折叠或打开

  1. -module(server2)
  2.     -export([start/2,rpc/2]).
  3.     start(Name,Mod) ->
  4.         register(Name,spawn(fun() -> loop(Name,Mod,Mod:init()) end)).
  5.     rpc(Name,Request) ->
  6.         Name ! {self(),Request},
  7.         receive
  8.             {Name,crash} -> exit(rpc);
  9.             {Name,ok,Response} -> Response
  10.         end.
  11.     loop(Name,Mod,OldState) ->
  12.         receive
  13.             {From,{swap_code,NewCallBackMod}} ->
  14.                 From ! {Name,ok,ack},
  15.                 loop(Name,NewCallBackMod,OldState);
  16.             {From,Request} ->
  17.                 try Mod:handle(Request,OldState) of
  18.                     {Response,NewState} ->
  19.                          From ! {Name,ok,Response},
  20.                          loop(Name,Mod,NewState)
  21.                 catch
  22.                     _:Why ->
  23.                         log_the_error(Name,Request,Why),
  24.                         From ! {Name,crash},
  25.                         loop(Name,Mod,OldState)
  26.                  end
  27.         end.
  28.     log_the_error(Name,Request,Why) ->
  29.         io:format("Server ~p request ~p~n"
  30.                   "caused exception ~p~n",
  31.                   [Name,Request,Why]).
This server provides both hot code swapping and transaction semantics.Neat.

Server 5: Even More Fun
Now that we've got the idea of dynamic code change,we can have even more fun.Here's a server that does nothing at all until you tell it to become a particular type of server:

点击(此处)折叠或打开

  1. -module(server5).
  2. -export([start/0,rpc/2]).

  3. start() -> spawn(fun() -> wait() end).
  4. wait() ->
  5.     receive
  6.         {become,F} -> F()
  7.     end.

  8. rpc(Pid,Q) ->
  9.     Pid ! {self(),Q},
  10.     receive
  11.         {Pid,Reply} -> Reply
  12.     end.
if we start this and then send it a {become,F} message,it will become and F server by evaluating F(),We'll start it:

点击(此处)折叠或打开

  1. 1> Pid = server5:start().
  2. <0.57.0>
Our server does nothing and just waits for a become message.

Let's now define a server function.It's nothing complicated,just something to compute factorial:

点击(此处)折叠或打开

  1. -module(my_fac_server).
  2. -export([loop/0]).

  3. loop() ->
  4.     receive
  5.         {From,{fac,N}} ->
  6.             From ! {slef(),fac(N)},
  7.             loop();
  8.         {become,Something} ->
  9.             Something()
  10.     end.

  11. fac(0) -> 1;
  12. fac(N) -> N*fac(N-1).
Just make sure it's compiled,and then we can tell process <0.57.0> to become a factorial server:

点击(此处)折叠或打开

  1. 2> c(my_fac_server).
  2. {ok,my_fac_server}
  3. 3> Pid ! {become,fun my_fac_server:loop/0}.
  4. {become,#Fun<my_fac_server.loop.0>}
Now that our process has become a factorial server,we can call it:

点击(此处)折叠或打开

  1. 4> server5:rpc(Pid,{fac,30}).
  2. 2652585981219058636308480000000
Our prcocess will return a factorial server,until we send it a {become,Something} message and tell it to do something else.

As you can see from the previous examples,we can make range of diffierent types of servers,with different semantics and some quite surprising properties.This technique is almost too powerful.Used to its full potential,it can yield very small programs of quite surprising power and beauty.When we make industrail-scale projects with dozens to hundreds of programmers involved,we might not actually want things to be too dynamic.We have to try to strike a balance between having something general and powerful and having something that is useful for commercial products.Having code that can morph into new sessions as runs is beautiful but terrible to debug if something goes wrong later.If we have made dozens of dynamic changes to our code and it then crashes,finding out exactly what went wrong is not easy.

The server example in this section are not actually not quite correct.They are written this way so as to emphasize the ideas involved, but they do have one or two extremely and subtle errors.I'm not going to tell you immediately what they are,but I'll give you some hints at the end of the chapter.

The Erlang module gen_server is the kind of logical conclusion of a succession of successively sophisticated servers (just like the one we've written so far in this chapter).

It has been in use in industrial products since 1998.Hundreds of servers can be part of single product.These servers have been written  by programmers using regular sequential code.All the error handling and all the nonfunctional behavior is factor out in the generic part of the server.

So now we'll make a great leap of imagination and look at the real gen_server.
阅读(1591) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~