But client started and closed, server accept two connection, and one is ok, the other is bad.
Actually, your server only accepted one connection:
- Enter
loop/1
upon accepting the connection from the client
inet:peername/1
returns {ok,{{192,168,2,184},51608}}
because the socket is still open
gen_tcp:recv/2
returns <<"aa">>
which was sent by the client
gen_tcp:send/2
sends the data from 3 to the client
- Enter
loop/1
again
inet:peername/1
returns {error,enotconn}
because the socket was closed by the client
gen_tcp:recv/2
returns {error,closed}
- The process exits normally
So in reality, your echo server is functioning just fine, however there are some improvements that can be made that are mentioned in the comment made by @zxq9.
Improvement 1
Hand off control of the accepted socket to the newly spawned process.
-module(echo).
-export([listen/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).
listen(Port) ->
{ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
accept(LSocket).
accept(LSocket) ->
{ok, CSocket} = gen_tcp:accept(LSocket),
Ref = make_ref(),
To = spawn(fun() -> init(Ref, CSocket) end),
gen_tcp:controlling_process(CSocket, To),
To ! {handoff, Ref, CSocket},
accept(LSocket).
init(Ref, Socket) ->
receive
{handoff, Ref, Socket} ->
{ok, Peername} = inet:peername(Socket),
io:format("[S] peername ~p~n", [Peername]),
loop(Socket)
end.
loop(Socket) ->
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
io:format("[S] got ~p~n", [Data]),
gen_tcp:send(Socket, Data),
loop(Socket);
{error, closed} ->
io:format("[S] closed~n", []);
E ->
io:format("[S] error ~p~n", [E])
end.
Improvement 2
Wait on the client side for the echo server to send back the data before closing the socket.
spawn(fun () ->
{ok, Socket} = gen_tcp:connect("127.0.0.1", 1111, [binary, {packet, 0}, {active, false}]),
{ok, Peername} = inet:peername(Socket),
io:format("[C] peername ~p~n", [Peername]),
gen_tcp:send(Socket, <<"aa">>),
case gen_tcp:recv(Socket, 0) of
{ok, Data} ->
io:format("[C] got ~p~n", [Data]),
gen_tcp:close(Socket);
{error, closed} ->
io:format("[C] closed~n", []);
E ->
io:format("[C] error ~p~n", [E])
end
end).
Example
The server should look something like this:
1> c(echo).
{ok,echo}
2> echo:listen(1111).
[S] peername {{127,0,0,1},57586}
[S] got <<"aa">>
[S] closed
The client should look something like this:
1> % paste in the code from Improvement 2
<0.34.0>
[C] peername {{127,0,0,1},1111}
[C] got <<"aa">>
Recommendations
As @zxq9 mentioned, this is not OTP style code and probably shouldn't be used for anything beyond educational purposes.
A better approach might be to use something like ranch or gen_listener_tcp for the server side listening and accepting of connections. Both projects have examples of echo servers: tcp_echo (ranch) and echo_server.erl (gen_listener_tcp).