Blog.csdn.net/mycwq/artic…
blog.csdn.net/mycwq\
Complete source code download address: download.csdn.net/download/li… \
Erlang OTP builds a TCP server based on Erlang OTP, which describes the implementation method of two hybrid sockets. It is really exciting to see the article. Building a non-blocking TCP server using OTP principles Combining these two articles, this article continues to discuss the concrete implementation of Erlang/OTP building TCP servers with examples of how to create a simple non-blocking TCP server using standard Erlang/OTP behavior.
The TCP Socket model
Active mode {active, true}, non-blocking way receives the message, but it cannot cope with large flow system request, the client sends the data too fast, and more than the speed of the server can handle, then, the system may lead to a message buffer is full, persistent, busy traffic extreme situation, system due to request too much overflow, Erlang virtual machine crashes due to insufficient memory. Passive mode {active, false}, blocking mode to receive messages, the underlying TCP buffer can be used to suppress the request, and reject the client message, in the place of receiving data will call gen_TCP :recv, causing blocking (in single-process mode can only passively wait for a specific client Socket, It’s dangerous. It is important to note that the operating system may also do some caching to allow the client machine to continue sending a small amount of data before blocking it, but Erlang has not yet called the recV function. Mixed mode (semi-blocking, {active, once}), active Socket for one message only, after the control process has sent a message data, it must explicitly call inet:setopts(Socket, [{active, once}]) to reactivate in order to receive the next message (before this, The system is blocked. It can be seen that the hybrid mode combines the advantages of both active mode and passive mode to realize traffic control and prevent the server from being overwhelmed by too many messages. So if you want to build a TCP server, it makes sense to build on the TCP Socket hybrid mode (semi-blocking). \
TCP Server Design
The design of the TCP server includes the main application tcp_server_app and the supervisor tcp_server_sup process, which has two child processes tcp_server_listener and tcp_client_sup. The tcp_server_listener handles the connection request from the client and tells the TCP_client_sup to start a tcp_server_handler instance process to handle a client request. The instance process then handles the server-client interaction data. \
\
Application and monitoring behavior
To build an Erlang/OTP application, we need to build some modules to implement the application and monitor the behavior. When the application starts, tcp_server_app:start/2 calls tcp_server_sup:start_link/1 to create the master supervisor process. The supervisor instantiates the child worker process tcp_server_listener and the child supervisor process tcp_client_sup by calling back tcp_server_sup:init/1. The subsupervisor calls back to tcp_server_sup:init/1 to instantiate the worker process tcp_server_handler, which is responsible for handling client connections. \
TCP server application (tcp_server_app.erl)
[plain] view plain copy
- -module(tcp_server_app).
- -behaviour(application).
- -export([start/2, stop/1]).
- -define(PORT, 2222).
- start(_Type, _Args) ->
- io:format(“tcp app start~n”),
- case tcp_server_sup:start_link(? PORT) of
- {ok, Pid} ->
- {ok, Pid};
- Other ->
- {error, Other}
- end.
- stop(_S) ->
- ok.
TCP server supervisor process (tcp_server_sup.erl)
[plain] view plain copy
- -module(tcp_server_sup).
- -behaviour(supervisor).
- -export([start_link/1, start_child/1]).
- -export([init/1]).
- start_link(Port) ->
- io:format(“tcp sup start link~n”),
- supervisor:start_link({local, ? MODULE}, ? MODULE, [Port]).
- start_child(LSock) ->
- io:format(“tcp sup start child~n”),
- supervisor:start_child(tcp_client_sup, [LSock]).
- init([tcp_client_sup]) ->
- io:format(“tcp sup init client~n”),
- {ok,
- { {simple_one_for_one, 0, 1},
- [
- { tcp_server_handler,
- {tcp_server_handler, start_link, []},
- temporary,
- brutal_kill,
- worker,
- [tcp_server_handler]
- }
- ]
- }
- };
- init([Port]) ->
- io:format(“tcp sup init~n”),
- {ok,
- { {one_for_one, 5, 60},
- [
- % client supervisor
- { tcp_client_sup,
- {supervisor, start_link, [{local, tcp_client_sup}, ?MODULE, [tcp_client_sup]]},
- permanent,
- In 2000,
- supervisor,
- [tcp_server_listener]
- },
- % tcp listener
- { tcp_server_listener,
- {tcp_server_listener, start_link, [Port]},
- permanent,
- In 2000,
- worker,
- [tcp_server_listener]
- }
- ]
- }
- }.
TCP server Socket listener process (tcp_server_listener.erl) \
[plain] view plain copy
- -module(tcp_server_listener).
- -behaviour(gen_server).
- -export([start_link/1]).
- -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
- -record(state, {lsock}).
- start_link(Port) ->
- io:format(“tcp server listener start ~n”),
- gen_server:start_link({local, ? MODULE}, ? MODULE, [Port], []).
- init([Port]) ->
- process_flag(trap_exit, true),
- Opts = [binary, {packet, 0}, {reuseaddr, true},
- {keepalive, true}, {backlog, 30}, {active, false}],
- State =
- case gen_tcp:listen(Port, Opts) of
- {ok, LSock} ->
- start_server_listener(LSock),
- #state{lsock = LSock};
- _Other ->
- throw({error, {could_not_listen_on_port, Port}}),
- #state{}
- end,
- {ok, State}.
- handle_call(_Request, _From, State) ->
- io:format(“tcp server listener call ~p~n”, [_Request]),
- {reply, ok, State}.
- handle_cast({tcp_accept, Pid}, State) ->
- io:format(“tcp server listener cast ~p~n”, [tcp_accept]),
- start_server_listener(State, Pid),
- {noreply, State};
- handle_cast(_Msg, State) ->
- io:format(“tcp server listener cast ~p~n”, [_Msg]),
- {noreply, State}.
- handle_info({‘EXIT’, Pid, _}, State) ->
- io:format(“tcp server listener info exit ~p~n”, [Pid]),
- start_server_listener(State, Pid),
- {noreply, State};
- handle_info(_Info, State) ->
- io:format(“tcp server listener info ~p~n”, [_Info]),
- {noreply, State}.
- terminate(_Reason, _State) ->
- io:format(“tcp server listener terminate ~p~n”, [_Reason]),
- ok.
- code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
- start_server_listener(State, Pid) ->
- unlink(Pid),
- start_server_listener(State#state.lsock).
- start_server_listener(Lsock) ->
- case tcp_server_sup:start_child(Lsock) of
- {ok, Pid} ->
- link(Pid);
- _Other ->
- do_log
- end.
The TCP server handles the client request process (tcp_server_handler.erl) \
[plain] view plain copy
- -module(tcp_server_handler).
- -behaviour(gen_server).
- -export([start_link/1]).
- -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
- terminate/2, code_change/3]).
- -record(state, {lsock, socket, addr}).
- -define(Timeout, 120*1000).
- start_link(LSock) ->
- io:format(“tcp handler start link~n”),
- gen_server:start_link(? MODULE, [LSock], []).
- init([LSock]) ->
- io:format(“tcp handler init ~n”),
- inet:setopts(LSock, [{active, once}]),
- gen_server:cast(self(), tcp_accept),
- {ok, #state{lsock = LSock}}.
- handle_call(Msg, _From, State) ->
- io:format(“tcp handler call ~p~n”, [Msg]),
- {reply, {ok, Msg}, State}.
- handle_cast(tcp_accept, #state{lsock = LSock} = State) ->
- {ok, CSock} = gen_tcp:accept(LSock),
- io:format(“tcp handler info accept client ~p~n”, [CSock]),
- {ok, {IP, _Port}} = inet:peername(CSock),
- start_server_listener(self()),
- {noreply, State#state{socket=CSock, addr=IP}, ? Timeout};
- handle_cast(stop, State) ->
- {stop, normal, State}.
- handle_info({tcp, Socket, Data}, State) ->
- inet:setopts(Socket, [{active, once}]),
- io:format(“tcp handler info ~p got message ~p~n”, [self(), Data]),
- ok = gen_tcp:send(Socket, <<Data/binary>>),
- {noreply, State, ? Timeout};
- handle_info({tcp_closed, _Socket}, #state{addr=Addr} = State) ->
- io:format(“tcp handler info ~p client ~p disconnected~n”, [self(), Addr]),
- {stop, normal, State};
- handle_info(timeout, State) ->
- io:format(“tcp handler info ~p client connection timeout~n”, [self()]),
- {stop, normal, State};
- handle_info(_Info, State) ->
- io:format(“tcp handler info ingore ~p~n”, [_Info]),
- {noreply, State}.
- terminate(_Reason, #state{socket=Socket}) ->
- io:format(“tcp handler terminate ~p~n”, [_Reason]),
- (catch gen_tcp:close(Socket)),
- ok.
- code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
- start_server_listener(Pid) ->
- gen_server:cast(tcp_server_listener, {tcp_accept, Pid}).
TCP server resource file (tcp_server.app) \
[plain] view plain copy
- {application,tcp_server,
- [{description,”TCP Server”},
- {VSN, “1.0.0”},
- {modules,[tcp_server,tcp_server_app,tcp_server_handler,
- tcp_server_listener,tcp_server_sup]},
- {registered,[]},
- {mod,{tcp_server_app,[]}},
- {env,[]},
- {applications,[kernel,stdlib]}]}.
compiler
Create the following directory structure for your application:
[plain] view plain copy
- ./tcp_server
- ./tcp_server/ebin/
- ./tcp_server/ebin/tcp_server.app
- ./tcp_server/src/tcp_server_app.erl
- ./tcp_server/src/tcp_server_sup.erl
- ./tcp_server/src/tcp_server_listener.erl
- ./tcp_server/src/tcp_server_handler.erl
Linux:
[plain] view plain copy
- $ cd tcp_server/src
- $ for f in tcp*.erl ; do erlc -o .. /ebin $f
- Seems to not work, please use: erlc-O.. /ebin *.erl\
Windows, CMD enter, remember to add environment variables to erL OTP path :\
[plain] view plain copy
- cd tcp_server/src
- for %i in (tcp*.erl) do erlc -o .. /ebin %i
To run the program
1. Start the TCP server
[plain] view plain copy
- erl -pa ebin
- .
- 1> application:start(tcp_server).
- tcp app start
- tcp sup start link
- tcp sup init
- tcp sup init client
- tcp server listener start
- tcp sup start child
- tcp handler start link
- tcp handler init
- ok
- 2> appmon:start().
- {ok, < 0.41.0 >}
\
Note that appmon appears to be an observer after ERl17. So if you can’t start appmon:start(), try observer:start().
Create a client to request the TCP server:
[plain] view plain copy
- 3 > f (S),} {ok, S = gen_tcp: connect (,0,0,1 {127}, 2222, [{0} packet,]).
- {ok, # Port < 0.1859 >}
- TCP handler info Accept client #Port<0.1860>
- tcp server listener cast tcp_accept
- tcp sup start child
- tcp handler start link
- tcp handler init
Send a message to the server using this request:
[plain] view plain copy
- 4> gen_tcp:send(S,<<“hello”>>).
- ok
- TCP Handler Info <0.53.0> got Message <<“hello”>>
4. Receiving the message from the server: \
[plain] view plain copy
- 5> f(M), receive M -> M end.
- {the TCP, # Port < 0.1861 >, “hello”}
5. Now let’s try sending multiple connection requests to the server: \
[plain] view plain copy
- 6 > gen_tcp: connect (,0,0,1 {127}, 2222, [{0} packet,]).
- .
- 7 > gen_tcp: connect (,0,0,1 {127}, 2222, [{0} packet,]).
- .
- 8 > gen_tcp: connect (,0,0,1 {127}, 2222, [{0} packet,]).
- .
\
6. The server program has a timeout function. If there is no operation within 2 minutes, the connection will automatically exit
[plain] view plain copy
- 9> tcp handler info <0.39.0> client connection timeout
- 9> tcp handler terminate normal
- 9> tcp handler info <0.52.0> client connection timeout
- 9> tcp handler terminate normal
- 9> tcp handler info <0.54.0> client connection timeout
- 9> tcp handler terminate normal
- 9> tcp handler info <0.56.0> client connection timeout
- 9> tcp handler terminate normal
\
7. Let’s briefly demonstrate the monitoring behavior of the server:
[plain] view plain copy
- 9 > exit (pid (0,58,0), kill).
- TCP Server listener info exit <0.58.0>
- true
- tcp sup start child
- tcp handler start link
- tcp handler init
\
conclusion
This example demonstrates how to create a simple non-blocking TCP server and how to use the standard Erlang/OTP behavior. As an exercise, the reader is encouraged to try abstracting the generic non-blocking TCP server functionality into a stand-alone behavior.
\
Complete source code download address: download.csdn.net/download/li…