• [转]Erlang:一个通用的网络服务器


      前面几篇文章里谈到了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. 

      你可以看到,服务器只含有自己的业务逻辑
      连接处理被封装到socket_server里面
      而这里的loop方法也和最初的echo服务器一样

      希望你可以从中学到点什么,我觉得我开始理解Erlang了

      欢迎回复,特别关于是如何改进我的代码,cheers! 
  • 相关阅读:
    Python Scrapy爬虫(下)
    Hadoop HDFS
    Spark核心 RDD(上)
    自定义日期格式------SimpleDateFormat
    常用类-- 使用comparator实现定制排序
    自定义日期格式------DateTimeFormatter
    多线程-方式四使用线程池
    多线程-方式三实现Callable接口方式 JDK5.0新增
    解决线程安全------lock锁
    死锁问题
  • 原文地址:https://www.cnblogs.com/freebird92/p/2302392.html
Copyright © 2020-2023  润新知