erlang分佈式入門(四)-TCP Server的詳解與改進

在上一篇實現的erlang分佈式入門(三)-TCP Server-Client 中的accept函數如下:

 

accept(LSocket) ->  
    {ok, Socket} = gen_tcp:accept(LSocket),  
    spawn(fun() -> loop(Socket) end),  
    accept(LSocket). 

 

使用BIF的spawn方法,創建了一個新的進程loop來處理客戶端連接,主要業務在loop函數中實現,然後繼續accept新的客戶端連接。

spawn的說明如下:

spawn(Fun) -> pid()

Types:
Fun = function()

Returns the pid of a new process started by the application of Fun to the empty list []. Otherwise works like spawn/3.

spawn(Node, Fun) -> pid()

Types:
Node = node()
Fun = function()

Returns the pid of a new process started by the application of Fun to the empty list [] on Node. If Node does not exist, a useless pid is returned. Otherwise works like spawn/3.

spawn(Module, Function, Args) -> pid()

Types:
Module = Function = atom()
Args = [term()]

Returns the pid of a new process started by the application of Module:Function to Args. The new process created will be placed in the system scheduler queue and be run some time later.

error_handler:undefined_function(Module, Function, Args) is evaluated by the new process if Module:Function/Arity does not exist (where Arity is the length of Args). The error handler can be redefined (see process_flag/2). If error_handler is undefined, or the user has redefined the default error_handler its replacement is undefined, a failure with the reason undef will occur.

> spawn(speed, regulator, [high_speed, thin_cut]).
<0.13.1>

spawn(Node, Module, Function, Args) -> pid()

Types:
Node = node()
Module = module()
Function = atom()
Args = [term()]

Returns the pid of a new process started by the application of Module:Function to Args on Node. If Node does not exists, a useless pid is returned. Otherwise works like spawn/3.

 

但是由於gen_tcp:accept是阻塞的,所以我們在服務端啓動tcp_server之後沒有返回值,erlang的命令行一直阻塞在那裏,一直等到有客戶端連接上去纔有反應。

以下是accept函數的說明:

accept(ListenSocket) -> {ok, Socket} | {error, Reason}
accept(ListenSocket, Timeout) -> {ok, Socket} | {error, Reason}

Types:
ListenSocket = socket()
Returned by listen/2.
Timeout = timeout()
Socket = socket()
Reason = closed | timeout | system_limit | inet:posix()

Accepts an incoming connection request on a listen socket. Socket must be a socket returned from listen/2. Timeout specifies a timeout value in ms, defaults to infinity.

Returns {ok, Socket} if a connection is established, or {error, closed} if ListenSocket is closed, or {error, timeout} if no connection is established within the specified time, or {error, system_limit} if all available ports in the Erlang emulator are in use. May also return a POSIX error value if something else goes wrong, see inet(3) for possible error values.

Packets can be sent to the returned socket Socket using send/2. Packets sent from the peer are delivered as messages:

{tcp, Socket, Data}

unless {active, false} was specified in the option list for the listen socket, in which case packets are retrieved by calling recv/2.

 

我們知道nginx的Master進程只用於接收客戶端連接,然後把連接交給worker子進程去處理。這是高併發服務器處理大量連接的很有效的方式。


以下是改進版的TCP服務端:

 

echo_server.erl

-module(echo_server).

-export([start/2, loop/1]).

%% @spec start(Port::integer(), Max::integer())  
%% @doc echo server
start(Port, Max) ->
        generic_server:start(echo_server, Port, Max, {?MODULE, loop}).

%% @spec loop(Sock::port())
%% @doc echo_server

loop(Sock) ->
        case gen_tcp:recv(Sock, 0) of
                {ok, Data} ->
                        io:format("server recv data close~p~n",[Data]),
                        gen_tcp:send(Sock, Data),
                        loop(Sock);
                {error, closed} ->
                        io:format("client sock close~n"),
                        gen_server:cast(echo_server, {connect_close, self()})
        end.

 

generic_server.erl

%% a generic tcp server
-module(generic_server).
-author('cheng [email protected]').
-vsn('0.1').
-behaviour(gen_server).

-define(TCP_OPTIONS, [binary, {packet, raw}, {active, false}, {reuseaddr, true}]).
-record(server_state, {
                        port,                   % listen port
                        loop,                   % the logic fun
                        ip=any,                 % ip
                        lsocket=null,           % listen socket
                        conn=0,                 % curent connect
                        maxconn                 % max connect
                        }).

%% internal export
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3, terminate/2]).

%% start tcp server 
-export([start/4]).

-export([accept_loop/5]).

%% start the generic server
start(Name, Port, Max, Loop) ->
        State = #server_state{port=Port, loop=Loop, maxconn=Max},
        io:format("max connection is ~p~n", [Max]),
        gen_server:start_link({local, Name}, ?MODULE, State, []).

%% create listen socket
init(State = #server_state{port=Port}) ->
        case gen_tcp:listen(Port, ?TCP_OPTIONS) of
                {ok, LSocket} ->
                        {ok, accept(State#server_state{lsocket=LSocket})};
                {error, Reason} ->
                        {stop, {create_listen_socket, Reason}}
        end.


%% accept spawn a new process
accept(State =#server_state{lsocket=LSocket, loop=Loop, conn=Conn, maxconn=Max}) ->
        proc_lib:spawn(generic_server, accept_loop, [self(), LSocket, Loop, Conn, Max]),
        State.

%% accept the new connection
accept_loop(Server, LSocket, {M, F}, Conn, Max) ->
        {ok, Sock} = gen_tcp:accept(LSocket),
        if
                Conn + 1 > Max ->
                        io:format("reach the max connection~n"),
                        gen_tcp:close(Sock);
                true ->
                        gen_server:cast(Server, {accept_new, self()}),
                        M:F(Sock)
        end.

%% the server receive the notify that a connect has construct
handle_cast({accept_new, _Pid}, State=#server_state{conn=Cur}) ->
        io:format("current connect:~p~n", [Cur+1]),
        {noreply, accept(State#server_state{conn=Cur+1})};

%% someone connect has been close, so change the max connect
handle_cast({connect_close, _Pid}, State=#server_state{conn=Cur}) ->
        io:format("current connect:~p~n", [Cur-1]),
        {noreply, State#server_state{conn=Cur-1}}.

handle_call(_From, _Request, State) -> {noreply, State}.
handle_info(_Info, State) -> {noreply, State}.
code_change(_OldVsn, State, _Extra) -> {ok, State}.

terminate(_Reason, _State) -> ok.
 

主要使用了erlang的gen_server內置函數,詳解:http://www.erlang.org/doc/design_principles/gen_server_concepts.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章