aboutsummaryrefslogblamecommitdiffstats
path: root/erts/emulator/test/socket_client.erl
blob: 1c07e799b8e1f18e6d76b70d3f0c8e426cde63e5 (plain) (tree)



















                                                                           
                       
 
         




                                                                


                         
 
                                                                              
 
              



                         

                  



                          

                   



                                        
 
                   



                                         
 







                                                           


                  



                          

                   



                                       
 
                   



                                        
 



                                                     

 


                                                              

                                         








                                                       
                          



































                                                                                    


                            


                                                  
               
                                               
 
                                                      

                                       
                              
                                                        
                                           
                             
                                       


                                                 




                                    
                                                     

                                       
                                                        
                                           

                                       



                                                 
         
                  
                                 
                                    

        













































































                                                                
                                        









                                                   


                                                                 


                                  










                                                   



                                                                 


                                  

        
 









                                                    
                    

                                

                                    







                                   
                                                    
                                  

                                        
             
                                                            

                                      









                                                                                     
                                             
                                            






                                                                           
                                                             
                        

                                                             
                                               



                                                
                                       
                                        
        



                                                     



                                             



                                     
 
    
 





                                                               
                     


                                           



                             
 
                                                           





                                   
                                                                             





                                                     







                                                             





                                                          
                                                                            




                                  







                                                             




                                

 














                                                                            

      

                              
 

                              
 




                                                                           
    



                                                             



      

                                 



                     

                                        
 


                                                         
 







                                                        
 







                                                                                  


      
 















                               
          
                 

       

              
          
                 
    
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2018-2018. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%

-module(socket_client).

-export([
         start/1, start/2, start/5, start/6,
         start_tcp/1, start_tcp/2, start_tcp/3,
         start_tcp4/1, start_tcp4/2, start_tcp6/1, start_tcp6/2,
         start_udp/1, start_udp/2, start_udp/3,
         start_udp4/1, start_udp4/2, start_udp6/1, start_udp6/2
        ]).

-define(LIB, socket_lib).

-record(client, {socket, verbose = true, msg = true, type, dest, msg_id = 1}).

start(Port) ->
    start(Port, 1).

start(Port, Num) ->
    start_tcp(Port, Num).

start_tcp(Port) ->
    start_tcp(Port, 1).

start_tcp(Port, Num) ->
    start_tcp4(Port, Num).

start_tcp4(Port) ->
    start_tcp4(Port, 1).

start_tcp4(Port, Num) ->
    start(inet, stream, tcp, Port, Num).

start_tcp6(Port) ->
    start_tcp6(Port, 1).

start_tcp6(Port, Num) ->
    start(inet6, stream, tcp, Port, Num).

start_tcp(Addr, Port, Num) when (size(Addr) =:= 4) andalso 
                                is_integer(Num) andalso 
                                (Num > 0) ->
    start(inet, stream, tcp, Addr, Port, Num);
start_tcp(Addr, Port, Num) when (size(Addr) =:= 8) andalso 
                                is_integer(Num) andalso 
                                (Num > 0) ->
    start(inet6, stream, tcp, Addr, Port, Num).


start_udp(Port) ->
    start_udp(Port, 1).

start_udp(Port, Num) ->
    start_udp4(Port, Num).

start_udp4(Port) ->
    start_udp4(Port, 1).

start_udp4(Port, Num) ->
    start(inet, dgram, udp, Port, Num).

start_udp6(Port) ->
    start_udp6(Port, 1).

start_udp6(Port, Num) ->
    start(inet6, dgram, udp, Port, Num).

start_udp(Addr, Port, Num) when (size(Addr) =:= 4) ->
    start(inet, dgram, udp, Addr, Port, Num);
start_udp(Addr, Port, Num) when (size(Addr) =:= 8) ->
    start(inet6, dgram, udp, Addr, Port, Num).


start(Domain, Type, Proto, Port, Num) 
  when is_integer(Port) andalso is_integer(Num) ->
    start(Domain, Type, Proto, which_addr(Domain), Port, Num);

start(Domain, Type, Proto, Addr, Port) ->
    start(Domain, Type, Proto, Addr, Port, 1).

start(Domain, Type, Proto, Addr, Port, 1 = Num) ->
    start(Domain, Type, Proto, Addr, Port, Num, true);
start(Domain, Type, Proto, Addr, Port, Num) 
  when is_integer(Num) andalso (Num > 1) ->
    start(Domain, Type, Proto, Addr, Port, Num, false).

start(Domain, Type, Proto, Addr, Port, Num, Verbose) ->
    put(sname, "starter"),
    Clients = start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose),
    await_clients(Clients).

start_clients(Num, Domain, Type, Proto, Addr, Port, Verbose) ->
    start_clients(Num, 1, Domain, Type, Proto, Addr, Port, Verbose, []).

start_clients(Num, ID, Domain, Type, Proto, Addr, Port, Verbose, Acc) 
  when (Num > 0) ->
    StartClient = fun() ->
                          start_client(ID, Domain, Type, Proto, Addr, Port, Verbose)
                  end,
    {Pid, _} = spawn_monitor(StartClient),
    ?LIB:sleep(500),
    i("start client ~w", [ID]),
    start_clients(Num-1, ID+1, Domain, Type, Proto, Addr, Port, Verbose, [Pid|Acc]);
start_clients(_, _, _, _, _, _, _, _, Acc) ->
    i("all client(s) started"),
    lists:reverse(Acc).

await_clients([]) ->
    i("all clients done");
await_clients(Clients) ->
    receive
        {'DOWN', _MRef, process, Pid, _Reason} ->
            case lists:delete(Pid, Clients) of
                Clients2 when (Clients2 =/= Clients) ->
                    i("client ~p done", [Pid]),
                    await_clients(Clients2);
                _ ->
                    await_clients(Clients)
            end
    end.


start_client(ID, Domain, Type, Proto, Addr, Port, Verbose) ->
    put(sname, ?LIB:f("client[~w]", [ID])),
    SA = #{family => Domain,
           addr   => Addr,
           port   => Port},
    %% The way we use tos only works because we
    %% send so few messages (a new value for every
    %% message).
    tos_init(),
    do_start(Domain, Type, Proto, SA, Verbose).

do_start(Domain, stream = Type, Proto, SA, Verbose) ->
    try do_init(Domain, Type, Proto) of
        Sock ->
            connect(Sock, SA),
            maybe_print_start_info(Verbose, Sock, Type),
            %% Give the server some time...
            ?LIB:sleep(5000),
            %% ok = socket:close(Sock),
            send_loop(#client{socket  = Sock, 
                              type    = Type,
                              verbose = Verbose})
    catch
        throw:E ->
            e("Failed initiate: "
              "~n   Error: ~p", [E])
    end;
do_start(Domain, dgram = Type, Proto, SA, Verbose) ->
    try do_init(Domain, Type, Proto) of
        Sock ->
            maybe_print_start_info(Verbose, Sock, Type),
            %% Give the server some time...
            ?LIB:sleep(5000),
            %% ok = socket:close(Sock),
            send_loop(#client{socket  = Sock,
                              type    = Type,
                              dest    = SA,
                              verbose = Verbose})
    catch
        throw:E ->
            e("Failed initiate: "
              "~n   Error: ~p", [E])
    end.

maybe_print_start_info(true = _Verbose, Sock, stream = _Type) ->
    {ok, Name}    = socket:sockname(Sock),
    {ok, Peer}    = socket:peername(Sock),
    {ok, Domain}  = socket:getopt(Sock, socket, domain),
    {ok, Type}    = socket:getopt(Sock, socket, type),
    {ok, Proto}   = socket:getopt(Sock, socket, protocol),
    {ok, OOBI}    = socket:getopt(Sock, socket, oobinline),
    {ok, SndBuf}  = socket:getopt(Sock, socket, sndbuf),
    {ok, RcvBuf}  = socket:getopt(Sock, socket, rcvbuf),
    {ok, Linger}  = socket:getopt(Sock, socket, linger),
    {ok, MTU}     = socket:getopt(Sock, ip,     mtu),
    {ok, MTUDisc} = socket:getopt(Sock, ip,     mtu_discover),
    {ok, MALL}    = socket:getopt(Sock, ip,     multicast_all),
    {ok, MIF}     = socket:getopt(Sock, ip,     multicast_if),
    {ok, MLoop}   = socket:getopt(Sock, ip,     multicast_loop),
    {ok, MTTL}    = socket:getopt(Sock, ip,     multicast_ttl),
    {ok, RecvTOS} = socket:getopt(Sock, ip,     recvtos),
    i("connected: "
      "~n   From: ~p"
      "~n   To:   ~p"
      "~nwhen"
      "~n   (socket) Domain:         ~p"
      "~n   (socket) Type:           ~p"
      "~n   (socket) Protocol:       ~p"
      "~n   (socket) OOBInline:      ~p"
      "~n   (socket) SndBuf:         ~p"
      "~n   (socket) RcvBuf:         ~p"
      "~n   (socket) Linger:         ~p"
      "~n   (ip)     MTU:            ~p"
      "~n   (ip)     MTU Discovery:  ~p"
      "~n   (ip)     Multicast ALL:  ~p"
      "~n   (ip)     Multicast IF:   ~p"
      "~n   (ip)     Multicast Loop: ~p"
      "~n   (ip)     Multicast TTL:  ~p"
      "~n   (ip)     RecvTOS:        ~p"
      "~n   => wait some", 
      [Name, Peer,
       Domain, Type, Proto,
       OOBI, SndBuf, RcvBuf, Linger,
       MTU, MTUDisc, MALL, MIF, MLoop, MTTL,
       RecvTOS]);
maybe_print_start_info(true = _Verbose, Sock, dgram = _Type) ->
    {ok, Domain}  = socket:getopt(Sock, socket, domain),
    {ok, Type}    = socket:getopt(Sock, socket, type),
    {ok, Proto}   = socket:getopt(Sock, socket, protocol),
    {ok, OOBI}    = socket:getopt(Sock, socket, oobinline),
    {ok, SndBuf}  = socket:getopt(Sock, socket, sndbuf),
    {ok, RcvBuf}  = socket:getopt(Sock, socket, rcvbuf),
    {ok, Linger}  = socket:getopt(Sock, socket, linger),
    {ok, MALL}    = socket:getopt(Sock, ip,     multicast_all),
    {ok, MIF}     = socket:getopt(Sock, ip,     multicast_if),
    {ok, MLoop}   = socket:getopt(Sock, ip,     multicast_loop),
    {ok, MTTL}    = socket:getopt(Sock, ip,     multicast_ttl),
    {ok, RecvTOS} = socket:getopt(Sock, ip,     recvtos),
    {ok, RecvTTL} = socket:getopt(Sock, ip,     recvttl),
    i("initiated when: "
      "~n   (socket) Domain:         ~p"
      "~n   (socket) Type:           ~p"
      "~n   (socket) Protocol:       ~p"
      "~n   (socket) OOBInline:      ~p"
      "~n   (socket) SndBuf:         ~p"
      "~n   (socket) RcvBuf:         ~p"
      "~n   (socket) Linger:         ~p"
      "~n   (ip)     Multicast ALL:  ~p"
      "~n   (ip)     Multicast IF:   ~p"
      "~n   (ip)     Multicast Loop: ~p"
      "~n   (ip)     Multicast TTL:  ~p"
      "~n   (ip)     RecvTOS:        ~p"
      "~n   (ip)     RecvTTL:        ~p"
      "~n   => wait some",
      [Domain, Type, Proto,
       OOBI, SndBuf, RcvBuf, Linger,
       MALL, MIF, MLoop, MTTL,
       RecvTOS, RecvTTL]);
maybe_print_start_info(_Verbose, _Sock, _Type) ->
    ok.

    
do_init(Domain, stream = Type, Proto) ->
    i("try (socket) open"),
    Sock = case socket:open(Domain, Type, Proto) of
               {ok, S} ->
                   S;
               {error, OReason} ->
                   throw({open, OReason})
           end,
    i("try (socket) bind"),
    case socket:bind(Sock, any) of
        {ok, _P} ->
            ok = socket:setopt(Sock, socket, timestamp, true),
            ok = socket:setopt(Sock, ip,     tos,       mincost),
            ok = socket:setopt(Sock, ip,     recvtos,   true),
            Sock;
        {error, BReason} ->
            throw({bind, BReason})
    end;
do_init(Domain, dgram = Type, Proto) ->
    i("try (socket) open"),
    Sock = case socket:open(Domain, Type, Proto) of
               {ok, S} ->
                   S;
               {error, OReason} ->
                   throw({open, OReason})
           end,
    case socket:bind(Sock, any) of
        {ok, _} ->
            ok = socket:setopt(Sock, socket, timestamp, true),
            ok = socket:setopt(Sock, ip,     tos,       mincost),
            ok = socket:setopt(Sock, ip,     recvtos,   true),
            ok = socket:setopt(Sock, ip,     recvttl,   true),
            Sock;
        {error, BReason} ->
            throw({bind, BReason})
    end.


which_addr(Domain) ->
    Iflist = case inet:getifaddrs() of
                 {ok, IFL} ->
                     IFL;
                 {error, Reason} ->
                     throw({inet,getifaddrs,Reason})
             end,
    which_addr(Domain, Iflist).


connect(Sock, SA) ->
    i("try (socket) connect to:"
      "~n   ~p", [SA]),
    case socket:connect(Sock, SA) of
        ok ->
            ok;
        {error, Reason} ->
            e("connect failure: "
              "~n   ~p", [Reason]),
            exit({connect, Reason})
    end.


send_loop(#client{msg_id = N} = C) when (N =< 10) ->
    i("try send request ~w", [N]),
    Req = ?LIB:enc_req_msg(N, "hejsan"),
    case send(C, Req) of
        ok ->
            i("request ~w sent - now try read answer", [N]),
            case recv(C) of
                {ok, {Source, Msg}} ->
                    if
                        (C#client.verbose =:= true) ->
                            i("received ~w bytes of data~s", 
                              [size(Msg),  case Source of
                                               undefined -> "";
                                               _ -> ?LIB:f(" from:~n   ~p", [Source])
                                           end]);
                        true ->
                            i("received ~w bytes", [size(Msg)])
                    end,
                    case ?LIB:dec_msg(Msg) of
                        {reply, N, Reply} ->
                            if
                                (C#client.verbose =:= true) ->
                                    i("received reply ~w: ~p", [N, Reply]);
                                true ->
                                    i("received reply ~w", [N])
                            end,
                            ?LIB:sleep(500), % Just to spread it out a bit
                            send_loop(C#client{msg_id = N+1})
                    end;
                {error, RReason} ->
                    e("Failed recv response for request ~w: "
                      "~n   ~p", [N, RReason]),
                    exit({failed_recv, RReason})
            end;
        {error, SReason} ->
            e("Failed send request ~w: "
              "~n   ~p", [N, SReason]),
            exit({failed_send, SReason})
    end;
send_loop(Client) ->
    sock_close(Client).

sock_close(#client{socket = Sock, verbose = true}) ->
    i("we are done - close the socket when: "
      "~n   ~p", [socket:info()]),
    ok = socket:close(Sock),
    i("we are done - socket closed when: "
      "~n   ~p", [socket:info()]);
sock_close(#client{socket = Sock}) ->
    i("we are done"),
    ok = socket:close(Sock).

    

send(#client{socket = Sock, type = stream}, Msg) ->
    socket:send(Sock, Msg);
send(#client{socket = Sock, type = dgram, dest = Dest}, Msg) ->
    %% i("try send to: "
    %%   "~n   ~p", [Dest]),
    %% ok = socket:setopt(Sock, otp, debug, true),
    TOS = tos_next(),
    ok = socket:setopt(Sock, ip, tos, TOS),
    case socket:sendto(Sock, Msg, Dest) of
        ok = OK ->
            OK;
        {error, _} = ERROR ->
            ERROR
    end.

recv(#client{socket = Sock, type = stream, msg = false}) ->
    case socket:recv(Sock) of
        {ok, Msg} ->
            {ok, {undefined, Msg}};
        {error, _} = ERROR ->
            ERROR
    end;
recv(#client{socket = Sock, verbose = Verbose, type = stream, msg = true}) ->
    case socket:recvmsg(Sock) of
        %% An iov of length 1 is an simplification...
        {ok, #{addr  := undefined = Source,
               iov   := [Msg],
               ctrl  := CMsgHdrs,
               flags := Flags}} ->
            if
                (Verbose =:= true) ->
                    i("received message: "
                      "~n   CMsgHdr: ~p"
                      "~n   Flags:   ~p", [CMsgHdrs, Flags]);
                true ->
                    ok
                        end,
            {ok, {Source, Msg}};
        {error, _} = ERROR ->
            ERROR
    end;
recv(#client{socket = Sock, type = dgram, msg = false}) ->
    socket:recvfrom(Sock);
recv(#client{socket = Sock, verbose = Verbose, type = dgram, msg = true}) ->
    case socket:recvmsg(Sock) of
        {ok, #{addr  := Source,
               iov   := [Msg],
               ctrl  := CMsgHdrs,
               flags := Flags}} ->
            if
                (Verbose =:= true) ->
                    i("received message: "
                      "~n   CMsgHdr: ~p"
                      "~n   Flags:   ~p", [CMsgHdrs, Flags]);
                true ->
                    ok
            end,
            {ok, {Source, Msg}};
        {error, _} = ERROR ->
            ERROR
    end.
        


which_addr(_Domain, []) ->
    throw(no_address);
which_addr(Domain, [{Name, IFO}|_IFL]) when (Name =/= "lo") ->
    which_addr2(Domain, IFO);
which_addr(Domain, [_|IFL]) ->
    which_addr(Domain, IFL).

which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) ->
    Addr;
which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) ->
    Addr;
which_addr2(Domain, [_|IFO]) ->
    which_addr2(Domain, IFO).


%% ---

%% enc_req_msg(N, Data) ->
%%     enc_msg(?REQ, N, Data).

%% enc_rep_msg(N, Data) ->
%%     enc_msg(?REP, N, Data).

%% enc_msg(Type, N, Data) when is_list(Data) ->
%%     enc_msg(Type, N, list_to_binary(Data));
%% enc_msg(Type, N, Data) 
%%   when is_integer(Type) andalso is_integer(N) andalso is_binary(Data) ->
%%     <<Type:32/integer, N:32/integer, Data/binary>>.
    
%% dec_msg(<<?REQ:32/integer, N:32/integer, Data/binary>>) ->
%%     {request, N, Data};
%% dec_msg(<<?REP:32/integer, N:32/integer, Data/binary>>) ->
%%     {reply, N, Data}.


%% ---

%% sleep(T) ->
%%     receive after T -> ok end.

                     
%% ---

%% formated_timestamp() ->
%%     format_timestamp(os:timestamp()).

%% format_timestamp(Now) ->
%%     N2T = fun(N) -> calendar:now_to_local_time(N) end,
%%     format_timestamp(Now, N2T, true).

%% format_timestamp({_N1, _N2, N3} = N, N2T, true) ->
%%     FormatExtra = ".~.2.0w",
%%     ArgsExtra   = [N3 div 10000],
%%     format_timestamp(N, N2T, FormatExtra, ArgsExtra);
%% format_timestamp({_N1, _N2, _N3} = N, N2T, false) ->
%%     FormatExtra = "",
%%     ArgsExtra   = [],
%%     format_timestamp(N, N2T, FormatExtra, ArgsExtra).

%% format_timestamp(N, N2T, FormatExtra, ArgsExtra) ->
%%     {Date, Time}   = N2T(N),
%%     {YYYY,MM,DD}   = Date,
%%     {Hour,Min,Sec} = Time,
%%     FormatDate =
%%         io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w" ++ FormatExtra,
%%                       [YYYY, MM, DD, Hour, Min, Sec] ++ ArgsExtra),
%%     lists:flatten(FormatDate).


%% ---

tos_init() ->
    put(tos, 1).

tos_next() ->
    case get(tos) of
        TOS when (TOS < 100) ->
            put(tos, TOS + 1),
            TOS;
        _ ->
            put(tos, 1),
            1
    end.


%% ---

e(F, A) ->
    ?LIB:e(F, A).

i(F) ->
    ?LIB:i(F).

i(F, A) ->
    ?LIB:i(F, A).