gen_tcp网络编程和Erlang/OPT的gen_serve

http://blog.csdn.net/cntway/article/details/7939987

原文: Erlang: A Generalized TCP Server 


前面几篇文章里谈到了Erlang的 gen_tcp网络编程 和Erlang/OPT的 gen_server 模块,现在让我们将它们两者绑定在一起 

大多数人认为“服务器”意味着网络服务器,但Erlang使用这个术语时表达的是更抽象的意义 
gen_serer在Erlang里是基于它的消息传递协议来操作的服务器,我们可以在此基础上嫁接一个TCP服务器,但这需要一些工作 

网络服务器的结构  
大部分网络服务器有相似的架构 
首先它们创建一个监听socket来监听接收的连接 
然后它们进入一个接收状态,在这里一直循环接收新的连接,直到结束(结束表示连接已经到达并开始真正的client/server工作) 

先看看前面网络编程里的echo server的例子: 
Java代码   收藏代码
  1. -module(echo).  
  2. -author('Jesse E.I. Farmer <jesse@20bits.com>').  
  3. -export([listen/1]).  
  4.   
  5. -define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).  
  6.   
  7. % Call echo:listen(Port) to start the service.  
  8. listen(Port) ->  
  9.     {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),  
  10.     accept(LSocket).  
  11.   
  12. % Wait for incoming connections and spawn the echo loop when we get one.  
  13. accept(LSocket) ->  
  14.     {ok, Socket} = gen_tcp:accept(LSocket),  
  15.     spawn(fun() -> loop(Socket) end),  
  16.     accept(LSocket).  
  17.   
  18. % Echo back whatever data we receive on Socket.  
  19. loop(Socket) ->  
  20.     case gen_tcp:recv(Socket, 0) of  
  21.         {ok, Data} ->  
  22.             gen_tcp:send(Socket, Data),  
  23.             loop(Socket);  
  24.         {error, closed} ->  
  25.             ok  
  26.     end.  

你可以看到,listen会创建一个监听socket并马上调用accept 
accept会等待进来的连接,创建一个新的worker(loop)来处理真正的工作,然后等待下一个连接 

在这部分代码里,父进程拥有listen socket和accept loop两者 
后面我们会看到,如果我们集成accept/listen loop和gen_server的话这样做并不好 

抽象网络服务器  
网络服务器有两部分:连接处理和业务逻辑 
上面讲到,连接处理对每个网络服务器都是几乎一样的 
理想状态下我们可以这样做: 
Java代码   收藏代码
  1. -module(my_server).  
  2. start(Port) ->  
  3.   connection_handler:start(my_server, Port, businees_logic).  
  4.   
  5. business_logic(Socket) ->  
  6.   % Read data from the network socket and do our thang!  

让我们继续完成它 

实现一个通用网络服务器  
使用gen_server来实现一个网络服务器的问题是,gen_tcp:accept调用是堵塞的 
如果我们在服务器的初始化例程里调用它,那么整个gen_server机制都会堵塞,直到客户端建立连接 

有两种方式来绕过这个问题 
一种方式为使用低级连接机制来支持非堵塞(或异步)accept 
有许多方法来支持这样做,最值得注意的是gen_tcp:controlling_process,它帮你管理当客户端建立连接时谁接受了什么消息 

我认为另一种比较简单而更优雅的方式是, 一个单独的进程来监听socket  
该进程做两件事:监听“接收连接”消息以及分配新的接收器 
当它接收一条新的“接收连接”的消息时,就知道该分配新的接收器了 

接收器可以任意调用堵塞的gen_tcp:accept,因为它允许在自己的进程里 
当它接受一个连接后,它发出一条异步消息传回给父进程,并且立即调用业务逻辑方法 

这里是代码,我加了一些注释,希望可读性还可以: 
Java代码   收藏代码
  1. -module(socket_server).  
  2. -author('Jesse E.I. Farmer <jesse@20bits.com>').  
  3. -behavior(gen_server).  
  4.   
  5. -export([init/1, code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).  
  6. -export([accept_loop/1]).  
  7. -export([start/3]).  
  8.   
  9. -define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).  
  10.   
  11. -record(server_state, {  
  12.         port,  
  13.         loop,  
  14.         ip=any,  
  15.         lsocket=null}).  
  16.   
  17. start(Name, Port, Loop) ->  
  18.     State = #server_state{port = Port, loop = Loop},  
  19.     gen_server:start_link({local, Name}, ?MODULE, State, []).  
  20.   
  21. init(State = #server_state{port=Port}) ->  
  22.     case gen_tcp:listen(Port, ?TCP_OPTIONS) of  
  23.         {ok, LSocket} ->  
  24.             NewState = State#server_state{lsocket = LSocket},  
  25.             {ok, accept(NewState)};  
  26.         {error, Reason} ->  
  27.             {stop, Reason}  
  28.     end.  
  29.   
  30. handle_cast({accepted, _Pid}, State=#server_state{}) ->  
  31.     {noreply, accept(State)}.  
  32.   
  33. accept_loop({Server, LSocket, {M, F}}) ->  
  34.     {ok, Socket} = gen_tcp:accept(LSocket),  
  35.     % Let the server spawn a new process and replace this loop  
  36.     % with the echo loop, to avoid blocking  
  37.     gen_server:cast(Server, {accepted, self()}),  
  38.     M:F(Socket).  
  39.      
  40. % To be more robust we should be using spawn_link and trapping exits  
  41. accept(State = #server_state{lsocket=LSocket, loop = Loop}) ->  
  42.     proc_lib:spawn(?MODULE, accept_loop, [{self(), LSocket, Loop}]),  
  43.     State.  
  44.   
  45. % These are just here to suppress warnings.  
  46. handle_call(_Msg, _Caller, State) -> {noreply, State}.  
  47. handle_info(_Msg, Library) -> {noreply, Library}.  
  48. terminate(_Reason, _Library) -> ok.  
  49. code_change(_OldVersion, Library, _Extra) -> {ok, Library}.  

我们使用gen_server:cast来传递异步消息给监听进程,当监听进程接受accepted消息后,它分配一个新的接收器  

目前,这个服务器不是很健壮,因为如果无论什么原因活动的接收器失败以后,服务器会停止接收新的连接 
为了让它变得更像OTP,我们因该捕获异常退出并且在连接失败时分配新的接收器 

一个通用的echo服务器  
echo服务器是最简单的服务器,让我们使用我们新的抽象socket服务器来写它: 
Java代码   收藏代码
  1. -module(echo_server).  
  2. -author('Jesse E.I. Farmer <jesse@20bits.com>').  
  3.   
  4. -export([start/0, loop/1]).  
  5.   
  6. % echo_server specific code  
  7. start() ->  
  8.     socket_server:start(?MODULE, 7000, {?MODULE, loop}).  
  9. loop(Socket) ->  
  10.     case gen_tcp:recv(Socket, 0) of  
  11.         {ok, Data} ->  
  12.             gen_tcp:send(Socket, Data),  
  13.             loop(Socket);  
  14.         {error, closed} ->  
  15.             ok  
  16.     end.  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值