1

I am making a socket connection to a localhost. I would like to receive input back from from the server on this connection(or any connection, but using the same connection seemed like the easiest solution). Because several clients will be able to connect to my server I created a logon function, which among other things, will spawn a thread that listens for messages sent from the server.

When I enter in the commands

c(erlSoc).    
erlSoc:logon("Jacob").

I receive the error

[C] error {error,einval}

I know that this line

E ->
                io:format("[C] error ~p~n", [E])

is printing it, but what do I need to change so that this error doesn't happen and I'm able to receive messages from the server.


Main point of Interest from erlSoc.erl

logon(Uname) ->
    {ok, Sock} = gen_tcp:connect("localhost", 5300, [binary, {packet, 0}]),
    spawn(erlSoc, client_receive, [Sock]),
    io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n"),
    client(Sock, Uname).

client_receive(Sock) ->
    case gen_tcp:recv(Sock, 0) of
       {ok, Data} ->
            io:format("[C]  ~p~n", [Data]);
        {error, closed} ->
            io:format("[C] closed~n", []);
        E ->
            io:format("[C] error ~p~n", [E])
    end.

erlSoc.erl

-module(erlSoc).
-export([start_server/0, logon/1, remove/2, server/1, client_receive/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

listen() ->
    {ok, LSocket} = gen_tcp:listen(5300, ?TCP_OPTIONS),
    io:format("Accepted the socket connection ~n"),
    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},
    io:format("Second Test ~n"),
    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} ->
        List = binary_to_list(Data),
        List2 = string:tokens(List,","),
            io:format("[S] got ~p~n", [List2]),
            parse_data(Socket, List2),
            loop(Socket);
        {error, closed} ->
            io:format("[S] closed~n", []);
        E ->
            io:format("[S] error ~p~n", [E])
    end.

server_node() ->
    erlSoc@localhost.

parse_data(Soc, [Task, Rname | Message]) -> 
    if 
    Task =:= "message" ->
        {erlSoc, self()} ! {message, Rname, Message};
    Task =:= "create" ->
        {erlSoc, server_node()} ! {create, Rname, Soc};
    Task =:= "list" ->
        {erlSoc, self()} ! {list, Soc};
    Task =:= "join" ->
        {erlSoc, self()} ! {join, Rname, Soc};
    Task =:= "leave" ->
        {erlSoc, self()} ! {leave, Rname, Soc}
    end.

start_server() ->
    spawn(erlSoc, server, [[[]]]),
    listen().

remove(X, L) ->
    [Y || Y <- L, Y =/= X].

server(RoomList) ->
    receive
        {message, Rname, Message} ->
            [_|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[Users|[Messages|Message]]]),
            send_message(Message, Rname, Users),
            server(RoomUpdate);
        {create, Rname, Soc} ->
            server([[Rname|[Soc|[]]]|RoomList]);
        {list, Soc} ->
            {Rooms, _} = RoomList,
            gen_tcp:send(Soc,Rooms),
            server(RoomList);
        {join, Rname, User} ->
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[[Users|User]|[Messages]]]),
            gen_tcp:send(User,[Rname,Messages]),
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomUpdate),
            io:format("Users in ~p : ~p~n", [Rname, Users]),
            server(RoomList);
        {leave, Rname, User} ->
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            NewU = remove(User, Users),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[[NewU]|[Messages]]]),
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomUpdate),
            io:format("Users in ~p : ~p~n", [Rname, Users]),
            server(RoomUpdate)
    end.


send_message(Message, ChatRoom, []) ->
    void;
send_message(Message, Chatroom, [To|Users]) ->
    gen_tcp:send(To,"Message From Chatroom "++Chatroom++": "++Message),
    send_message(Message, Chatroom, Users).


logon(Uname) ->
    {ok, Sock} = gen_tcp:connect("localhost", 5300, [binary, {packet, 0}]),
    spawn(erlSoc, client_receive, [Sock]),
    io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n"),
    client(Sock, Uname).

client_receive(Sock) ->
    case gen_tcp:recv(Sock, 0) of
       {ok, Data} ->
            io:format("[C]  ~p~n", [Data]);
        {error, closed} ->
            io:format("[C] closed~n", []);
        E ->
            io:format("[C] error ~p~n", [E])
    end.

client(Sock, Uname) ->
    {ok,[Task]} = io:fread("Task? : ", "~s"),
    if
    Task =:= "message" ->
        {ok, [Rname]} = io:fread("Send the message to which room? : ", "~s"),
        Message = io:get_line("Type your message: "),
        ok = gen_tcp:send(Sock,"message,"++Rname++","++Uname++": "++Message);
    Task =:= "create" ->
        {ok, [Rname]} = io:fread("Enter a room name : ", "~s"),
        ok = gen_tcp:send(Sock, "create,"++Rname);
    Task =:= "list" ->
        ok = gen_tcp:send(Sock, "list");
    Task =:= "join" ->
        {ok, [Rname]} = io:fread("Leave Which Room? : ", "~s"),
        ok = gen_tcp:send(Sock, "leave,"++Rname);
    Task =:= "leave" ->
        {ok, [Rname]} = io:fread("Join Which Room? : ", "~s"),
        ok = gen_tcp:send(Sock, "join,"++Rname);
    Task =:= "help" ->
        io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n");
    Task =:= "exit" ->
        gen_tcp:close(Sock)
    end.

Update

-module(erlSoc).
-export([start_server/0, logon/1, remove/2, server/1, client_receive/1, client/2]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

listen() ->
    {ok, LSocket} = gen_tcp:listen(5300, ?TCP_OPTIONS),
    io:format("Accepted the socket connection ~n"),
    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},
    io:format("Second Test ~n"),
    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} ->
        List = binary_to_list(Data),
        List2 = string:tokens(List,","),
            io:format("[S] got ~p~n", [List2]),
            parse_data(Socket, List2),
            loop(Socket);
        {error, closed} ->
            io:format("[S] closed~n", []);
        E ->
            io:format("[S] error ~p~n", [E])
    end.

server_node() ->
    erlSoc@localhost.

parse_data(Soc, [Task, Rname | Message]) -> 
    case Task of 
    "message" ->
        {erlSoc, server_node()} ! {message, Rname, Message};
    "create" ->
        io:format("[C]  ~p~n", [Rname]),
        {erlSoc, server_node()} ! {create, Rname, Soc};
    "list" ->
        {erlSoc, server_node()} ! {list, Soc};
    "join" ->
        {erlSoc, server_node()} ! {join, Rname, Soc};
    "leave" ->
        {erlSoc, server_node()} ! {leave, Rname, Soc}
    end.

start_server() ->
    spawn(erlSoc, server, [[[]]]),
    listen().

remove(X, L) ->
    [Y || Y <- L, Y =/= X].

server(RoomList) ->
    receive
        {message, Rname, Message} ->
            [_|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[Users|[Messages|Message]]]),
            send_message(Message, Rname, Users),
            server(RoomUpdate);
        {create, Rname, Soc} ->
            server([[Rname|[Soc|[]]]|RoomList]),
            io:format("[C]  ~p~n", [Rname]);
        {list, Soc} ->
            {Rooms, _} = RoomList,
            gen_tcp:send(Soc,Rooms),
            server(RoomList);
        {join, Rname, User} ->
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[[Users|User]|[Messages]]]),
            gen_tcp:send(User,[Rname,Messages]),
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomUpdate),
            io:format("Users in ~p : ~p~n", [Rname, Users]),
            server(RoomList);
        {leave, Rname, User} ->
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomList),
            NewU = remove(User, Users),
            RoomUpdate = lists:keyreplace(Rname, 1, RoomList, [Rname|[[NewU]|[Messages]]]),
            [Room|[Users|Messages]] = lists:keyfind(Rname, 1, RoomUpdate),
            io:format("Users in ~p : ~p~n", [Rname, Users]),
            server(RoomUpdate)
    end.


send_message(Message, ChatRoom, []) ->
    void;
send_message(Message, Chatroom, [To|Users]) ->
    gen_tcp:send(To,"Message From Chatroom "++Chatroom++": "++Message),
    send_message(Message, Chatroom, Users).


logon(Uname) ->
    {ok, Sock} = gen_tcp:connect("localhost", 5300, [binary, {packet, 0}, {active, false}]),
    Merp = spawn(erlSoc, client_receive, [Sock]),
    gen_tcp:controlling_process(Sock, Merp),
    io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n"),
    Raph = spawn(erlSoc, client, [Sock, Uname]),
    gen_tcp:controlling_process(Sock, Raph).

client_receive(Sock) ->
    case gen_tcp:recv(Sock, 0) of
       {ok, Data} ->
            io:format("[C]  ~p~n", [Data]),
            client_receive(Sock);
        {error, closed} ->
            io:format("[C] closed~n", []);
        E ->
            io:format("[C] error ~p~n", [E])
    end.

client(Sock, Uname) ->
    {ok,[Task]} = io:fread("Task? : ", "~s"),
    if
    Task =:= "message" ->
        {ok, [Rname]} = io:fread("Send the message to which room? : ", "~s"),
        Message = io:get_line("Type your message: "),
        ok = gen_tcp:send(Sock,"message,"++Rname++","++Uname++": "++Message);
    Task =:= "create" ->
        {ok, [Rname]} = io:fread("Enter a room name : ", "~s"),
        ok = gen_tcp:send(Sock, "create,"++Rname);
    Task =:= "list" ->
        ok = gen_tcp:send(Sock, "list");
    Task =:= "join" ->
        {ok, [Rname]} = io:fread("Leave Which Room? : ", "~s"),
        ok = gen_tcp:send(Sock, "leave,"++Rname);
    Task =:= "leave" ->
        {ok, [Rname]} = io:fread("Join Which Room? : ", "~s"),
        ok = gen_tcp:send(Sock, "join,"++Rname);
    Task =:= "help" ->
        io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n");
    Task =:= "exit" ->
        gen_tcp:close(Sock)
    end,
    client(Sock,Uname).
Sam
  • 1,765
  • 11
  • 82
  • 176

1 Answers1

2

Since you tagged functional-programming I will tell you more than just bugs I've found in code.

I've placed some comments in your code:


erlSoc.erl

-module(erlSoc).
-export([start_server/0, logon/1, remove/2, server/1, client_receive/1]).
-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

% ...

loop(Socket) ->
    % Remember that in server, after accepting a connection, 
    % you are waiting for packet
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->

% ...

parse_data(Soc, [Task, Rname | Message]) -> 
if 
Task =:= "message" ->
    % below code does not work. If you want to send a message to a process,
    % you can use its process id (pid) or its registered name or name of the
    % node which that process belongs to with its pid
    % Based server/1 function, I think that you want to have a process named
    % erlSoc and send message to it. If yes, then you have to register that 
    % process for that name using erlang:register/2 and just use:
    % erlSoc ! YourMessage
    {erlSoc, self()} ! {message, Rname, Message};
% ...
Task =:= "list" ->
    % It seems that you are sending a socket to your erlSoc process and you
    % did not make erlSoc controller of socket !
    {erlSoc, self()} ! {list, Soc};

logon(Uname) ->
    % In below, Your socket will be in 'active' mode and in client_receive/2 function
    % you have called gen_tcp:recv/2 on socket which is invalid (einval)
    {ok, Sock} = gen_tcp:connect("localhost", 5300, [binary, {packet, 0}]),

    % In below, You are passing Sock to a new process which is not owner of socket
    % You have to call gen_tcp:controlling_process/2 here
    spawn(erlSoc, client_receive, [Sock]),
    io:format("Create a Room: create~nList Rooms: list~nJoin Rooms: join~n Leave Rooms: leave~nSend a message: message ~n"),
    % Also here you need to call gen_tcp:controlling_process/2 in that process to
    % become owner of socket in this process
    client(Sock, Uname).

client_receive(Sock) ->
    % As I mentioned, In server you were waiting for a packet, Also here in
    % client you are waiting for a packet ! So nothing will happen
    % If you want to use gen_tcp:recv, I recommend to use it with third 
    % argument which is timeout
    case gen_tcp:recv(Sock, 0) of
       {ok, Data} ->
            io:format("[C]  ~p~n", [Data]);
        {error, closed} ->
            io:format("[C] closed~n", []);
        E ->
            io:format("[C] error ~p~n", [E])
    end.

% ...

I recommend to rewrite some parts of the code. It's better to separate client and server code. Also I recommend to name thing correctly and in Erlang, both module and function names should be lowercase. It's recommended. Instead of using if, you can use case, for example:

if 
Task =:= "message" ->
    {erlSoc, self()} ! {message, Rname, Message};
Task =:= "create" ->
    {erlSoc, server_node()} ! {create, Rname, Soc};
Task =:= "list" ->
    {erlSoc, self()} ! {list, Soc};
Task =:= "join" ->
    {erlSoc, self()} ! {join, Rname, Soc};
Task =:= "leave" ->
    {erlSoc, self()} ! {leave, Rname, Soc}
end.

can be:

case Task of 
    "message" ->
        {erlSoc, self()} ! {message, Rname, Message};
    "create" ->
        {erlSoc, server_node()} ! {create, Rname, Soc};
    "list" ->
        {erlSoc, self()} ! {list, Soc};
    "join" ->
        {erlSoc, self()} ! {join, Rname, Soc};
    "leave" ->
        {erlSoc, self()} ! {leave, Rname, Soc}
end.
Pouriya
  • 1,626
  • 11
  • 19
  • How do I make the socket within client_receive able to accept incoming messages from the server. You said something about it being in active mode but how do I make it otherwise? – Sam Dec 15 '18 at 13:47
  • @Jacob Like your listening socket ! in third line you used `{active, false}` as socket option which is used for server side. In client side for opening socket you should specify this option too. `gen_tcp:connect("localhost", 5300, [{active, false}, binary, {packet, 0}])` – Pouriya Dec 15 '18 at 14:06
  • Ok, I made an update and now I'm getting ** exception error: no match of right hand side value {error,econnrefused} in function erlSoc:logon/1 (erlSoc.erl, line 105). And I didn't really get what you were saying about the client waiting for a packt. What exactly would I change for that part? – Sam Dec 15 '18 at 15:23
  • I used gen_tcp:controlling process on the thread that spawns client and client_receive and I"m still getting `{error,not_owner}`. Is it because I removed ownership from the first thread when I assigned it to the second thread? How could I get around this? – Sam Dec 15 '18 at 17:44
  • It's not recommended to pass socket to different processes many times. when you do that, in other processes you have to wait for process which is owner of socket, before reading any data from socket. – Pouriya Dec 16 '18 at 15:00
  • If I do something like `spawn(erlSoc, client, [Sock, Uname]),` my client function runs and then ends, without getting user input, and without doing any recursion – Sam Dec 19 '18 at 14:08
  • @Jacob, consider asking your new questions as separate question or start discussion in the [Erlang chat](https://chat.stackoverflow.com/rooms/75358/erlang-otp). – RimeBeliskner Dec 21 '18 at 18:18