Create initial version of server

Just a very simple TCP echo server to get started with.
This commit is contained in:
Camden Dixie O'Brien 2025-02-22 12:38:41 +00:00
parent a66b817ba3
commit bf5f517e85
7 changed files with 112 additions and 0 deletions

2
server/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
_build/*
*.beam

2
server/rebar.config Normal file
View File

@ -0,0 +1,2 @@
{erl_opts, [debug_info]}.
{deps, []}.

1
server/rebar.lock Normal file
View File

@ -0,0 +1 @@
[].

18
server/src/proto_sup.erl Normal file
View File

@ -0,0 +1,18 @@
% Copyright (c) Camden Dixie O'Brien
% SPDX-License-Identifier: AGPL-3.0-only
-module(proto_sup).
-behaviour(supervisor).
-export([start_link/1]).
-export([init/1]).
start_link(Port) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, [Port]).
init([Port]) ->
SupFlags = #{stragegy => one_for_all,
intensity => 1,
period => 5},
ChildSpecs = [tcp_server:child_spec(Port)],
{ok, {SupFlags, ChildSpecs}}.

View File

@ -0,0 +1,10 @@
{application, study_system_server,
[{description, "Study System Server"},
{vsn, "0.1.0"},
{registered, []},
{mod, {study_system_server_app, []}},
{applications, [kernel, stdlib]},
{env, []},
{modules, []},
{licenses, ["AGPL-3.0-only"]},
{links, []}]}.

View File

@ -0,0 +1,14 @@
% Copyright (c) Camden Dixie O'Brien
% SPDX-License-Identifier: AGPL-3.0-only
-module(study_system_server_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
Port = application:get_env(study_system_server, port, 12888),
proto_sup:start_link(Port).
stop(_State) ->
ok.

65
server/src/tcp_server.erl Normal file
View File

@ -0,0 +1,65 @@
% Copyright (c) Camden Dixie O'Brien
% SPDX-License-Identifier: AGPL-3.0-only
-module(tcp_server).
-behaviour(gen_server).
-export([start_link/1, child_spec/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-record(state, {socket, acceptor}).
start_link(Port) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Port], []).
child_spec(Port) ->
#{id => ?MODULE,
start => {?MODULE, start_link, [Port]},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [?MODULE]}.
init([Port]) ->
{ok, Socket} = gen_tcp:listen(Port, [binary, {active, true}]),
Pid = spawn_link(fun() -> acceptor_loop(Socket) end),
{ok, #state{socket = Socket, acceptor = Pid}}.
handle_call(_Request, _From, State) ->
{reply, ok, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({'EXIT', Pid, Reason}, State = #state{acceptor = Pid}) ->
{stop, {acceptor_died, Reason}, State};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, #state{socket = Socket}) ->
gen_tcp:close(Socket),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
acceptor_loop(Socket) ->
case gen_tcp:accept(Socket) of
{ok, ClientSocket} ->
gen_tcp:controlling_process(
ClientSocket,
spawn(fun() -> handle_connection(ClientSocket) end)),
acceptor_loop(Socket);
{error, closed} ->
ok
end.
handle_connection(Socket) ->
receive
{tcp, Socket, Data} ->
gen_tcp:send(Socket, Data),
handle_connection(Socket);
{tcp_closed, Socket} ->
ok
end.