Skip to content

kevsmith/gen_nb_server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Description

gen_nb_server is an OTP behavior designed to simplify writing completely non-blocking TCP servers. Gone are the days of having to use a separate process to perform the listen/accept loop. Instead, gen_nb_server uses features built into prim_inet to create a truly non-blocking server. This is the same mechanism networking-intense projects, like RabbitMQ, use to create their TCP servers.

In addition, every gen_nb_server is also a gen_server so you can gen_server:call/cast/info to your heart's content! What's not to like?

How to use gen_nb_server

  1. Drop the gen_nb_server behavior annotation at the top of your source file like so:
-behavior(gen_nb_server).
  1. Implement the required functions. These include the usual suspects from gen_server (see the gen_server manpage for details) and two new functions: sock_opts/0 and new_connection/4.

2a. sock_opts/0 is used by gen_nb_server to retrieve the set of socket options to use when creating the listen socket. These options will also be inherited by the client connection sockets. See the manpages for gen_tcp and inet for more information on socket options.

2b. new_connection/4 is called every time a new connection is accepted. It is called with the newly connected socket and the server's current state.

Here's a complete example which should give you an idea on how to use gen_nb_server:

-module(example).

-export([start_link/0,
         add_listener/3,
         remove_listener/3]).

-export([init/2, handle_call/3, handle_cast/2, handle_info/2]).
-export([terminate/2, sock_opts/0, new_connection/4]).

-behavior(gen_nb_server).

start_link() ->
    gen_nb_server:start_link(?MODULE, []).

add_listener(Pid, IpAddr, Port) ->
    gen_server:call(Pid, {add_listener, IpAddr, Port}).

remove_listener(Pid, IpAddr, Port) ->
    gen_server:call(Pid, {remove_listener, IpAddr, Port}).

init([], State) ->
    {ok, State}.

handle_call({add_listener, IpAddr, Port}, _From, State) ->
    case gen_nb_server:add_listen_socket({IpAddr, Port}, State) of
        {ok, State1} ->
            {reply, ok, State1};
        Error ->
            {reply, Error, State}
    end;
handle_call({remove_listener, IpAddr, Port}, _From, State) ->
    case gen_nb_server:remove_listen_socket({IpAddr, Port}, State) of
        {ok, State1} ->
            {reply, ok, State1};
        Error ->
            {reply, Error, State}
    end;
handle_call(_Msg, _From, State) ->
    {reply, ignored, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

handle_info({tcp, Sock, Data}, State) ->
    Me = self(),
    P = spawn(fun() -> worker(Me, Sock, Data) end),
    gen_tcp:controlling_process(Sock, P),
    {noreply, State};

handle_info(_Msg, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

sock_opts() ->
    [binary, {active, once}, {packet, 0}].

new_connection(_IpAddr, _Port, Sock, State) ->
    Me = self(),
    P = spawn(fun() -> worker(Me, Sock) end),
    gen_tcp:controlling_process(Sock, P),
    {ok, State}.

worker(Owner, Sock) ->
    gen_tcp:send(Sock, "Hello\n"),
    inet:setopts(Sock, [{active, once}]),
    gen_tcp:controlling_process(Sock, Owner).

worker(Owner, Sock, Data) ->
    gen_tcp:send(Sock, Data),
    inet:setopts(Sock, [{active, once}]),
    gen_tcp:controlling_process(Sock, Owner).

Note: This code is also available in priv/example.

About

OTP behavior for writing non-blocking servers

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages